# Cobra — language guide for AI code generation

Cobra is a Python-like scripting language implemented in Go. It runs on two
interchangeable engines (a tree-walking interpreter and a bytecode VM) that
behave identically. This document is a complete, precise reference for
writing **correct** Cobra. Cobra looks like Python but is NOT Python — read
the "What Cobra does NOT have" section to avoid generating invalid code.

## Running

    cobra script.cobra          # run on the tree-walker
    cobra --vm script.cobra     # run on the bytecode VM (same behavior)
    cobra build script.cobra    # compile to script.cobrac bytecode
    cobra repl                  # (just `cobra` with no args) interactive REPL

## Syntax basics

- Statements end at a newline. There are NO semicolons.
- Blocks are closed with the keyword `end` (not indentation, not braces).
- Comments start with `#` and run to end of line.
- Booleans are `true` / `false` (lowercase). The empty value is `null`
  (NOT `None`, `nil`, or `undefined`).
- Logical operators are the WORDS `and`, `or`, `not` (NOT `&&`, `||`, `!`).
- `elif` is one word (there is no `else if`).

## Values and types

Types: integer (64-bit), float, string, boolean, null, list, dict, function,
struct/instance, plus concurrency handles (task, mutex, channel).

    let n = 42            # integer
    let f = 3.14          # float
    let s = "hello"       # string (double quotes only)
    let b = true          # boolean
    let nothing = null
    let xs = [1, 2, 3]    # list
    let d = {"a": 1}      # dict

`type(x)` returns the type name as a string. `str`, `int`, `float` convert.

## Variables

`let` declares a (mutable) variable; plain `x = ...` reassigns an existing
one (error if it was never declared). `const` declares an immutable binding.

    let count = 0
    count = count + 1          # OK (reassign). NOTE: there is no `+=`.

    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

## Operators

Arithmetic `+ - * / %`, comparison `== != < > <= >=`, logical `and or not`.
`+` concatenates strings and (with care) is only numeric/strings — there is
NO `//` floor division. Precedence follows Python.

## Control flow

    if x > 10
        print("big")
    elif x > 0
        print("small")
    else
        print("non-positive")
    end

    while count < 5
        count = count + 1
    end

    for item in [10, 20, 30]
        print(item)
    end

`for ... in` iterates lists, strings (character by character), dict keys, and
`range(...)`. `range(stop)`, `range(start, stop)`, `range(start, stop, step)`
work like Python's but RETURN A LIST. Use `break` and `continue` in loops.

## Functions

