Skip to content

Cob v0.0.5

Latest

Choose a tag to compare

@github-actions github-actions released this 15 Sep 21:05
· 7 commits to main since this release

Cob Language v0.0.5

Cob v0.0.5 -- Codename C!

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 plain make builds by default.
    Both build with a plain Makefile, no configure/autoconf step for
    either, so a clean make from 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_db builds 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_interp remains 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 sequential INSERTs + 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-gc

with 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, and cob_interp_db's full
    dependency chain (make sqlite tcl tk cob_interp_db).
  • Ran window_button/window_slider/window_textbox against
    cob_interp_window on 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 existing window_label() text.
  • Confirmed the .strawberry cache 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.sh suite
    (arithmetic/SQLite/window cycles) after all of the above changes --
    still all passing, no regressions.
  • Reproduced the popcorn_comp magic-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 gcc in place of zig cc only because this environment has no
    network access to ziglang.org; the real CI runner already has Zig
    set up via mlugg/setup-zig@v2 and should use the default path.
  • Confirmed, by inspection of both files' enum definitions, that
    popcorn_comp.c's ExprKind/StmtKind enums are byte-identical to
    cob_interp.c's for every kind popcorn_comp actually 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 against cob_interp_window before committing them.
  • test.cob (this repo's manual GUI test script) runs clean
    end-to-end, 20 one-second ticks then a graceful window_close();
    also confirmed a since-fixed version of this script that used if
    statements does not parse -- Cob's grammar has no if/else at
    all, only while/set/pop/shuck and 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 two configure
    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() (in cob_interp_db) need TCL_LIBRARY
    pointed at vendor/TCL/library for Tcl_Init() to succeed cleanly
    -- make prints the line to set it, but it isn't set automatically.
  • If w64devkit's sh has the same broken-PATH problem for some other
    tool lookup inside Tcl/Tk's configure beyond 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 cpi repo's download.html fix 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, and build.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
    the popcorn_comp bug) only got as far as the job that failed;
    later jobs in that same workflow run haven't been confirmed since.