Repository navigation
Cob Language v0.0.5
Overview
Three new capabilities for cob_interp, all opt-in and none of them
affecting the interpreter's original zero-dependency build:
- SQLite (
sql_open/sql_exec/sql_query/sql_close) and
_cobwindow, a native-window GUI backed by raylib
(window_open/window_label/window_wait/window_close) with
real, interactive raygui widgets (window_button/
window_slider/window_textbox), are now baked into
cob_interp_full-- the binary plainmakebuilds by default.
Both build with a plainMakefile, noconfigure/autoconf step for
either, so a cleanmakefrom a fresh checkout builds everything
itself in well under a minute. - Tcl/Tk (
tcl_eval/tk_eval) are still available, but no longer
part of the default build --make cob_interp_dbbuilds a separate
binary with them, for anyone who specifically wants that and is
willing to accept a much heavier, autoconf-based Tcl/Tk source build
(see "Fixed" below for what that build fights through on some
Windows toolchains). make cob_interpremains the original lightweight interpreter --
no SQLite, no_cobwindow, no Tcl/Tk, no vendor build at all.
Also new this release: a stress-test suite (tools/stress/), a
Dockerfile to run it in a reproducible container, and both a
ci.yml GitHub Actions workflow that runs it in Docker and natively,
and a build.yml workflow that now also round-trips the widget
keywords through the .strawberry cache.
Added
SQLite bindings
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 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)
Builds via a single-file amalgamation (vendor/SQLite/sqlite3.c) --
no configure step, compiles the same way on every platform.
_cobwindow -- a native-window GUI backed by raylib, now with real widgets
window_open(<title>) -> handle (int, 0 on failure)
window_label(<handle>, <text>) -> 0 (sets/replaces the in-window text)
window_wait(<handle>, <seconds>) -> 0 (keeps the window responsive for ~<seconds>)
window_close(<handle>) -> 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)
Backed by vendor/raylib (real 2D/3D graphics, proper anti-aliased
text rendering) instead of raw Win32/Xlib calls -- one build recipe
and one code path for every platform, rather than hand-maintained
separate Win32 and Xlib implementations. raylib's own build is a
plain Makefile: no configure, no autoconf, so none of the
busybox-ash/vendor-tree fragility documented under "Fixed" below
applies to it.
vendor/raygui (a header-only immediate-mode widget library that
draws on top of raylib) is now wired up to Cob syntax as
window_button/window_slider/window_textbox. Each widget is
identified by its own label text -- the first call with a new label
creates it, auto-stacked on screen below the window_label() text;
every later call with that 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 -- a
button's click flag resets on read, so a click is reported exactly
once; a slider re-ranges (and clamps) if called again with a
different <max>; a textbox is always in raygui's edit mode, since
Cob has no click-to-focus concept to hook a toggle to.
raylib only supports one native window per process (InitWindow()/
CloseWindow() are process-global, not per-handle) -- window_open()
called again before the first window is closed warns and returns 0,
same "0 means failure" convention every other handle-returning keyword
here uses. The close button ([X]) is intentionally swallowed -- Cob
has no callback mechanism to notify a running script that the user
clicked it, so the window keeps running until window_wait()'s timer
runs out or window_close() is called explicitly, the same
"explicit close only" model harvest()/trash() already use for
memory.
Verified for real, not just built: ran a .cob script against
cob_interp_window/cob_interp_full on a live (headless, via Xvfb)
X11 display and confirmed with actual screenshots that a real window
opened showing the label text, a button, a slider (with a readable
caption -- see "Fixed" below), and an editable text box with a
blinking cursor, all rendered with proper anti-aliased text.
cob_interp_full -- SQLite + _cobwindow, the new default
make (no target, i.e. make all) now builds cob_interp_full
instead of the lightweight cob_interp. It lists $(SQLITE_LIB) and
$(RAYLIB_LIB) as real Make prerequisites, so a single make builds
SQLite and raylib from vendor/ itself, with no separate build steps,
no configure/autoconf anywhere in the chain, and (confirmed) well
under a minute on a clean checkout.
make cob_interp still builds the original lightweight binary --
zero vendor dependency, builds in under a second -- for anyone who
wants that explicitly instead of the new default.
Kept as opt-in: cob_interp_db (SQLite + Tcl + Tk)
tcl_eval(<script>) -> string (Tcl's string result)
tk_eval(<script>) -> string (same interpreter as tcl_eval, plus Tk)
Still available via make cob_interp_db for anyone who specifically
wants tcl_eval()/tk_eval(). This needs a real Tcl/Tk source build
(make sqlite tcl tk, or cob_interp_db triggers it automatically
the same way cob_interp_full triggers SQLite/raylib) -- see "Fixed"
below for the autoconf/busybox-ash issues that build can hit on some
Windows toolchains, now resolved for the two specific failures
reported so far. _cobwindow is not part of this binary; use
cob_interp_full for that.
Stress-test suite, Dockerfile, ci.yml, and build.yml
tools/stress/run_stress.sh builds cob_interp_full and runs three
.cob scripts designed to catch regressions that only show up under
sustained load, not a single quick smoke test:
stress_arithmetic.cob-- 200,000-iteration loop (interpreter
overhead/stability)stress_sqlite.cob-- 2,000 sequentialINSERTs + a query (SQLite
under load)stress_window.cob-- 20 open/label/wait/close cycles (resource
leaks in the raylib backend would show up as the loop slowing down
or crashing, not just "does it open one window")
Dockerfile builds cob_interp_full in a clean Ubuntu 24.04
container with exactly the dependencies it needs (X11 + OpenGL dev
headers, Xvfb) and runs the stress suite as its entrypoint:
docker build -t cob-stress .
docker run --rm cob-stress # one pass
docker run --rm cob-stress --iterations 20 # repeat 20x
.github/workflows/ci.yml runs the same suite two ways -- once via
the Dockerfile (docker-stress job), once directly on the runner
(native-stress job) -- on push/PR to main, nightly on a schedule,
and on manual dispatch with a configurable iteration count.
.github/workflows/build.yml's test_extensions job now also opens
a real headless X11 window and round-trips window_button/
window_slider/window_textbox through the .strawberry cache --
write once, run again to force a cache hit, diff the two runs' output
-- specifically to catch a broken write_expr()/read_expr() pair
for the three new widget expression kinds (see "Fixed" below for why
this matters more than it might sound like).
Legal: raylib/raygui licenses
legal/license_raylib.terms and legal/license_raygui.terms added
(both zlib License). LICENSE.md's "THIRD-PARTY LICENSE NOTICE"
section was also out of date -- it only mentioned TinyCC, which isn't
even in this repo anymore -- rewritten to list every vendored
component actually present (miniz, SQLite, Tcl, Tk, raylib, raygui)
and where its license text lives.
cpi.cob.pixel-pulse.work.gd: fixed the "Package registry" 404 (separate repo)
docs/index.html's footer nav links "Package registry" to
.../download.html, which never existed on the cpi registry site
(Cob-Software-Foundation/cpi, a separate repo) -- only index.html
did, so the link 404'd. Added docs/download.html there: fetches
api/v1/packages/index.json and each manifest client-side, renders
them as cards matching the site's styling. This is a change to the
cpi repo, not this one -- shipped as its own small zip alongside
this release, not included in the main source zip.
Fixed
popcorn_comp: .strawberry magic constant was three format bumps stale
Repro: any plain .cob script, no SQL/window keywords required --
./cob smoke.cob then ./popcorn_comp smoke.strawberry -o smoke_native
failed with:
[popcorn_comp] error: 'smoke.strawberry' is not a valid .strawberry v3 file
Root cause: popcorn_comp.c keeps its own independent, byte-for-
byte-compatible copy of the .strawberry reader (documented in its
own header comment as deliberately duplicated from cob_interp.c),
including its own STRAWBERRY_MAGIC constant. cob_interp.c's magic
has moved on three times since -- COBSTRW3 (strings) -> COBSTRW4
(SQLite/Tcl/Tk) -> COBSTRW5 (_cobwindow) -> COBSTRW6 (this
release's widget keywords) -- but popcorn_comp.c's copy was never
updated past COBSTRW3. Once cob_interp started writing COBSTRW6
caches, every cache it wrote looked invalid to popcorn_comp, even
for scripts using none of the newer keywords -- this had already been
silently broken for the COBSTRW4/COBSTRW5 bumps too, just never
caught until CI ran the widget cache round-trip test above and the
existing end-to-end popcorn_comp smoke test both failed the same
way.
Fix: bumped popcorn_comp.c's STRAWBERRY_MAGIC to COBSTRW6
and de-hardcoded the version number out of its error message. Checked
first, rather than just patching the string blindly, that this was
actually safe: popcorn_comp.c's ExprKind (0-4) and StmtKind (all
4) enums are byte-identical to cob_interp.c's -- the COBSTRW4/
COBSTRW5/COBSTRW6 bumps only ever appended new ExprKind values
after EXPR_HARVEST for keywords popcorn_comp doesn't implement
codegen for anyway, so its read_expr()'s existing
default: return -1 already handles those gracefully (reported as
"corrupt or truncated", not a crash) with no further change needed.
Added a comment on the constant spelling out that it needs bumping by
hand every time cob_interp.c's does, since that manual-sync step is
the actual root cause here.
_cobwindow: slider caption was clipped off-window
window_slider()'s on-screen caption ("Volume: 42") was passed as
raygui's textLeft parameter, which draws to the left of the
slider's bounding box -- at the x=20 left margin used for every
widget here, that put most of the caption off the edge of the window,
invisible. Fixed by passing it as textRight instead (drawn to the
right, where there's room). Caught by an actual screenshot during
verification, not just a build check.
popcorn_comp: Windows system() quoting bug in bundled-Zig auto-resolve
Repro:
./popcorn_comp.exe smoke_test.strawberry -o temp.exe --no-gcwith a zig\ folder present next to popcorn_comp.exe (auto-resolve
path, no --cc given) failed with:
'C:\...\zig\zig.exe" cc -std=c99 ... ' is not recognized as an internal or external command
Root cause: find_bundled_zig_cc() (src/popcorn_comp.c) returns
a pre-quoted compiler string ("path" cc). cob_spawn_compile() then
appends further quoted arguments, producing a command line with
multiple separate quoted tokens that starts with a quote. system()
on Windows hands this to cmd.exe /c <string>, which strips only the
first and last " of a command line that starts with a quote -- not
matching pairs -- corrupting the line. The explicit
--cc "zig\zig.exe cc" path was never affected.
Fix: in cob_spawn_compile(), on Windows, if the assembled
command starts with ", wrap the whole command in one more pair of
quotes before calling system(). Only src/popcorn_comp.c changed;
cob_interp and farmer are unaffected.
Makefile: vendor directory casing
make sqlite/tcl/tk referenced vendor/sqlite/tcl/tk
(lowercase); the real directories are vendor/SQLite/TCL/TK.
Failed outright on any case-sensitive filesystem. Fixed.
Makefile: Windows Tcl/Tk build (for cob_interp_db)
The Tcl/Tk build (used only by cob_interp_db now) needed real
Windows-specific handling that the first pass got wrong: a separate
win/ build tree (not unix/), different static-lib names
(libtcl90.a/libtcl9tk90.a, no dot before "90"), Tk's own
vendor/TK/xlib/ shim instead of real X11, -DSTATIC_BUILD=1 plus
stub libs for both Tcl and Tk, and Windows-specific system libraries
instead of -lX11. Verified by cross-building the entire chain with a
real x86_64-w64-mingw32 toolchain through the actual Makefile
targets.
Two further real-world Windows build failures were reported and
fixed the same way (reading the vendored configure script's exact
logic to find the specific escape-hatch environment variable, since
w64devkit's bundled sh -- BusyBox ash -- parses a Windows-style
PATH incorrectly and breaks autoconf's own tool-detection loops):
configure: error: no acceptable C compiler found (fixed via
CC="$(CC)") and configure: error: no acceptable egrep could be found (fixed via EGREP_TRADITIONAL="grep -E"). Both confirmed via
BusyBox-based reproduction and/or direct script analysis; if a further
"no acceptable X could be found" error turns up for some other tool,
that's a different code path needing the same kind of targeted fix,
not a sign these two didn't work.
Makefile: cob_interp_db/cob_interp_full now build their own dependencies
sqlite/tcl/tk/raylib are real file targets (keyed on the
actual library file each one produces) instead of always-rerun
.PHONY targets, and cob_interp_db/cob_interp_full list them as
real prerequisites. One command each -- make cob_interp_db or
make cob_interp_full -- now builds everything needed the first time
and skips anything already built on later runs.
Two bugs were found and fixed while getting this right: SQLite's
compiled .a initially had one shared path across platforms, so
switching from a Linux build to a Windows cross-compile without
make vendor-clean silently linked a stale Linux object into a
Windows binary (fixed by giving it an OS-specific name); and the
"builds its own dependencies" claim was initially true only by
accident, since the prerequisite lists were missing entirely (fixed
by actually adding them, then reverified from a genuinely clean tree).
Verified this session
- All four build targets compile clean:
cob_interp,
cob_interp_window,cob_interp_full, andcob_interp_db's full
dependency chain (make sqlite tcl tk cob_interp_db). - Ran
window_button/window_slider/window_textboxagainst
cob_interp_windowon a live headless X11 display (Xvfb) and
confirmed via actual screenshots -- both before and after the
slider-caption fix -- that the button, slider, and an always-
editable text box with a blinking cursor render correctly alongside
the existingwindow_label()text. - Confirmed the
.strawberrycache correctly round-trips the three
new widget expression kinds: wrote a cache, deleted nothing, reran
against the same script, and got byte-identical output both times. - Reran the full existing
tools/stress/run_stress.shsuite
(arithmetic/SQLite/window cycles) after all of the above changes --
still all passing, no regressions. - Reproduced the
popcorn_compmagic-desync CI failure locally
end-to-end (gcc -> cob/popcorn_comp-> write cache -> recompile
natively -> diff outputs against the interpreter's own output),
confirmed the exact error message and root cause, applied the fix,
and reran the identical sequence to confirm it now passes -- using
--cc gccin place ofzig cconly because this environment has no
network access to ziglang.org; the real CI runner already has Zig
set up viamlugg/setup-zig@v2and should use the default path. - Confirmed, by inspection of both files' enum definitions, that
popcorn_comp.c'sExprKind/StmtKindenums are byte-identical to
cob_interp.c's for every kindpopcorn_compactually implements,
so the magic-constant fix alone was sufficient -- no encoding
divergence to also account for. - Validated
.github/workflows/build.yml's YAML syntax after adding
the new widget-cache smoke test, and ran its exact shell commands
locally againstcob_interp_windowbefore committing them. test.cob(this repo's manual GUI test script) runs clean
end-to-end, 20 one-second ticks then a gracefulwindow_close();
also confirmed a since-fixed version of this script that usedif
statements does not parse -- Cob's grammar has noif/elseat
all, onlywhile/set/pop/shuckand the builtins.
Not verified
- Nothing in this release has been run on an actual Windows machine by
anyone other than the person who reported the twoconfigure
failures above (both now fixed and cross-compile-verified) and who
has since separately confirmed the widget keywords work for them on
real Windows hardware. Beyond that one confirmation, the raylib/
raygui widget path has otherwise only been exercised headless via
Xvfb on Linux in this environment. _cobwindow(raylib/raygui) has not been exercised on a real
desktop compositor beyond the one Windows confirmation above -- no
Wayland/XWayland session, no verification of multiple simultaneous
window_textbox()es' keyboard-focus behavior (raygui's edit mode
isn't focus-aware across widgets; likely fine for one box at a time,
unverified with more than one open together).tcl_eval()/tk_eval()(incob_interp_db) needTCL_LIBRARY
pointed atvendor/TCL/libraryforTcl_Init()to succeed cleanly
--makeprints the line to set it, but it isn't set automatically.- If w64devkit's
shhas the same broken-PATH problem for some other
tool lookup inside Tcl/Tk'sconfigurebeyond the two already
fixed, that would need the same kind of targeted fix and hasn't been
ruled out. - GitHub Actions Zig download/bundle on genuine Windows/macOS runners:
still outstanding from prior releases. - The
cpirepo'sdownload.htmlfix has been verified locally, not
against the live deployment -- needs to actually be pushed to
Cob-Software-Foundation/cpi. ci.yml's scheduled/Docker jobs, andbuild.yml's full matrix
(Windows/macOS cross-builds, the Zig-bundling release step), haven't
executed inside a real GitHub Actions runner with this session's
changes yet -- the one real CI run available (the log that surfaced
thepopcorn_compbug) only got as far as the job that failed;
later jobs in that same workflow run haven't been confirmed since.
