# Cobra — complete language specification for AI code generation Cobra is a general-purpose scripting language implemented in Go. It runs on two interchangeable engines — a tree-walking interpreter and a bytecode VM — that behave identically, and it can also be compiled ahead of time to a native machine-code executable. This document is a COMPLETE and PRECISE reference: every construct, builtin, method and module below was verified against the running language. Cobra has its own syntax and its own rules. It is not a dialect of any other language — do not transfer syntax from elsewhere. The "What Cobra does NOT have" section at the end lists the constructs that will fail if you generate them. Everything in this document works on both engines and prints identical output. --- ## 1. Running Cobra cobra script.cobra # tree-walking engine cobra --vm script.cobra # bytecode VM (same behavior, faster) cobra --native script.cobra # compile to machine code, then run cobra # interactive REPL (multi-line aware) cobra --vm # REPL on the VM cobra build script.cobra # -> script.cobrac (bytecode) cobra build --bundle script.cobra # bytecode with all imports embedded cobra build --exe script.cobra # self-contained binary (VM embedded) cobra build --native script.cobra # standalone native executable cobra script.cobrac # run compiled bytecode cobra fmt -w script.cobra # format source in place cobra watch script.cobra # re-run on file change cobra --check script.cobra # parse-only syntax check cobra lsp # language server (stdio) Extra arguments after the file reach the program via `os.args()`. **Native compilation** is optional and semantics-preserving — identical output, just faster. It supports the type-stable core (functions, structs with `init` and methods, lists, dicts, strings, `while` / `for`-`in` / `if`, and the `math` module). Programs using closures, `async`, `try`/`catch`, inheritance, records, decorators or other modules are reported as "not natively compilable" and run on the VM instead. When generating code, do not assume `--native` applies. --- ## 2. Syntax fundamentals - Statements end at a newline. There are **no semicolons**. - Blocks open with a keyword and close with **`end`** — not indentation, not braces. Indentation is purely cosmetic. - Comments start with `#` and run to end of line. There are no block comments. - Booleans are `true` / `false` (lowercase). The empty value is **`null`** (never `None`, `nil`, or `undefined`). - Logical operators are the **words** `and`, `or`, `not` (never `&&`, `||`, `!` — though `!` works as a prefix negation alias). - `elif` is one word. There is no `else if`. - A function body must begin on the line **after** `def name(...)`; a `def` header and its body are never on one line (except the arrow form, §6.2). - A line ending in a binary operator (`+ - * / %`, comparisons, `and`, `or`, `=`, the augmented assigns, `.`, `,`, an open bracket) **continues** onto the next: let total = price + tax + shipping **Keywords** (reserved): `let const def func end if elif else while for in break continue return try catch finally throw import from export as struct class record contract super async await true false null and or not`. (`func` is a synonym for `def`; `class` for `struct`.) **Declaration order.** A name is usable once its declaration has *run*, with one important exception: a **function body** may reference a `def` or `struct` declared later in the file, because the body only runs when called. So mutual recursion works: def is_even(n) if n == 0 return true end return is_odd(n - 1) # is_odd is defined below — fine end def is_odd(n) return n == 0 ? false : is_even(n - 1) end But **top-level code cannot use a name before its declaration line**: `print(later(2))` above `def later(x)` is an "undefined variable" error. Put `import` statements at the top of the file, declare a `contract` before the code that references it, and call functions from top level only after their `def`. --- ## 3. Values and types | type | literal | notes | |---|---|---| | INTEGER | `42`, `-7` | 64-bit signed. No big integers. | | FLOAT | `3.14`, `1.0` | 64-bit IEEE-754. Prints `3.0`, never `3`. | | DECIMAL | `19.99m` | Exact fixed-point (money). `0.1m + 0.2m` is exactly `0.3`. | | STRING | `"hi"`, `"""multi\nline"""` | Immutable, UTF-8, **character-addressed**. | | BOOLEAN | `true`, `false` | | | NULL | `null` | | | LIST | `[1, 2, 3]` | Mutable, heterogeneous. | | DICT | `{"a": 1}` | Insertion-ordered. Keys: string, int, float, bool. | | RANGE | `range(10)` | Lazy integer sequence — list-like but NOT a list. | | FUNCTION | `def`, `def(x) -> x` | First-class, closes over scope. | | instance | `Point(1, 2)` | `type(p)` is the struct name, e.g. `"Point"`. | | TASK / MUTEX / CHANNEL | `work()`, `Mutex()`, `Channel()` | Concurrency handles. | `type(x)` returns the type name as a string (`"INTEGER"`, `"LIST"`, `"Point"`…). Conversions: `int(x)`, `float(x)`, `str(x)`, `decimal(x)`. **Truthiness:** falsy values are `null`, `false`, `0`, `0.0`, `""` (empty string), `[]` (empty list) and `{}` (empty dict). Everything else is truthy — so `if xs` is false for an empty list, and `while queue` drains it. **Numeric promotion:** an int mixed with a float yields a float (`1 + 2.5` is `3.5`). Integer `/` integer is **integer division** (`7 / 2` is `3`); use `float(7) / 2` for `3.5`. A decimal mixed with an int stays exact; mixed with a float it degrades to float. **Strings are character-addressed** (rune-based, not byte-based): `len("héllo")` is `5`, and `"héllo"[1]` is `"é"`. --- ## 4. Variables `let` declares. Plain `x = ...` reassigns an existing binding (error if never declared). `const` declares an immutable binding. let count = 0 count = count + 1 # reassign count += 1 # augmented assignment const PI = 3.14159 # PI = 4 # ERROR: cannot reassign a constant const xs = [1] xs.push(2) # OK — the BINDING is fixed, the value is not **Multi-value assignment** (destructuring) works from any list-yielding value: let a, b = [1, 2] # a=1, b=2 let p, q = 7, 8 # a bare list on the right let first, second = "ab".split("") The element count must match exactly, or it is a runtime error. --- ## 5. Operators Arithmetic: `+ - * / %` — `+` also concatenates strings and lists. Comparison: `== != < > <= >=`. Logical: `and`, `or`, `not` (short-circuiting). Augmented assignment `+= -= *= /= %=` updates an existing target in place and follows the same rules as the plain operator. It works on variables (`x += 1`), index targets (`xs[i] += 1`, `d[k] *= 2`) and fields (`self.n += 1`). It cannot reassign a `const`. Ternary conditional: `cond ? a : b` — only the chosen branch runs. It is right-associative, binds looser than `and`/`or` but tighter than `=`, so `x = c ? a : b` assigns the chosen value. Precedence, loosest to tightest: `=` → `? :` → `or` → `and` → `not` → `== !=` → `< > <= >=` → `+ -` → `* / %` → prefix `-x` `not x` → call `f(...)` → index `x[i]`, property `x.y`. Equality compares by value for numbers, strings, booleans, lists and dicts (deep), and for **records**; plain struct instances compare by **identity**. `1 == 1.0` is true (numeric equality crosses int/float). --- ## 6. Functions def add(a, b) return a + b end let f = add # functions are values print(f(2, 3)) # 5 - Functions take **exactly** the declared number of positional arguments. There are **no default values** and **no keyword arguments**. - No variadic user functions (no `*args` / `**kwargs`). - A function with no `return` yields `null`. - Functions close over their enclosing scope. ### 6.1 Closures def adder(n) def add(x) return x + n # captures n end return add end let add5 = adder(5) print(add5(10)) # 15 ### 6.2 Anonymous functions (lambdas) Cobra HAS anonymous functions, in two forms. The keyword is `def`, never `lambda`. let double = def(x) -> x * 2 # arrow form: one expression, implicit return print(double(21)) # 42 let compute = def(x) # block form: multiple statements let y = x + 1 return y * 10 end print(compute(4)) # 50 Both forms work inline as arguments: `map(def(x) -> x * x, [1, 2, 3])`. ### 6.3 Decorators One or more `@expr` lines above a `def` transform or register it. `@d` rebinds `name = d(name)`; `@d(args)` calls `d(args)` first to get the decorator. They stack (the one nearest the `def` applies first) and work on `async def`. def twice(fn) return def(x) -> fn(fn(x)) end @twice def inc(x) return x + 1 end print(inc(0)) # 2 --- ## 7. Control flow if x > 10 print("big") elif x > 0 print("small") else print("non-positive") end while count < 5 count += 1 end for item in [10, 20, 30] print(item) end `for ... in` iterates lists, strings (character by character), dict **keys**, and `range(...)`. `break` and `continue` work in both loop forms. `range(stop)`, `range(start, stop)`, `range(start, stop, step)` produce a lazy RANGE: indexable, sliceable and iterable, but **not a list** — `type()` is `"RANGE"` and it has no `.push`. Wrap it if you need a list. There is no `match`/`switch` and no `for(;;)` C-style loop. --- ## 8. Strings Immutable, UTF-8, character-addressed. ### 8.1 Literals and interpolation let name = "ada" let msg = "hello " + name # concatenation let s = "hello ${name}, ${1 + 2}" # INTERPOLATION — ${...} takes any expression let block = """ Multi-line text: quotes " and "" need no escaping. Escapes (\n) and ${interpolation} still work. """ Cobra HAS string interpolation with `${...}` (it does not have Python f-strings — there is no `f"..."` prefix; the `${}` form works in every string literal). ### 8.2 String methods s.upper() s.lower() s.strip() s.lstrip() s.rstrip() s.split(",") ",".join(list) s.replace("a", "A") s.contains("d") s.startswith("a") s.endswith("z") s.find("d") # index, or -1 s.count("a") s.slice(1, 3) s.substr(1, 2) # explicit alternatives to slicing len(s) s[0] s[1:3] `chr(n)` is the one-character string for code point `n`; `ord(s)` is the code point of `s`'s first character. --- ## 9. Lists let xs = [3, 1, 2] xs.push(4) # append (also: push(xs, 4)) let last = xs.pop() # remove and return the last (also: pop(xs)) xs.sort() # IN PLACE xs.reverse() # IN PLACE xs.contains(2) # true / false xs.find(2) # index, or -1 xs.count(2) # occurrences len(xs) xs[0] # index (negative counts from the end) xs[0] = 99 # index assignment xs + [5, 6] # concatenation (new list) ### Slicing — `seq[low:high:step]` Works on lists (new list), strings (rune-safe) and ranges (lazy RANGE). xs[1:3] # elements 1..2 xs[:2] # from the start xs[1:] # to the end xs[:] # full copy xs[-2:] # last two — negative indices count from the end xs[::-1] # reversed copy — negative step Out-of-range bounds **clamp** (never error). There is NO slice *assignment* (`xs[1:3] = ...` is invalid). --- ## 10. Dicts Insertion-ordered. Keys may be strings, integers, floats or booleans. let ages = {"alice": 30, "bob": 25} ages["carol"] = 35 ages["alice"] # 30 — missing key is a runtime error ages.get("dave", 0) # 0 — safe lookup with a default ages.has("bob") # true (also: has(ages, "bob")) ages.del("bob") # remove (also: del(ages, "bob")) ages.keys() # list of keys (also: keys(ages)) ages.values() # list of values ages.items() # list of [key, value] pairs len(ages) for key in ages # iterating a dict yields its KEYS print(key, ages[key]) end for pair in ages.items() # NO unpacking — index the pair print(pair[0], pair[1]) end --- ## 11. Structs (classes) struct Point const ORIGIN = 0 # class constant -> Point.ORIGIN def init(x, y) # constructor; `self` is implicit self.x = x self.y = y end def length2() # method — call with parentheses return self.x * self.x + self.y * self.y end get magnitude() # property — read WITHOUT parentheses return self.length2() end set scaled(k) # setter — runs on `p.scaled = 2` self.x = self.x * k end static def origin() # static — Point.origin(), no self return Point(0, 0) end end let p = Point(3, 4) print(p.x) # 3 field print(p.length2()) # 25 method (parentheses) print(p.magnitude) # 25 property (NO parentheses) print(Point.ORIGIN) # 0 class constant let o = Point.origin() # static method Instances print as `Point{x: 3, y: 4}` and compare by **identity**. **Fields are sealed after `init` returns.** Every field must be created in `init`; assigning to a name `init` never created is an error with a did-you-mean suggestion (`p.wieght = 5` → "Point has no field 'wieght' (did you mean 'weight'?)"). This catches typos. Structs without an `init` stay open. A setter must write a **different** field than the property name, or it recurses. ### 11.1 Inheritance Single inheritance with `<`. Methods, getters, setters and constants are inherited. `super.method(...)` calls the parent's version. struct Animal def init(name) self.name = name end def speak() return "..." end end struct Dog < Animal def init(name, breed) super.init(name) # run the parent constructor self.breed = breed end def speak() # override return self.name + " says woof" end end There is no multiple inheritance and no `isinstance` (use a contract). ### 11.2 Records — immutable value objects `record Name ... end` is a struct whose instances are **immutable** and compare by **value**. `.with(dict)` returns a modified copy. record Point def init(x, y) self.x = x self.y = y end end let a = Point(1, 2) print(a == Point(1, 2)) # true — VALUE equality let b = a.with({"y": 9}) # copy with y changed; a is untouched # a.x = 5 # ERROR: cannot modify immutable record ### 11.3 Contracts — duck-typed interfaces `contract Name ... end` lists required method names, one per line. `implements(value, Contract)` checks them at runtime. contract Speaker speak end print(implements(dog, Speaker)) # true There is no operator overloading and no dunder methods (`__eq__`, `__str__`, …). --- ## 12. Error handling try let data = json.parse(text) catch err print("failed:", err) finally print("always runs") end - `catch` catches **everything** — there are no exception types or classes. - At least one of `catch` / `finally` is required; `catch` may omit the variable. - `throw ` raises any value: `throw "boom"`, `throw {"code": 404}`. - **Runtime errors are caught as their message string**, e.g. `"line 3: division by zero"` — so `type(err)` is `"STRING"` for them, but the value of a `throw` is whatever you threw (a dict stays a dict). - Uncaught errors print a call-stack trace and exit non-zero. --- ## 13. Built-in functions **Core:** `print(...)`, `len(x)`, `type(x)`, `str(x)`, `int(x)`, `float(x)`, `decimal(x)`, `range(...)`, `chr(n)`, `ord(s)`, `hash(x)`, `implements(v, C)`. **Collection helpers** (also available as methods): `push(xs, v)`, `pop(xs)`, `keys(d)`, `values(d)`, `has(d, k)`, `del(d, k)`. **Input:** `input(prompt?)`, `read_line()` — both return `null` at end of input. **Higher-order** (all EAGER — they return lists, never lazy iterators): map(fn, xs) # [1,2,3] -> [1,4,9] filter(fn, xs) # keep where fn(x) is truthy reduce(fn, xs) # fold left: fn(acc, elem) reduce(fn, xs, initial) # with an initial accumulator zip(xs, ys) # [[x0,y0], [x1,y1], ...] enumerate(xs) # [[0, x0], [1, x1], ...] any(xs) all(xs) # truthiness over a list **Parallel** (no GIL — these use every CPU core, order preserved): pmap(fn, xs) # parallel map pfilter(fn, xs) # parallel filter preduce(fn, xs, initial) # parallel reduce (fn must be associative) pforeach(fn, xs) # parallel side effects **Concurrency constructors:** `Mutex()`, `RWMutex()`, `Channel(capacity?)`, `select(ops, default?)`. --- ## 14. Concurrency — no GIL, true parallelism Calling an `async def` returns a **task** immediately; tasks run in parallel on real OS threads across CPU cores. `await` joins. import "time" async def work(n) time.sleep(0.05) return n * n end let single = await work(4) # 16 let results = await [work(1), work(2), work(3)] # gather, in input order Because tasks are truly parallel, shared mutable state must be synchronized: let m = Mutex() # also RWMutex() — many readers, one writer let total = 0 async def bump() m.lock() total = total + 1 m.unlock() end await [bump(), bump(), bump()] let ch = Channel() # or Channel(capacity) for a buffered channel async def produce() ch.send(42) end produce() let v = ch.recv() # select waits on several channel operations at once: let r = select([ch, [ch2, "value"]]) # recv from ch, or send "value" to ch2 # r is {"index", "value", "ok", "send"}; select(ops, default) is non-blocking. Methods — `Mutex`: `lock` / `unlock` / `try_lock`. `RWMutex`: `rlock` / `runlock` / `lock` / `unlock`. `Channel`: `send` / `recv` / `close` / `try_send` / `try_recv`, and `len(ch)`. `http.serve` handlers run in parallel — guard shared state. --- ## 15. Modules Import a built-in module by name, or another Cobra file by path. Members are reached with `.`: import "math" print(math.sqrt(16)) import "lib/utils.cobra" # binds `utils` import "lib/utils.cobra" as u # or rename it print(utils.thing()) A module exposes **only what it marks `export`** — nothing is public by default (strict, like ES modules or Rust's `pub`): # geometry.cobra export const PI = 3.14159 export def area(r) # public return PI * r * r end def _helper() # private — invisible to importers return 1 end `export` works on `let`, `const`, `def`, `struct` and `record`. Selective import binds names directly, with no prefix. A typo or a non-exported name fails at import time: from "geometry.cobra" import area, PI as pi from "geometry.cobra" import * # every exported name `cobra build --bundle app.cobra` embeds every imported `.cobra` file into one self-contained bytecode file (`.so` plugins still load from disk). --- ## 16. Standard library All failures are catchable. Import by name. - **`math`** — `pi`, `e`, `sqrt`, `abs`, `floor`, `ceil`, `round` (banker's), `pow`, `sin`, `cos`, `tan`, `log`, `exp`, `min`, `max`, `random`. `min`/`max` take two-plus numbers or one list. - **`fs`** — `read`, `write`, `append`, `read_lines`, `write_lines`, `exists`, `size`, `remove`, `rename`, `mkdir`, `list_dir`, `is_dir`, `cwd`. - **`os`** — `args`, `env`, `setenv`, `platform`, `time`. - **`json`** — `parse(text)`, `stringify(value, indent?)`. - **`time`** — `now`, `clock`, `sleep(seconds)`, `format`, `parse`, `parts`, `make`. - **`re`** — `matches`, `find`, `find_all`, `groups`, `replace`, `split`. - **`http`** — `get`, `post`, `request`, `serve` (handlers run in parallel), `url_encode`, `url_decode`. - **`net`** — `connect`/`dial`, `listen`, `accept`, `send`, `recv`, `recv_line`, `close`. - **`proc`** — `run`, `shell`. - **`encoding`** — `base64_encode`, `base64_decode`, `base64url_encode`, `base64url_decode`, `hex_encode`, `hex_decode`. - **`crypto`** — `sha256`, `sha1`, `md5`, `hmac_sha256`, `random_hex`. - **`random`** — `seed(n)`, `random()` (0..1 float, no args), `int(min, max)`, `float(min, max)`, `choice(list)`, `shuffle(list)`. - **`runtime`** — `gc()`, `free_os_memory()`, `mem()`, `set_gc_percent(pct)`, `num_goroutines()`, `num_cpu()`. (Cobra never frees by hand — this is for profiling.) - **`test`** — a built-in unit-test framework: import "test" @test.it # register a test def addition() test.assert_eq(2 + 2, 4) end let summary = test.run() # runs all, prints a report print(summary["ok"]) # {passed, failed, total, ok} Assertions: `assert(cond, msg?)`, `assert_eq(actual, expected, msg?)`, `assert_neq(a, b, msg?)`, `assert_raises(fn, msg?)`. Register with a custom name via `test.case(name, fn)`. Tests are plain functions. --- ## 17. What Cobra does NOT have — do NOT generate these Every item below is a construct from another language that **does not exist** in Cobra. Generating it produces a parse or runtime error. - **NO f-strings** (`f"..."`). Cobra's interpolation is `"${expr}"` — see §8.1. - **NO list/dict/set comprehensions** (`[x for x in xs]`). Use `map`/`filter` or a `for` loop. - **NO tuples** and **no unpacking in a `for`** (`for k, v in d.items()` is invalid — index `pair[0]` / `pair[1]`). Multi-value *assignment* (`let a, b = ...`) does exist — see §4. - **NO sets.** - **NO `lambda` keyword.** Anonymous functions are `def(x) -> expr` — see §6.2. - **NO default arguments, NO keyword arguments, NO `*args` / `**kwargs`.** - **NO `match` / `switch`.** Use `if` / `elif` / `else`. - **NO `while ... else`, NO `for(;;)` C-style loops.** - **NO `with` / context managers.** - **NO generators, NO `yield`, NO lazy iterators.** `map`/`filter` return lists. - **NO operator overloading, NO dunder methods** (`__eq__`, `__str__`, …). - **NO multiple inheritance, NO `isinstance`** (use a `contract`). - **NO `//` floor division** (integer `/` is already integer division). - **NO `**` power operator** — use `math.pow(x, y)`. - **NO `++` / `--`** — use `+= 1` / `-= 1`. - **NO big integers** — `int` is 64-bit. - **NO `None` / `True` / `False`** — use `null` / `true` / `false`. - **NO semicolons, NO braces for blocks** — statements end at newline, blocks end with `end`. - **A `def` body is never inline** with the header (except the `->` arrow form). - **NO slice assignment** (`xs[1:3] = ...`). - **Empty containers are TRUTHY** — `if xs` is true for `[]`. Use `len(xs) == 0`. --- ## 18. Complete example # inventory.cobra — a small program touching most of the language. import "math" import "json" record Item def init(name, price, qty) self.name = name self.price = price # decimal — exact money self.qty = qty end get total() return self.price * self.qty end end struct Inventory def init() self.items = [] end def add(item) self.items.push(item) end def value() let sum = 0m for item in self.items sum += item.total end return sum end def expensive(limit) return filter(def(it) -> it.price > limit, self.items) end end let inv = Inventory() inv.add(Item("bolt", 0.25m, 100)) inv.add(Item("gear", 12.50m, 4)) inv.add(Item("motor", 89.99m, 2)) print("items: ${len(inv.items)}") print("total value: ${inv.value()}") for item in inv.expensive(10m) print(" ${item.name}: ${item.qty} x ${item.price} = ${item.total}") end let report = {} for item in inv.items report[item.name] = str(item.total) end print(json.stringify(report)) try let broken = inv.items[99] catch err print("caught: ${err}") end Output (verified — this is exactly what the program above prints): items: 3 total value: 254.98 gear: 4 x 12.50 = 50.00 motor: 2 x 89.99 = 179.98 {"bolt":"25.00","gear":"50.00","motor":"179.98"} caught: line 57: list index out of range: 99 Note the decimals: `0.25m * 100` is exactly `25.00`, and the total is exact — no floating-point drift. That is what the `m` suffix is for.