Release Notes
1.33.0
Language and stdlib
- Regex match results now carry position information. The dicts returned
by
re.match/re.matchAll,pcre.match/pcre.matchAll, and thematch/matchAllmethods on both compiledPatternclasses gain three fields:span([start, end]of the whole match),spans(one[start, end]per capture group, aligned withgroups,nullfor a group that did not participate), andnamedSpans(the same for named groups). Offsets are character positions, end-exclusive, sotext.substring(span[0], span[1])is exactly the matched text and multibyte characters count as one position. Existing fields keep their values, but match dicts now iterate and print in a fixed field order (text, span, groups, spans, named, namedSpans, with named entries in pattern order) where they previously rendered in sorted-key order.
1.32.2
Tooling
- The language server now resolves document and workspace URIs sent by a
Windows-hosted editor for WSL files (
\\wsl.localhost\<distro>\...and legacy\\wsl$\...forms, plusC:\drive paths). Project imports no longer produce spurious "cannot resolve import" diagnostics in that setup, and hover, completion, and go-to-definition see the real files. - The VS Code extension translates WSL UNC paths when VS Code runs on the
Windows host: language-server URIs convert both ways (returned locations
open correctly), and Run File and the test runner pass in-WSL paths to
the toolchain. Executable paths using the legacy
\\wsl$\form are now recognized.
1.32.1
Tooling
- The language server now resolves exports from imported project modules.
With
import app.foo, go-to-definition and hover onfoo.barjump to and describebarin the module's source file,foo.completion lists the module's members with their signatures, and signature help covers calls to imported functions. Aliased imports (import app.foo as f) and selective imports (from app.foo import bar) resolve the same way, including go-to-definition on a bare from-imported name; go-to-definition on the module alias itself opens the module file. Native and stdlib modules keep their catalog documentation.
1.32.0
Changed
geblang testnow executes on the bytecode VM by default, the same backend release builds ship, so a test that passes only on the tree-walking evaluator no longer hides a VM-path regression. A test file the VM cannot run fails loudly, naming the file, instead of silently falling back.--runtime=evaluator(alias--disable-vm) runs the whole suite on the evaluator. A file covering a documented, temporarily-accepted divergence may carry a# @vm-divergence: <key>comment plus a matchingKNOWN_DIVERGENCES.mdrow to run on the evaluator; the runner cross-validates the two and reports how many files ran that way.instanceofis now module-exact: the right-hand side resolves to the specific class or interface declaration named in scope, and an instance matches when its actual class is that declaration or a subtype of it. Two same-named classes from different modules no longer match each other.typeof(x) == SomeClasscomparisons are unchanged (still by name). The types chapter documents the distinction.- Deferred calls now evaluate their callee, receiver, and arguments when the
deferstatement executes, on both backends, as the documentation always stated. A variable mutated after the defer statement no longer changes what the deferred call sees; a defer inside a loop captures each iteration's values. - Passing arguments to a parent constructor that does not exist is now an error
on both backends:
parent(args)where the base class declares no constructor raises a catchableRuntimeError. Previously one runtime silently ignored the arguments. Zero-argumentparent()to such a base is still a no-op, and a base with a matching constructor is unchanged.
Added
- Methods are first-class values:
let f = SomeClass.staticMethodandlet m = instance.methodproduce callable values that can be stored, passed, returned, deferred, and used as callbacks (xs.sortBy(obj.key)), locally and across modules. A bound method keeps its receiver by reference. defer f(...xs)accepts a list spread; the list reference freezes at the defer statement and its elements are expanded when the call fires.- Modules can export overloaded functions:
mod.over(5)andmod.over("x")select by argument types across the module boundary, in every form - qualified, from-import, as a stored value, with named arguments, deferred, and as a callback. Previously a module declaring two overloads of an exported function failed to compile on the standard runtime. - An overloaded
asyncfunction called through a stored value or an imported overload set returns aTasklike a direct call does; in a mixed set the selected overload decides (async yields aTask, sync returns its value). Previously the body ran synchronously and returned the plain result.
Fixes
- An error thrown inside a callback passed to a native higher-order function
(
sortBy,map,filter,reduce, and thecollectionsequivalents) and caught by a surrounding try/catch no longer corrupts the VM: iteration stops at the throwing element and execution continues cleanly after the catch, matching the evaluator. - An uncaught error thrown inside such a callback now keeps the full caller chain in the VM stack trace.
- A fault raised by a deferred call or a
del-fired destructor whose body lives in another module is reported once with a clean message on both backends, instead of nesting a fully rendered error inside the failure text. - The
==operator andassertEqualsnow share one canonical equality definition, pinned by guard tests on both backends: comparing native handle values (for example two file-serve descriptors) no longer panics, andassertEqualsagrees with==for complex numbers. web.http: a file response built byserveFilesurviveshttp.responseFrom(...), so builder-style wrapping preserves the streamed file.- A typed error thrown inside a callback or a decorated function, method, or
constructor is catchable by its class on the VM (
catch (ValueError e)now matches; previously the class was lost and the error rendered twice). - Closures capture outer variables referenced through string interpolation, pipelines, partial application, comprehensions, match-case patterns, and destructuring assignments; on the VM these previously read wrong values or failed with "local is undefined".
- Named arguments on a decorated function or method bind to the original declared parameter names on both backends; decorators are transparent to callers.
- A class inheriting
__next/__doneor__iterfrom a class in another module iterates withfor-inon the VM. deferaccepts module-qualified functions (defer mod.cleanup()) and nested-selector static methods on the VM.delworks on variables whose class is declared in another module.- Cross-module interface hierarchies (
Sub extends Basein another module) matchinstanceof Baseon the VM. - Running a test file's
test.runon the VM reports failures with the same clean class-prefixed message and frames asgeblang test. - A spread argument works in any position (
f(1, ...rest, 4)), combines with named arguments (f(c: 1, ...rest)), and multiple spreads expand in source order; previously arguments after a spread were silently dropped and named arguments before a spread were silently treated as positional. Native functions reject named arguments with spread with a clear error. - An uncaught error thrown from a callback declared in another module, a capturing closure, or a callable object passed to a native higher-order function now keeps the full caller chain in the stack trace, identical on both backends. The reverse direction is also complete: when a function from another module passes your callback to a higher-order method, that function's own frame appears in the trace and in caught stackTrace() frames.
- Named arguments work on functions imported from another module
(
mod.f(b: 2, a: 1)), including out-of-order names, mixes with positional and spread arguments, omitted defaults, from-import forms, and deferred calls; previously these were rejected at runtime. - Named arguments also work when constructing a class from another module
(
mod.Point(y: 2, x: 1)), including overloaded constructors, from-import and aliased forms, dict spreads, defaults, and deferred construction. - A destructor (
func ~ClassName()) on a class from another module fires atdelor program exit like a local class does; previously it could fire eagerly right after construction, anddelnever invoked it. Exit-sweep ordering (reverse creation), del-fires-once, and destructors that construct further destructible values behave identically on both backends. - An uncaught error thrown from a decorated function, method, or static method renders an identical stack trace on both backends: the wrapper frame is named after the decorated function and the top-level frame keeps its line. A class decorator's uncaught throw also keeps the top-level line.
- A DECORATED method used as a first-class value runs its decorator wrapper
with the receiver bound, on both backends:
let f = s.handle; f(5)behaves likes.handle(5), including in callbacks and defers. Previously the call failed with a wrong-arity error, and a decorated static method taken as a value silently skipped its decorator. profile.elapsedandmetrics.durationnow return the documented milliseconds as a float with sub-millisecond precision (previously raw nanoseconds as an integer). Thenow()token they take is unchanged.- Variables mutated inside a
trybody keep their values when an exception unwinds to the handler, andfinallyeffects always run; previously the standard runtime rolled the frame's locals back to their try-entry values. - A
selectstatement inside a closure captures its channels; previously a worker running such a closure could read an undefined value and block forever. - Calling a method on an instance whose class is exported and overridden in another module dispatches to the override; previously a statically resolved call could run the base method.
- A cross-module named-argument call may omit a defaulted parameter between
two supplied ones (
f(a: 1, c: 3)withbdefaulted). reflect.parametersandreflect.returnTypework on function values, including functions passed across module boundaries;reflect.fieldsreports declared field types and lossless decorator arguments (floats and lists included) for cross-module instances;reflect.interfacesreturns bare interface names on both backends.reflect.class(name)prefers the program's own classes over a same-named class exported by a standard-library module.test.mockpatches apply inside test methods on the standard runtime and restore automatically between methods.
Performance
redis: the reply reader buffers network chunks in linear time, so large bulk replies no longer copy quadratically (a 16 MB reply drops from roughly 885 ms to 41 ms), andgetBytesreturns the raw payload without a decode and re-encode round trip.
1.31.1
Debugging
- Step Over, Step Into, and Step Out in an asynchronous worker now resume other parked threads in continue mode. This prevents a selected worker from deadlocking when its next operation depends on another thread closing a channel, releasing a lock, or completing a task.
- Single-thread execution is supported: continue or step one selected thread
while the others stay paused (the adapter advertises single-thread execution
requests and honors the per-request
singleThreadflag). - Setting a variable while paused now persists in the Variables view across refreshes, including in outer stack frames.
Added
- A new
serveFile(path, opts)in theweb.httpmodule returns a response the server streams from disk with HTTP conditional-request semantics: HEAD, byte ranges, If-None-Match/ETag, and If-Modified-Since, with Content-Length set and without reading the whole file into memory. A missing file yields 404. The file response is an opaque value that request data cannot forge, and the server sets a default ETag so If-None-Match works without the caller supplying one. - A new
path.real(p)returns the canonical absolute path with symlinks resolved, for containment checks that must not be fooled by a symlink.
Fixes
- A runtime fault (for example division by zero) inside a function or method in
another module is now caught with its clean message, matching the evaluator,
instead of a rendered
uncaught ...block; an uncaught fault still renders the full cross-module trace exactly once. - An error thrown from a
withresource's__enteror__exitis now caught by its real class with the clean message (for examplecatch (ValueError e)), instead of a flattenedRuntimeError. Awithresource whose__enter/__exitis inherited from a class in another module now runs it (it was silently skipped). - When a
withbody and its__exitboth raise, the__exiterror now propagates and replaces the body's, consistently on both backends (Python__exit__semantics). Awithbody that returns is likewise overridden by an__exitthat raises. - The HTTP server now canonicalizes the request path (collapsing a leading
//and resolving./..segments) before it reaches handlers and routing, so a non-canonical path like//admincan no longer slip past a path-prefix check while still matching a trimmed route. --allow-ffi <path-or-glob>permissions now apply when running an exported module entry point withgeblang -m, in addition to permissions declared ingeblang.yaml. Top-level,run, and module help now document the repeatable FFI permission flag and its required library path or glob.- The
redisclient now parses replies at the byte level, so values are read without converting the whole reply buffer to text. Values that are valid UTF-8 are returned as strings as before; a value that is not valid UTF-8 raises a clear error naminggetBytes, and a newgetBytes(key)returns the raw bytes. This also fixes replies whose bytes span more than one socket read. - The
messagingbackends (sqs,sns,stomp) now raise a clear error when abytespayload is not valid UTF-8, telling the caller to encode it first (for example as base64), instead of failing with a generic decode error. Valid UTF-8 byte payloads are sent as text as before. llmimage analysis (analyzeImage) again accepts binary image bytes; the base64 encoding no longer round-trips the image through a UTF-8 string.- The file-backed session store now validates the session id read from the request cookie before using it in a file path. A malformed or tampered cookie is treated as no session instead of reaching the filesystem.
instanceofagainst a builtin type name that also names an imported module (such asbytesorstring) is now accepted in expression position (return, argument, assignment), not only insideif/whileconditions.assertEqualsnow treats the type value fromtypeof(x)as equal to its name string or to a class value of the same name, matching the==operator, soassertEquals("bytes", typeof(x))andassertEquals(SomeClass, typeof(inst))both pass.json.parseAsno longer fails with "class index out of range" when deserializing a class whose parent is in another module, including ingeblang buildbinaries.
1.31.0
Debugging
- The VS Code debugger now sets breakpoints inside concurrent worker bodies:
async.run/async.all/async.raceworkers, generator bodies, and network request handlers, in addition to the main script. Each running worker appears as its own thread in the Call Stack pane; hitting a breakpoint stops all threads, Continue resumes them, and a step advances only the selected thread.
Changed
bytes.toString()and thebytesvalue.toString()method now reject data that is not valid UTF-8 with a clear error, instead of returning a string that carried the raw bytes. Convert non-text bytes with.toHex()or.toBase64().
Performance
- String index (
s[i]),substring/slice, andlength()now cache rune offsets for strings longer than 256 bytes, making each access amortized O(1) and a character-scanning loop O(n) instead of O(n^2). Shorter strings are rescanned per access with a maximum scan of 256 bytes. The cache is concurrency-safe and memory-bounded, with no API change.
1.30.1
Changed
- Primitive method names now use their documented, case-sensitive spelling on
both backends. The bytecode VM previously accepted arbitrary casing, and
conversion helpers such as
toIntaccepted arbitrary casing on both backends.
Performance
- Bytecode primitive-method dispatch no longer normalizes the method name on every call. Canonical camel-case calls no longer allocate for normalization; a template-shaped dispatch benchmark improved from about 38-40 ns to 21-22 ns per call.
Fixes
reflect.method(instance, name)now returnsnullon the bytecode backend wheninstancebelongs to another module and the method does not exist, matching the evaluator instead of reportingunknown class.
1.30.0
Changed
- Module-level variables mutated by a function call across a module boundary
now persist on the bytecode backend, including a call that throws (the
assignments it made before the throw are kept) and a synchronous re-entrant
call (it sees the outer call's in-progress assignments). Module globals
remain unsuitable for concurrent or transactional state; use
store.Storeor another explicit synchronized handle for those cases. http.serve,http.listen, andnet.serveaccept anopts.shareHandlerflag: when true the handler is shared across requests instead of isolated per request, for frameworks that manage their own per-request isolation.- Native (built-in) functions accept named arguments, the same as user
functions: an argument may be passed by parameter name, in any order (for
example
math.pow(base: 2.0, exponent: 3.0)), identically on both backends. An unknown or duplicated parameter name is a runtime error.
Performance
- Cross-module function, method, constructor, and static-method calls are substantially faster on the bytecode backend.
Fixes
instanceof, an inherited__serialize, and overloaded-callback selection now work for a class that extends, or is declared in, another module; the bytecode backend previously resolved only same-module hierarchies.- An overloaded function used as a value (passed to a callback or stored in a
variable) now retains all overloads and selects by positional arity,
defaults, variadics, and runtime types, including module-qualified and
user-generic parameter types (for example
Box<Dog>versusBox<Animal>). Previously the bytecode backend could keep only the last overload and could not disambiguate generic parameters. - An async function returns a Task when it is passed as a value, used as a callback, or called across a module boundary, matching a direct async call. Previously the bytecode backend could run it synchronously and return the raw value.
- A
deferinside a function that throws across a module boundary now runs as the error propagates to the caller. - A function body may reference a top-level
constorletdeclared later in the same file. - A generator that mutates a module-level variable writes the change back when it is consumed.
- Concurrent calls to already-loaded modules run on isolated workers, dispatch of an already-loaded module is race-free while another module lazily loads, and method dispatch on a returned instance no longer retains its construction VM.
- Deserializing a class instance whose type defines methods no longer fails on the constructor's implicit receiver parameter.
- A handler passed directly to
http.serve,http.listen, ornet.serveis isolated per request on the bytecode backend: its captured state is deep-cloned for each request, matching the evaluator. Previously the bytecode backend shared the handler's captured state across concurrent requests. - A handler defined in a different module from its
http.serve,http.listen, ornet.servecall is now isolated per request on the bytecode backend too; it was still shared across requests. - Deep-cloning a value that contains a reference cycle (for example a dict that holds itself) no longer recurses without bound; this could happen during per-request handler isolation.
- The
messagingmodule (RabbitMQ and Kafka) runs on the bytecode backend; those calls previously failed there withunsupported native call.
1.29.2
Fixes
- Explicit type arguments on a generic function or method call are now
validated. A type argument that contradicts the actual arguments, such as
firstMatch<string>(listOfInts), is reported as an error rather than being silently discarded. The binding is also enforced at runtime, so a dynamically-typed argument that violates an explicit type argument throws, with the evaluator and bytecode VM in agreement.
1.29.1
Logging
- New
log.syslog(opts)destination sends structured log records to a syslog server or the local syslog daemon, framed as RFC 5424. It joins the existing logger family (stdout/stderr/file/toStream/custom), so the same level calls apply. Transport isudp(default),tcp, orlocal;udpandtcpwork on Linux, macOS/BSD, and Windows, whilelocal(the platform daemon socket) is Unix-only. The message body is the same JSON the other destinations emit, and facility, app name, and hostname are configurable. The logger connects when constructed (a bad address fails fast) and drops transient send failures rather than raising.
Fixes
sys.exit(code)now terminates the program cleanly with its code from any context, including inside an exportedmainand across module boundaries, on both runtimes. Previously it could surface as an uncaught error in those cases instead of exiting.
Documentation
- Logging now has its own reference chapter, split out of the observability chapter for discoverability.
1.29.0
Language
- Numeric literals now support scientific notation. Unsuffixed exponent
literals are exact
decimalvalues (1e3,1.5e-3,2E8); addffor IEEE 754 floats (1e308f). - Enums can now be backed by scalar
stringorintvalues:enum Status: string { Active = "active"; }. Backed variants expose a read-only.value,EnumName.from(value)returns the matching variant or throws, andEnumName.tryFrom(value)returns the variant ornull. Backing values must be unique literals, and variants cannot mix backing values with associated data.
Runtime
- Bytecode chunk format version bumped 75 to 76 so backed enum metadata round-trips through cached bytecode and built binaries.
- The experimental
geblang build --nativepath now rejects backed enums with a clear diagnostic instead of emitting incorrect Go; use the default bytecode build path for backed enums.
1.28.1
Language
- A type annotation on a
for-inloop variable is now checked and enforced, the same way a typed declaration is.for (int x in xs)is fine, but an unknown type name (for examplefor (var x in xs), wherevaris not a type) and an element-type mismatch (for (string x in [1, 2])) are reported bygeblang check,run, andtest. Omit the type to infer it from the iterable.
Tooling
geblang build --helpandgeblang test --helpnow list every flag, including--resourceand the--allow-ffi/--allow-onnx/--allow-process-control/--allow-browsercapability flags.- Editor autocomplete now shows the correct signatures for
io.captureStdoutandio.captureStderr: they take no arguments and return a capture stream you read withio.toString.
Documentation
- Corrected reference-manual examples that used syntax the language does not accept:
match block-body arms,
whenguards (the keyword isif), named enum fields, untyped lambda parameters, scientific-notation float literals, and a backtick string literal.
1.28.0
Standard library
- New
htmlmodule for parsing real-world HTML and querying it with CSS selectors.html.parse(source)returns the root node; nodes exposeselect/selectFirst(CSS3 selectors: type, class, id, attribute operators, descendant/child/sibling combinators, structural pseudo-classes, grouping),text,attr,attrs,tag,html(inner HTML),children, andparent. The parser is lenient, so malformed markup is repaired rather than rejected. See the HTML chapter in the standard-library reference.
Fixes
dir()now lists the methods of the native value types (n-dimensional arrays, data frames, series, distributions, complex numbers, generators, and html nodes) on the bytecode VM, matching the evaluator. Previously it returned an empty list for those types on the VM.
1.27.1
Standard library
- HTTP client responses now expose the final URL after redirects.
r.url()(and the index formr["url"]) returns the address the response was ultimately served from, so a request to a redirecting endpoint reports where it landed. It is an empty string for responses not produced by a request (for examplehttp.response(...)), and is carried throughwithStatus/withHeader/withBodyandtoDict.
1.27.0
Tooling
geblang run <script.gb> [args...]is now an explicit alias for the default script-running form (geblang <script.gb> [args...]). Both invoke the bytecode VM with evaluator fallback and accept the same flags (geblang run --vm-strict app.gb). The verb stays optional; the next token afterrunis always a script path, never re-read as another subcommand.- Cross-platform builds:
geblang build --runtime <path>embeds the bundle into a runtime compiled for another platform, and the newscripts/cross-build.shhelper drives it end to end (--target linux/amd64,darwin/arm64,windows/amd64, ...) using the Go toolchain, so any host can build for any target. - Geblang now builds and runs on Windows. FFI, local ONNX inference, and
advisory file locking report a clear unsupported error there; the
hnswvector-store backend uses an exact index; and interactive console widgets and the REPL line editor fall back to plain line input. See the bundling chapter.
Standard library
- The
mathmodule gains special functions and exact combinatorics. Special functions (real-valued):gamma,lgamma,beta,lbeta,erf,erfc,erfinv, and the Bessel functionsj0/j1/jn/y0/y1/yn. Combinatorics return exact integers at arbitrary precision:factorial,comb,perm,gcd,lcm, and the log binomial coefficientlcomb.factorial,perm, andcombrejectnabove 100000 to bound memory and CPU. - New
statsmodule: probability distributions as objects. A constructor (stats.normal(0, 1),stats.binomial(20, 0.5), ...) returns a distribution exposingpdf,cdf,ppf,mean,variance,std, andsample. Twelve distributions are included: normal, uniform, exponential, gamma, beta, chi-squared, Student's t, F, log-normal, Weibull, binomial, and Poisson.sample(n, {"seed": k})draws reproducibly into an ndarray. - The
statsmodule also gains hypothesis tests and confidence intervals, each returning a dict. Tests:tTestOneSample,tTestIndependent(pooled or Welch),tTestPaired,chiSquareTest,chiSquareIndependence,mannWhitneyU, andksTest. Intervals:confidenceIntervalMean,confidenceIntervalProportion, andconfidenceIntervalDiffMeans. - The
statsmodule also gains regression:linregress(simple linear least-squares, returning slope, intercept, correlation, r-squared, p-value, and slope standard error), pluspolyfit(least-squares polynomial fit) andpolyval(polynomial evaluation). - The
statsmodule also gains descriptive extensions:skewnessandkurtosis(population, with excess kurtosis),covariance(sample), andcorrcoef(Pearson correlation). - New
physicsmodule: twelve physical constants as zero-argument functions (c,G,planck,hbar,avogadro,boltzmann,gasConstant,elementaryCharge,electronMass,protonMass,stefanBoltzmann,gravity) andconvert(value, fromUnit, toUnit)covering length, mass, time (scale), and temperature (affine: C, F, K). - New
complexmodule: complex number value with two constructors (complex.of,complex.fromPolar), a full method set (re,im,abs,arg,conj,neg,exp,sqrt, and binaryadd/sub/mul/div/pow/equals), and operator overloads for+,-,*,/,**, unary-, and==. Plain numbers are promoted automatically on either side of a binary operation. - New
geomodule:haversineDistance,bearing,midpoint, anddestinationfor geodetic calculations on the sphere. Coordinates are in decimal degrees using a mean Earth radius of 6371 km. Distance functions accept an optional unit argument ("km"default,"m","mi","nmi");midpointanddestinationreturn{"lat", "lon"}dicts. - New
async.tasksmodule: high-level task combinators over the async core for callers who would rather hand off a function and data than manage tasks, channels, and semaphores directly.map/forEachrun a function over a collection concurrently (results in input order, optionalconcurrencycap, fail-fast);retryadds exponential-backoff retry;settleawaits every task without failing fast;anyreturns the first task to succeed; andparallelruns a list or dict of callables at once.
Fixes
- A closure created in one module and passed to
async.runfrom another module is now accepted on the bytecode VM, matching the evaluator. The VM previously rejected such a cross-module callable with "async.run expects a function".
Platform
- macOS and BSD support: the toolchain now builds and runs on macOS and the
BSDs. Terminal handling uses per-platform ioctl constants, and
sys.osVersionplusprocess.list/process.infoare implemented for those platforms.
1.26.0
Language
- Partial application: a call with one or more
_placeholder arguments now returns a new callable with those positions left open (add(_, 10),wrap(_, "-", _),open(mode: _)). Non-hole arguments are captured once at creation; the target resolves at application. Works across functions, methods, constructors, native builtins, module functions, and callable objects. (Partials over multiple same-arity overloads are resolved at application by the interpreter; compiled builds reject them statically - use a typed wrapper.)
1.25.0
Language
- Mixing
decimalandfloatin arithmetic - the deliberate precision wall that keeps adecimal's exactness from silently leaking into afloat- is now reported bygeblang checkwhen both operand types are statically known, not only at runtime. The error names both fixes and their tradeoff: cast withas float(fast, drops the decimal's exactness) oras decimal(keeps an exact type, adopts the float's binary imprecision). reflect.fields(class)now includes adockey on each field entry: the docblock written immediately before the field (a##line or a/** */block), ornullwhen there is none. This matches how class, function, and method docblocks were already surfaced.
Standard library
- New experimental
browsermodule: drive a headless Chrome/Chromium over the DevTools Protocol for functional testing and scripted control.browser.launchopens a browser; pages cangoto,waitFor,click,type/fill,evaluateJavaScript, readtext/content/title,screenshot/pdf, managecookies, list tabs, and intercept requests withroute(continue, block, or fulfill a mock response). It speaks the protocol directly with no external driver, is gated behind--allow-browser, and always terminates the browser on close. Needs Chrome installed (located via$GEBLANG_CHROMEor anexecutableoption).
Tooling
geblang fmtis substantially more robust. It now re-parses its own output and refuses to write anything that is not identical to the input at the AST level, so formatting can never silently change a program's meaning or break its syntax. By default it follows a width-aware layout standard (a 100-column target): explicit grouping parentheses, intentional blank lines, comments (including trailing same-line comments), and any construct the author split across lines (operator and method chains, list/dict/set literals, call argument lists) are kept, and a collection or argument list is wrapped onto one item per line when a single line would exceed the width.geblang fmtgained two formatting modes.--cleanproduces the minimal canonical form (drops redundant parentheses, flattens multi-line chains, concatenations, and collections onto one line);--strip-commentsremoves all comments. The flags are independent and may be combined.
1.24.1
Bundling
- Built binaries (
geblang build) can now carry privileged capabilities baked in at build time, so they run with no launch flags. Declare them ingeblang.yamlunderpermissions:-ffi(existing),onnx: true,processControl: true- and/or passgeblang build --allow-ffi <path-or-glob>/--allow-onnx/--allow-process-control; build flags add to what the manifest declares. - The
onnxandprocessControlpermissions also enable those capabilities forgeblang run/geblang testin the project, matching howpermissions.ffialready behaved. A binary (or run) without them stays locked down: a gated call throwsPermissionError.
Tooling
- The
geblangbinary now embeds the source standard library, so a standalone binary - copied anywhere, with noGEBLANG_STDLIBand no stdlib directory beside it - resolves source stdlib modules (llm,rag,vectorstore, ...) with no setup. An on-disk stdlib (a repo checkout orGEBLANG_STDLIB) still takes precedence, so developing against a working copy is unchanged.
Runtime
- A running program now starts a background memory sweeper that periodically
returns freed heap pages to the OS, so a long-running server's RSS no longer
stays pinned at its allocation high-water mark after a burst (e.g. buffering
large uploads). It is on by default and tuned with
GEBLANG_GC(offto disable),GEBLANG_GC_INTERVAL(default30s), andGEBLANG_GC_THRESHOLD_MB(default64).profile.gc()still forces a collection on demand. - Streaming HTTP response handles (
http.requestStream,http.fetchStream) are now released as soon as the stream is fully read or closed, and any left open are swept at shutdown, so a long-running server that streams many responses no longer accumulates handles for the life of the program.
1.24.0
Standard library
vecmathgainednormalize(L2-normalize a vector, or each vector in a list) andsemanticSearch(queries, corpus, k, metric), the multi-query form oftopKthat returns the top-k corpus matches per query as{index, score}.- The
llmclient gainedmodels()(list the models available to the account) andembedBatch(texts, opts)(embed many strings in one call, returning{vectors, model, usage}), across the OpenAI, Anthropic, and Bedrock providers (Anthropic has no embeddings API, soembedBatchthrows there). - The
llmchatmethod now supports tool / function calling. Passopts.tools(provider-neutral{name, description, parameters}schemas); when the model calls a tool the result carriestoolCalls({id, name, arguments}), and you continue the conversation with a{role: "tool", toolCallId, content}message. One portable shape across OpenAI, Anthropic, and Bedrock Claude models. - New
http.requestStream(options)performs a request and returns aStreamResponsewhoseread()yields the body line-by-line as it arrives (nullat end), the client-side analog of server-sent events.status(),headers(),done(), andclose()round it out. - The
llmclient gainedchatStream(messages, opts, callback): streaming chat that invokescallbackwith each content delta and returns the assembled result. Supported for OpenAI and Anthropic (server-sent events); Bedrock uses a binary event-stream protocol and throws there. - New experimental local-model modules.
transformers.tokenizeruns WordPiece tokenization from a HuggingFacetokenizer.json(BERT-family encoders), returning paddedinput_ids/attention_mask/token_type_ids.onnx.session(modelPath)loads an ONNX model for cgo-free local inference via ONNX Runtime;Session.runmaps int64 input ndarrays to float64 output ndarrays. Both are gated behind the new--allow-onnxlaunch flag, and ONNX Runtime is located viaopts.libPath/$GEBLANG_ONNXRUNTIME.transformers.poolreduces a[batch, seq, dim]hidden state + mask to[batch, dim]sentence embeddings (mean / cls / max, L2-normalized), so tokenize ->onnx.session-> pool gives fully local, offline sentence embeddings that feed straight intovecmath/vectorstore/rag.rag.LocalEmbedder(modelDir)wraps that pipeline as a drop-inEmbedder, for a fully on-device index/retrieve loop with no API calls. - HTTP client failures now carry a precise, catchable class: a timeout raises
TimeoutErrorand a TLS/certificate failure raisesTlsError, both newIOErrorsubclasses (socatch (IOError e)still catches them) - so callers can retry on a timeout but fail fast on a bad certificate. The same classification applies engine-wide to any timeout/TLS failure. A mid-stream read failure onhttp.requestStreamnow surfaces as a catchable error rather than looking like a clean end of stream.
1.23.3
Standard library
- New
search/searchPatternmethods on lists, dicts, and strings return every matching locator (not just the first): list indices, dict keys (matched on their values), or string positions.search(value)matches by equality,search(callable)by a predicate, andsearchPattern(regex)by regular expression. Each returns an empty list when nothing matches.
1.23.2
Standard library
- Lists gained
fill(value, count): it appendscountcopies ofvaluein place and returns the list, so it chains.countmust be>= 0; a typed list rejects a value of the wrong element type.
1.23.1
Language
- A cast to a primitive followed by
<now parses as a comparison:x as int < yis(x as int) < y. Primitive types never take type arguments, so<after one is the less-than operator. Generic casts such asx as list<int>are unchanged.
Standard library
cryptgained real X.509 chain support:crypt.verifyCertChainverifies a certificate chain's signatures up to a trusted root (throwing on failure),crypt.parseCertnow also returns thepublicKeyPEM and rawextensions,crypt.asn1Decodedecodes DER into a nested structure, andcrypt.parseAndroidAttestationreads the Android Key Attestation extension.- SQLite connections accept tuning options on the
db.Connection({...})options-dict, applied as per-connection pragmas at connect time:wal,synchronous,foreignKeys,busyTimeoutMs,cacheSizeKb,mmapSizeMb, andtempStoreMemory. Each is explicit (wal: truesets only WAL). A newconnection.optimize()runsPRAGMA optimizefor query-planner maintenance.
Type checking
- Division
/is true division and its result type isdecimal(orfloat), even when the operands divide evenly. Assigning a division result to anint(int n = a / b) is now a compile-time error on every path (check/test/run/build), reported ascannot assign decimal to int. Use//for an integer (floor) result, or(a / b) as intto truncate. This closes a case where the bytecode VM crashed at runtime while the evaluator produced a decimal.
Modules and packages
- Path dependencies in
geblang.yamlnow accept absolute paths, a leading~for the home directory, and$VAR/${VAR}environment references. Relative paths are still resolved against the manifest. - A scheme-less
gitdependency value (for examplegithub.com/acme/httplib) is now resolved ashttps://.... Explicit schemes and the scp-likegit@host:pathform are left untouched.
1.23.0
Standard library
iofile handles gained random access:io.seek(handle, offset, whence)(whence"start"/"current"/"end"),io.tell,io.truncate, andio.atEnd. Open modes now includer+,w+,a+, and the exclusive-createx/x+.- New filesystem helpers:
io.copy,io.copyTree,io.move(rename with a cross-device copy fallback),io.lstat,io.scanDir,io.touch, andio.writeTextAtomic. Theio.stat/io.lstatdictionaries gainedisFileandisSymlink. - New
filemodule: aFileobject (file.open(path, mode)) wraps a handle with method-style read / write / seek,with-block auto-close, and line iteration.streams.IOStreamgainedseek/tell/truncate/atEnd.
Fixes
reflect.classes()called from inside an imported module now returns the entry module's classes on the bytecode VM, matching the evaluator (the VM previously omitted them), so whole-program class scans behave identically on both backends.- A class declared in the entry file now resolves the entry file's top-level
bindings (imports and module-level
let/const) when its methods are dispatched from another module on the bytecode VM, matching the evaluator.
Native compilation (experimental)
- Module-level
let/constread by a function now compiles natively, in both imported modules and the entry module: the binding lowers to a package-level value so the function can see it. A module's functions can also call each other. These previously reported as unsupported or failed to build with an undefined-symbol error. list.join(sep)compiles natively for lists of any element type.- Comparing a list, dict, set, or bytes value to
nullcompiles natively (a nil check), matching the interpreter; structural==between two collections remains deferred. - Unwrapping a nullable dict with
as(e.g.value as dict<string, any>after a null check) compiles natively. io.mkdircompiles natively.
1.22.0
Language
-
Enums expose two operations on the enum type itself.
EnumName.values()returns a list of the simple (nullary) variants in declaration order, andEnumName.fromName(s)resolves a variant by its exact, case-sensitive name, returning the variant ornullwhen none matches. Tagged variants are excluded from both, since a bare name cannot construct a variant that carries fields. Identical on both backends. -
Numeric-check predicates let a value be tested before converting, instead of catching a failed cast. On strings:
isInt(),isDecimal(), andisNumeric()report whether the string parses as an integer, a decimal, or either, using the same rules astoInt()/toDecimal()(sos.isInt()is true exactly whens.toInt()would succeed, bases and underscore separators included). On numbers:float.isInt()anddecimal.isInt()report whether the value is a whole number (NaNand infinity are not). Identical on both backends. -
zrange(start, end[, step])is the exclusive, Python-style counterpart to the inclusiverangebuiltin: it omits the end value (zrange(0, 5)is[0, 1, 2, 3, 4]) and adds a one-arg formzrange(n)that ranges from 0. It returns an eagerlist<int>;collections.rangeremains the lazy exclusive form. Identical on both backends.
Tooling
geblang checknow verifies that amatchover an enum subject handles every variant. A match that omits a variant and has nodefaultis reported aswarning[match-nonexhaustive], naming the missing variants. It is advisory (the code still runs and throwsMatchErroronly if an unhandled value reaches the match); a variant handled only by a guarded case counts as missing, since the guard may be false. Surfaced in the editor through the language server.
Fixes
optionandresult: the absent/error path no longer binds the generic type to null.none(),err(), andofNullable(null)now keep their declared type, so a typedunwrapOrfallback works (divide(5, 0).unwrapOr(-1)returns-1instead of throwing),orNull()returns?T, and the documented examples run.- A private in-memory SQLite database (
:memory:) now pins its connection pool to a single connection. Each:memory:connection is a separate database, so concurrent access previously could open a second, empty one (no such table); pinning makes concurrent reads and writes share one database. Usefile::memory:?cache=sharedor a file path for a larger pool. reflect.fields,reflect.methods,reflect.classNameand the other reflect introspection calls now accept a nestedreflect.class(value)(orreflect.function) argument identically on both backends. Previously the bytecode backend rejected the nested form with a non-literal argument, requiring the class to be bound to a variable first.cli.choose(label, options, default)accepts a literal default index again; a small integer literal was wrongly rejected as "default index must be int".sys.run(command, args)accepts its documentedlist<string>argument form again (it was rejected with "arguments must be strings"); the trailing-varargs form keeps working.
Standard library
dataframe.filterFn(row -> bool)filters a frame with a per-row Geblang predicate, each row passed as a dict of column name to value, complementing the faster columnwise expression filter. Athrowinside the predicate propagates and is catchable, and predicates run safely from concurrent async tasks. Identical on both backends.json.parseAs(andyaml/toml/xmlparseAs) now reconstructs nested class fields recursively: a field whose declared type is another class, or alist/dictof one, is itself deserialized into an instance, so a whole object tree comes back fully typed (including across modules).anyand primitive fields keep their raw parsed value, a class with__deserializestill controls its own nesting, and a value whose shape does not match the declared field is left as-is. Identical on both backends.cli.multiChoose(label, options)selects several options at once: an arrow-key checkbox UI on a terminal (up/down or j/k move, space toggles,atoggles all, enter confirms, q/ctrl-c cancels) with a numbered comma-separated fallback when stdin is not a terminal. An optional third argument pre-checks options by index.io.withStdin(input, body)runsbodywith the console readers consuminginput(line input or raw key sequences) instead of stdin, then restores it. The interactive UI is suppressed while it runs, so it makes prompts (includingcli.multiChoose) testable. Returns the body's value.
Native compilation (experimental)
geblang build --nativenow lowers directascasts from a string tointorfloat, and from a non-bool tobool, matching the interpreter (these previously reported as unsupported). The new numeric-check predicates compile natively as well.- The enum static surface (
values()/fromName()) compiles natively, as do nullable enums (?Color) and equality on nullable value-types (?int == 5), which previously reported as unsupported or failed to build. as decimalcasts (from an int, float, string, or dynamic value) compile natively, matching the interpreter.
1.21.0
Language
- Enums can declare instance methods, callable on any variant with
thisbound to the receiving variant and read viamatch this. There is one method body per name; per-variant behaviour is expressed inside it, not as an override table. Methods sit beside the existing associated-value and backed-scalar access (which still resolve first), and a method named like a built-in variant accessor is a compile error. Enums stay immutable. - Enums can implement interfaces with
implements. Conformance is checked (a missing method is an error agreeing across check, test, run, and build), interface defaults apply where an enum leaves a method unimplemented, and an enum value flows into an interface-typed slot with dispatch landing on the enum's method. The bareenum Name { A, B }form is unchanged. Identical on both backends.
Native compilation (experimental)
geblang build --nativeis a preview that compiles a program to a standalone native binary by transpiling it to Go, for a speedup on compute-heavy code. It supports a growing subset of the language and standard library and fails the build with a clear diagnostic on anything unsupported, never producing a binary that behaves differently from the interpreter; use plaingeblang buildfor the full language. The supported set is unstable and will change between releases. See the bundling chapter for what is and is not supported.
Process and system management
sys.osVersion()returns the OS kernel and release string, complementingsys.platform()andsys.arch().- The
processmodule reports the running process's own identity and credentials:pid(),ppid(),uid(),gid(),euid(),egid(), andgroups(). - It can inspect other running processes with
process.list(),process.info(pid), andprocess.exists(pid). - Privileged control of arbitrary processes (
process.setuid(),process.setgid(),process.kill(pid),process.signal(pid, sig)) is gated behind the opt-in--allow-process-controllaunch flag and raises aPermissionErrorwhen the flag is absent. Handle methods acting on a process the script launched itself stay ungated. - Identity and credential functions are unix-oriented and process enumeration is Linux-first; a function unsupported on the host platform raises a clear, catchable error rather than returning an empty or wrong result.
Fixes
- Nullable typed-collection element tags accept
null: anullmay be written into alist<?int>or adict<string, ?int>on every write surface (push,insert, index assignment,set,add), while a non-nullablelist<int>still rejects it. Identical on both backends. - The bytecode VM's unary-minus type error now matches the evaluator's wording.
x as anyis accepted as a widening no-op on the bytecode VM, matching the evaluator; it previously raised a spurious "cannot cast" error.geblang buildreports a missing or incompatible entrymainat build time and writes no binary, instead of failing only when the built binary runs. The entry module mustexport func main()orexport func main(list<string> args), optionally returning: int.
1.20.0
Language
-
Explicit constructor type arguments are enforced at runtime on both runtimes:
Box<string>(42)throwsRuntimeError(previously the bytecode runtime accepted it silently while the evaluator threw, and the recorded bindings could contradict the stored value). Subtype arguments still pass and calls without explicit type arguments stay inference-open. Enforcement applies same-module and across module boundaries. This is a behavior change for code that relied on the silent acceptance. -
Method parameters typed with a class-level type parameter are enforced against the instance's reified bindings on both runtimes:
put(42)on aBox<string>throwsBox.put expects T for parameter 'value', got int. Inherited methods enforce extends-clause bindings (IntBox extends Box<int>); a method's own type parameters still bind per call. This is a behavior change for code that relied on uncheckedTparameters. -
instanceofanswers parameterized checks against user generic classes from the instance's reified bindings, with the same invariant model aslist<int>:b instanceof Box<string>is true when the class matches andTis bound tostring(previously always false). Bare-name checks are unchanged; type-parameter names in the argument list resolve in generic frames. -
A declaration annotation over a direct constructor call of the same class validates like explicit type arguments:
Box<int> x = Box("text")is rejected (statically when the contradiction is visible, at runtime otherwise). The annotation still wins over inference for the recorded bindings;letwithout an annotation stays inference-open. This is a behavior change. -
Explicit type arguments resolve constructor overloads: with
Box(T value)andBox(int value)declared,Box<string>(42)selectsBox(int value)instead of reporting an ambiguous overload. Bindings only break ties; single-candidate mismatches keep the precise per-parameter error. -
Calling a function declared to return
anyin a statically typed context (typed parameter, generic constructor argument, typed declaration) no longer fails compilation on the bytecode runtime; the value is validated at runtime on both runtimes, matching the evaluator's long-standing behavior. -
The element-tag write barrier on typed collections is now hierarchy-aware and complete. Subclass and implementer writes pass (
list<Animal>.push(Dog())previously threw - name-equality only), and the barrier covers every mutation surface: index assignment (xs[0] = v),list.set, dict key AND value writes (d[k] = v,dict.set), andset.addwere previously unchecked. Covariant generic passing is now sound: alist<Dog>received aslist<Animal>rejects aCatwrite against its real tag. This is a behavior change in both directions. -
Generic constraints accept primitive and class leaves alongside interfaces, combined with
|and&:<T implements string|int>now works (previously primitives never satisfied a constraint), and a class leaf is satisfied by the class or any subclass. A bare form drops the keyword:<T string|int>on functions and classes means the same thing. Constraint-violation messages are now identical on both runtimes (type bool does not satisfy constraint string|int for type parameter T).
Standard library
ndarrayanddataframegain operator support:+ - * / **and unary-work elementwise on arrays and numeric series (scalars on either side, broadcasting included),< > <= >=on arrays return 0/1 masks (a.where(a > 2.0)), and dataframe expressions build with operators (df.col("age") > 30,df.col("price") * df.col("qty")).==and!=keep their language-wide meaning everywhere;eq()/ne()cover elementwise equality. Series operator results are ndarrays.dataframe.pivot({"index", "columns", "values", "agg"})spreads a column's values into per-value columns with aggregation (defaultsum; any groupBy aggregator exceptcollect).
Static analysis
geblang check(and the compile path ofrun,test, andbuild) reportserror[semantic]when a constructor call's explicit type arguments contradict a statically-known argument type (Box<string>(42)). Covariant passing stays clean;any-typed, named, and spread arguments defer to the runtime check.- An unknown type name in an
instanceoftarget is now an error instead of silently evaluating to false (x instanceof Nope). Cross-module trailing-name matching and generic type parameters stay recognized; the check stands down when an import's surface cannot be resolved. - An unknown type name in a generic constraint clause
(
<T implements Nope>) is flagged at the declaration, like every other annotation position, instead of only failing when the function is called.
Older releases (1.0.0 through 1.19.0) are archived: see the release notes archive.