Overview
Cob is a corn-themed hybrid language with Python-style,
indentation-and-colon block structure. A program is a sequence of
statements; blocks (currently only while —
there is no if/else) are opened with a
trailing : and their body is whatever's indented
further than the header line.
set x = 5 while x > 0: pop("counting down") set x = x - 1 pop("liftoff")
Indentation is spaces only — tabs are a hard error.
The toolchain
| Tool | What it does |
|---|---|
cob_interp | Interprets a .cob file directly, with a fast-boot .strawberry bytecode cache. Ships in several builds — see below — that add SQLite, a native window, and/or Tcl/Tk on top of the same core language. |
popcorn_comp | Compiles a .strawberry file to a real, standalone native executable. Only the core language on this page (not the SQLite/_cobwindow/Tcl/Tk extensions below) is currently supported. |
farmer | The package manager — installs .cob libraries from a static registry. |
Language reference
pop("text")
Writes text to stdout followed by a newline.
pop("hello") # hello pop("line one\nline two")
Supported escapes inside the string literal: \\, \", \n, \t.
set <name> = <expr>
Assigns a value to a variable, creating it if it doesn't exist
yet. A value is either an integer or a string. Expressions support
+ - * / with standard precedence
(*// bind tighter than
+/-) on two integers; + also
concatenates when either side is a string, converting the other
side to text first.
set a = 2 + 3 * 4 # 14 set b = a - 1 set greeting = "count: " + a # "count: 14"
while <condition>:
Loops while the condition is true. A bare expression is a
truthiness test (nonzero); a comparison uses
== != < > <= >=, and works the same way on
two strings as it does on two integers. Loops nest to any depth
and are capped at 10,000,000 iterations as a safety net against
runaway/infinite programs.
while a != 0: pop("tick") set a = a - 1
shuck <library_name>
Loads <library_name>.cob (checking the current
directory first, then
cob_modules/<library_name>/<library_name>.cob
— the layout farmer harvest
installs into) and splices its statements in at that point, like a
textual include. Circular imports and excessive nesting depth are
rejected with a clear error.
shuck greeter pop("back in the main program")
harvest(<bytes>) / trash(<variable>)
Low-level manual memory: harvest(n) is an expression
that allocates n bytes and returns an opaque integer
handle; trash(v) frees the handle held in v.
Both require the --no-gc flag — a
program that uses either without it is refused outright by both
cob_interp and popcorn_comp, not silently
ignored.
set p = harvest(64)
trash(p)
trash(). Double-free and unknown-handle use are
reported as warnings, not crashes.
_MakeCache = False
If the literal first line of a .cob file is
_MakeCache = False (whitespace/case-insensitive), the
.strawberry fast-boot cache is disabled entirely for that file
— neither read nor written.
_MakeCache = False
pop("always reparsed fresh")
Extensions 0.0.5
SQLite — sql_open / sql_exec / sql_query / sql_close
A thin binding onto SQLite's C API, one open database handle per
call to sql_open().
| Call | Returns |
|---|---|
sql_open(<path>) | handle (int), 0 on failure |
sql_exec(<handle>, <sql>) | rc (int), 0 on success |
sql_query(<handle>, <sql>) | string — first column of the first row, "" if none |
sql_close(<handle>) | 0 |
set h = sql_open("scores.db") set rc = sql_exec(h, "CREATE TABLE IF NOT EXISTS t (name TEXT)") set rc = sql_exec(h, "INSERT INTO t VALUES ('cob')") set name = sql_query(h, "SELECT name FROM t LIMIT 1") pop("got: " + name) set rc = sql_close(h)
configure step, no separate library install.
_cobwindow — a native window, backed by raylib + raygui
A small, real (not simulated) desktop window, opened with
shuck cobwindow first.
| Call | Returns |
|---|---|
window_open(<title>) | handle (int), 0 on failure |
window_label(<h>, <text>) | 0 — sets/replaces the window's text |
window_wait(<h>, <seconds>) | 0 — keeps the window responsive for ~<seconds> |
window_close(<h>) | 0 |
window_button(<h>, <label>) | 1 if clicked since the last read of that label, else 0 |
window_slider(<h>, <label>, <max>) | current value, 0..<max>, as an int |
window_textbox(<h>, <label>) | current string contents of the box |
shuck cobwindow set h = window_open("Cob Window Demo") set rc = window_label(h, "Hello from Cob!") set clicked = window_button(h, "OK") set volume = window_slider(h, "Volume", 100) set name = window_textbox(h, "Name") set rc = window_wait(h, 5) set rc = window_close(h)
Each widget is identified by its own label text, not a separate
handle — the first window_button()/
window_slider()/window_textbox() call
with a given label creates that widget, auto-stacked on screen
below the window_label() text; every later call with
the same label (and kind — a button and a slider can share a
label without colliding) reads or updates that same widget.
Declaring and reading happen in the same call, since Cob has no
separate "declare once, poll every frame" mechanism:
window_button()'s click flag resets on read, so a click is reported exactly once.window_slider()re-ranges (and clamps the current value into the new range) if called again with a different<max>.window_textbox()is always in an editable state — Cob has no click-to-focus concept to hook a toggle to.
window_open() again before closing the first warns
and returns 0. The window's own close button
([X]) is intentionally ignored; Cob has no callback
mechanism to notify a running script that it was clicked, so the
window stays open until window_wait()'s timer runs
out or window_close() is called explicitly —
the same "explicit close only" model harvest()/
trash() use for memory. Running more than one
window_textbox() at a time hasn't been verified for
keyboard-focus behavior; treat that as unverified rather than
unsupported.
Tcl/Tk — tcl_eval / tk_eval
| Call | Returns |
|---|---|
tcl_eval(<script>) | string — Tcl's own string result |
tk_eval(<script>) | string — same interpreter as tcl_eval, with Tk also loaded |
pop("answer=" + tcl_eval("expr {6*7}")) # answer=42
cob_interp_db build — see
below. Needs a real X11 $DISPLAY
at runtime for tk_eval(); without one it evaluates to
a normal Tcl error string, the same as real Tk would report, not a
crash.
Tool reference
cob_interp
cob_interp <file.cob> [--no-cache] [--no-gc]
| --no-cache | Skip reading/writing the .strawberry cache for this run. |
| --no-gc | Unlock harvest()/trash(). |
Four build variants, all built from the same src/cob_interp.c:
| Build | Adds on top of the core language above |
|---|---|
cob_interp | Nothing — zero vendor dependency, builds in under a second. |
cob_interp_window | _cobwindow (raylib + raygui). |
cob_interp_full | SQLite + _cobwindow — what a plain make builds by default. |
cob_interp_db | SQLite + Tcl/Tk (no _cobwindow). |
sql_*/window_*/tcl_eval/tk_eval
keyword in a build that doesn't include it prints a one-line
warning and evaluates to a harmless default (0 or
"") rather than failing to build or crashing.
popcorn_comp
popcorn_comp <file.strawberry> -o <output> [--no-gc] [--cc <compiler>] [--emit-c <path.c>]
Transpiles the bytecode to plain C, then spawns a real C compiler
to produce a native executable. Default backend is
Zig's zig cc —
a Clang-based drop-in C compiler that bundles libc/CRT files for
essentially every target in one install, which is what makes
CobOS/CobArch cross-compiling a plain
-target flag instead of needing a separately
installed cross-toolchain per platform.
Compiler is resolved in this order:
| --cc <path> | Explicit override, always wins. |
$CobCC | Environment variable override. |
$CobOS / $CobArch | Looked up in a table of Zig target triples. |
zig cc | Default: native compile, Zig auto-detects the host. |
CobOS=windows CobArch=amd64 popcorn_comp prog.strawberry -o prog.exe
# -> spawns: zig cc -target x86_64-windows-gnu ...
cob_interp-only for now —
popcorn_comp's codegen doesn't implement any of them
yet. A .strawberry cache that uses one of those
keywords still loads (the format is shared and stays
backward-readable), it just fails cleanly at the specific
unsupported call instead of at load time.
farmer
farmer harvest <package>
Fetches {registry}/api/v1/packages/<package>.json,
verifies the metadata's name field matches what was
requested, downloads the package zip, verifies its
sha256 if one was provided, and unzips it into
cob_modules/<package>/ — ready for
shuck <package>.
$COB_FARMER_BASE_URL | Override the registry base URL. |
The registry itself lives at pixel-pulse-labs/cpi, served for free via GitHub Pages — no server, just static JSON files.