cheatah ↔ Python
A living document of what Python features cheatah supports and the small set of deliberate deviations from Python syntax (chosen for simplicity and a clean, fast compile to C++). Goal: most Python scripts port to cheatah with light edits. Update this as the language grows.
TL;DR of the deviations: blocks use { } not indentation; let declares a variable; fn not def; struct not class; everything is imported (even print, from io); types are inferred and static under the hood.
✅ Supported (and how it maps to Python)
Feature | cheatah | Python |
|---|---|---|
Comments |
|
|
Int / float / str / bool |
| same, but |
Variable declaration |
|
|
Reassignment |
|
|
Arithmetic |
| same |
Power |
|
|
Comparison |
| same |
Logical |
| same |
|
|
|
|
|
|
|
|
|
|
| same |
|
|
|
Function def |
|
|
Exceptions |
|
|
Recursion | works | works |
Function call |
| same |
Indexing |
| same |
Slicing |
| same |
List literal |
|
|
Empty typed list/dict |
|
|
Growable list |
|
|
Dict literal |
|
|
Index assignment |
| same |
Iterate a container |
|
|
String concatenation |
| same |
String predicates |
|
|
Method-call syntax |
|
|
Module import |
| same |
Member access |
| same |
Records |
|
|
Methods |
|
|
Interfaces |
|
|
Enums |
|
|
Construction / fields |
|
|
|
|
|
Built-ins |
|
|
True / False are written lowercase (true / false).
Enums
enum declares a scoped, type-safe enumeration — it lowers to a C++ enum class, not a plain C enum, so members never implicitly convert to integers and are always reached through the enum's name:
# cheatah # Python
enum Color { from enum import Enum
RED class Color(Enum):
GREEN RED = 1
BLUE GREEN = 2
} BLUE = 3
enum Status { OK = 0, WARN = 1, FAIL = 2 } # members reached as Color.REDMembers are separated by newlines, commas, or semicolons.
A member may carry an explicit value (
OK = 0); without one it follows C++ rules (the previous value plus one, starting at 0).Access members through the enum name —
Color.RED(→Color::RED). Compare with==/!=,matchon them, store them instructfields (state: Color), and pass them to functions.They print for debugging:
io.print(Color.RED)showsColor.RED(and they work inio.formatand inside printed lists/dicts too), just like Python. An out-of-range value (e.g. from acpp { … }cast) showsColor(<n>).
Command-line programs
import sys exposes sys.argv (a list[str] — sys.argv[0] is the program name, sys.argv[1:] the arguments), just like Python. purrc always compiles a program to a loadable module (.so/.dylib/.dll); the cheatah runtime runs it and forwards the command-line arguments into sys.argv:
purrc app.purr -o app.so # a module (purrc never emits a standalone binary)
cheatah app.so one two # sys.argv == ["app.so", "one", "two"]To ship a program as its own command (so users type app …, not cheatah app.so …), build a tiny native launcher that invokes the runtime on the module — that is what cheatah_add_program() and the biome package manager do. Compiled cheatah code therefore always runs under the runtime, never standalone.
🔀 Deliberate deviations from Python (and why)
Blocks use braces
{ }, not indentation + colons.# cheatah # Python if x > 0 { if x > 0: f() f() }Why: the lexer stays simple (no INDENT/DEDENT), and braces give C-style structure. Porting: replace
:+ indentation with{ … }.letdeclares a new variable; bare=reassigns.let x = 1thenx = 2. Why: a clear declaration point maps to C++auto x = …;. Porting: addleton first assignment of a name.fninstead ofdef.fn add(a, b) { return a + b }.structinstead ofclass— with methods and interfaces. Astructis more than a data class: it carries typed fields, methods, and can declare the interfaces it fulfills.# cheatah # Python interface Shape { from typing import Protocol fn area(self) class Shape(Protocol): } def area(self) -> float: ... struct Circle : Shape { @dataclass r: float class Circle: fn area(self) { r: float return 3.14159 * self.r * self.r def area(self): } return 3.14159 * self.r ** 2 } let c = Circle(2.0) c = Circle(2.0) io.print(c.area()) print(c.area())Fields are typed (
int float str bool, a container, or another struct).Methods take
selffirst (fn area(self) { … }), are called with method syntax (c.area()), and compile to real member functions.Interfaces are C++20 concepts:
interface Shape { fn area(self) }lists required methods, andstruct Circle : Shape { … }makes the compiler verify statically thatCirclefulfillsShape(astatic_assertat compile time — not duck-typed at runtime). A parameter typed by an interface (fn describe(s: Shape) { … }) constrains what may be passed — fast (no virtual dispatch) and enough for patterns like the strategy pattern.Inheritance lives in interfaces, not structs. A struct never inherits — it stays a simple bag of fields + methods that implements one or more interfaces. Interfaces today are flat: refining one interface from another (C++ concept subsumption) is on the roadmap, not in yet. Struct inheritance is a deliberate non-goal, so the interface graph is where any "is-a" structure lives.
No custom constructor /
__init__yet — construction is positional over the fields in declaration order (Circle(2.0),Bar("d", 1.0)).
Everything is imported — including
print.printlives inio(import iothenio.print(...)); the math functionsabs/min/max/round/powlive inmath(math.abs(...)), not as globals. Why: explicit dependencies = the compiler links exactly what you use. Porting: add the relevantimportand qualify (print→io.print,sqrt→math.sqrt). (Truly global built-ins likelen/hex/ordneed no import.) Resolution is Python-like:import a.b.cis found first next to the source (a/b/c.purr/.hpp), then in any--import-root <dir>(a package manager passes one per dependency — see biome's[dependencies]), else the compiler errors telling you how to point it at the module.^is bitwise-xor (C++), not power. Use**for exponentiation (it maps tostd::pow), e.g.2 ** 10./is true division;//floors. Like Python 3,/always yields a float (evenint / int), and//floor-divides toward −∞.Containers map to STL types.
list[T]→std::vector<T>,dict[K,V]→std::unordered_map<K,V>,array[T,N]→std::array<T,N>(static). List/dict literals infer element types via C++ CTAD, so they must be non-empty (an empty[]/{}needs a type annotation). Iterating adictyields key/value pairs (not keys like Python).Statically typed under the hood (type inference).
let/params use C++auto, so a variable's type is fixed by its initializer. No dynamic re-typing (x = 1; x = "s"won't work).No indentation significance. Newlines separate statements;
{ }groups them; indentation is purely cosmetic. A;is an optional statement separator/terminator (let a = 1; let b = 2, a trailingx = x + 1;, or to separatestructfields) — handy if you're used to Python's;or C++'s, but never required.Exceptions are message-based & catch-all.
try { … } except e { … }catches any error and bindseto the message string (not an exception object);raise "msg"throws a generic error. No typedexcept ValueError,as, orfinallyyet.withfor resources — RAII, not a context-manager protocol.with expr [as name] { … }bindsexprfor the block and lowers to a plain C++ scope, so the value's destructor runs at block exit on every path (return/break/exception). It is the direct analog of Python'swith open(…) as f:— cheatah'sio.open,socket.open/serve,tls.open, andwebsocket.open/open_urlreturn owning guard values that close on scope exit. Unlike Python there is no__enter__/__exit__protocol: any value works, and cleanup is just its destructor.withyields no value binding beyondname, and there is noelse/multiple-context (with a, b:) form yet — nest blocks instead.
🚧 Not yet supported (roadmap)
Comprehensions; typed exceptions & finally; interface refinement (one interface building on another — interfaces are flat for now); custom constructors / __init__ (construction is positional for now); f-strings & rich string formatting (use io.format); slice assignment (a[1:3] = …) and step slices (a[::2]); tuples/unpacking; generators/yield; lambda.
All tracked toward frictionless Python → cheatah porting. Struct inheritance is a non-goal by design — structs stay simple and only implement interfaces; all "is-a" structure lives in the interface graph (#4).
For full .py → .purr ports, see the worked examples on the Coming from Python page.