A function body must be on its OWN line(s) — you cannot write the body inline
on the `def` line. Functions are first-class and close over their scope.

    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`.
- There is NO `lambda`; to make a function inline, nest a `def` and return it:

      def adder(n)
          def add(x)
              return x + n
          end
          return add
      end
      let add5 = adder(5)
      print(add5(10))   # 15

### Decorators

One or more `@expr` lines above a `def` transform or register it (like
Python). `@d` rebinds `name = d(name)`; `@d(args)` calls `d(args)` first.
They stack (nearest the `def` applies first) and work on `async def`.

    let routes = {}
    def route(path)
        def register(fn)
            routes[path] = fn
            return fn
        end
        return register
    end

    @route("/home")
    def home(req)
        return "welcome"
    end

## Structs (simple classes)

    struct Point
        const ORIGIN_X = 0          # class constant -> Point.ORIGIN_X

        def init(x, y)              # constructor; self is implicit
            self.x = x
            self.y = y
        end

        def length()
            return self.x * self.x + self.y * self.y
        end

        get magnitude()             # property: read as p.magnitude (no parens)
            return self.length()
        end

        static def unit()           # static: Point.unit(), no self
            return Point(1, 1)
        end
    end

    let p = Point(3, 4)
    print(p.x)              # 3
    print(p.length())      # 25  (method call needs parentheses)
    print(p.magnitude)     # 25  (property, NO parentheses)
    print(Point.ORIGIN_X)  # 0   (class constant)
    let u = Point.unit()   # static method

Instances print as `Point{x: 3, y: 4}` and compare by identity. New fields
may be added by assignment (`p.z = 9`). There is NO inheritance, NO `super`,
and NO operator overloading / dunder methods.

## Error handling

    try
        let data = json.parse(text)
    catch err
        print("failed:", err)
    finally
        print("always runs")
    end

    throw "something went wrong"   # any value can be thrown
    throw {"code": 404}            # including dicts

`catch` catches everything (there are no exception types). Runtime errors are
caught as their message string, e.g. `"line 3: division by zero"`. At least
one of `catch`/`finally` is required.

## Lists

    let xs = [3, 1, 2]
    xs.push(4)            # also: push(xs, 4)
    let last = xs.pop()   # also: pop(xs)
    xs.sort()             # in place
    xs.reverse()          # in place
    print(xs.contains(2)) # true
    print(xs.find(2))     # index or -1
    print(xs.count(2))    # occurrences
    print(len(xs))        # length
    print(xs[0])          # indexing
    xs[0] = 99            # index assignment

NOTE: there is NO slicing (`xs[1:3]` is invalid) and NO list comprehensions.

## Dicts

    let ages = {"alice": 30, "bob": 25}
    ages["carol"] = 35
    print(ages["alice"])
    print(ages.keys())            # also keys(ages)
    print(ages.values())
    print(ages.has("bob"))        # also has(ages, "bob")
    print(ages.get("dave", 0))    # default if missing
    ages.del("bob")               # also del(ages, "bob")
    for pair in ages.items()      # NO unpacking: items() gives [key, value] lists
        print(pair[0], pair[1])
    end

## Strings

Strings are immutable. Build them with `+` and `str(...)` — there are NO
f-strings and NO interpolation.

    let name = "ada"
    print("hello " + name + ", age " + str(36))

    name.upper()              name.lower()
    name.strip()              name.split(",")
    ", ".join(["a", "b"])     name.replace("a", "A")
    name.contains("d")        name.startswith("a")  name.endswith("a")
    name.find("d")            name.count("a")

## Built-in functions

`print`, `len`, `type`, `str`, `int`, `float`, `range`, `push`, `pop`,
`keys`, `values`, `has`, `del`, plus higher-order list builtins:

    def sq(n) ... end   # (body on its own line)
    map(sq, [1, 2, 3])              # [1, 4, 9]
    filter(is_even, [1, 2, 3, 4])   # [2, 4]
    reduce(add, [1, 2, 3, 4])       # 10   (folds left; fn(acc, elem))
    reduce(add, [1, 2, 3], 100)     # 106  (with an initial value)
    zip([1, 2], ["a", "b"])         # [[1, "a"], [2, "b"]]
    enumerate(["x", "y"])           # [[0, "x"], [1, "y"]]
    any([0, 1])                     # true
    all([1, 0])                     # false

These are EAGER (they return lists, not lazy iterators).

## Concurrency — no GIL, true parallelism

Calling an `async def` returns a task immediately; tasks run in parallel
across CPU cores. `await` joins.

    import "time"
    async def work(n)
        time.sleep(0.05)
        return n * n
    end

    let results = await [work(1), work(2), work(3)]   # gather, in order
    let single = await work(4)

Because tasks run in true parallel, shared mutable state must be synchronized
explicitly:

    let m = Mutex()                 # also RWMutex() for 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 buffered
    async def produce()
        ch.send(42)
    end
    produce()
    let v = ch.recv()               # also try_send / try_recv (non-blocking), len(ch)

    # select waits on several channels at once:
    let r = select([ch, [ch2, "value"]])   # recv ch, or send "value" to ch2
    # r is {index, value, ok, send}; select(ops, default) is non-blocking.

`Mutex`: `lock` / `unlock` / `try_lock`. `RWMutex`: `rlock` / `runlock` /
`lock` / `unlock`. `Channel`: `send` / `recv` / `close` / `try_send` /
`try_recv`. `http.serve` handlers run in parallel — guard shared state.

## Modules and the standard library

Import another Cobra file by path, or a built-in module by name; access
members with `.`:

    import "lib/utils.cobra"     # runs the file once; use utils.thing(...)
    import "math"
    print(math.sqrt(16))

Built-in modules and key members:

- `fs`: read, write, append, read_lines, write_lines, exists, size, remove,
  rename, mkdir, list_dir, is_dir, cwd
- `math`: pi, e, sqrt, abs, floor, ceil, round, pow, sin, cos, tan, log, exp,
  min, max, random
- `os`: args, env, setenv, platform, time
- `json`: parse, stringify
- `time`: now, clock, sleep, format, parse, parts
- `re`: matches, find, find_all, groups, replace, split
- `http`: get, post, request, serve   (serve handlers run in parallel)
- `proc`: run, shell
- `net`: connect, listen, accept, send, recv, recv_line, close

## What Cobra does NOT have (do NOT generate these)

These are common Python features that DO NOT exist in Cobra. Generating them
produces errors:

- NO f-strings or string interpolation. Use `"x=" + str(x)`.
- NO list/dict/set comprehensions. Use `map`/`filter` or a `for` loop.
- NO tuples, NO multiple assignment (`a, b = 1, 2`), NO unpacking
  (`for k, v in d.items()`). Iterate `d.items()` and index `pair[0]`,
  `pair[1]`.
- NO slicing (`xs[1:3]`, `s[::-1]`). Index a single element only.
- NO augmented assignment (`+=`, `-=`, `*=`, ...). Write `x = x + 1`.
- NO ternary expression (`a if c else b`). Use an `if`/`else` statement.
- NO `lambda`. Nest a `def` and return it.
- NO default or keyword arguments; NO `*args` / `**kwargs` in user functions.
- NO `match` / `switch`. Use `if`/`elif`/`else`.
- NO inheritance, NO `super`, NO interfaces, NO operator overloading / dunder
  methods (`__eq__`, `__str__`, ...).
- NO sets. NO `//` floor division. NO big integers (int is 64-bit).
- NO `with` / context managers. NO generators / `yield`.
- NO `None`/`True`/`False` — use `null`/`true`/`false`.
- A `def` body MUST be on the line(s) after `def name(...)`, never inline.

## A complete example

    # A small word-count program.
    import "fs"

    def count_words(text)
        let counts = {}
        for word in text.split(" ")
            let w = word.strip().lower()
            if w == ""
                continue
            end
            if counts.has(w)
                counts[w] = counts[w] + 1
            else
                counts[w] = 1
            end
        end
        return counts
    end

    let counts = count_words("the cat the dog the bird")
    for pair in counts.items()
        print(pair[0] + ": " + str(pair[1]))
    end
