Skip to content

Releases: dwgebler/geblang

Geblang 1.33.0

Choose a tag to compare

@dwgebler dwgebler released this 23 Jul 20:12
  • Regex match results now carry position information. The dicts returned
    by re.match / re.matchAll, pcre.match / pcre.matchAll, and the
    match / matchAll methods on both compiled Pattern classes gain
    three fields: span ([start, end] of the whole match), spans
    (one [start, end] per capture group, aligned with groups, null
    for a group that did not participate), and namedSpans (the same for
    named groups). Offsets are character positions, end-exclusive, so
    text.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.

Geblang 1.32.1

Choose a tag to compare

@dwgebler dwgebler released this 10 Jul 21:12

Tooling

  • The language server now resolves exports from imported project modules.
    With import app.foo, go-to-definition and hover on foo.bar jump to
    and describe bar in 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.

Geblang 1.32.0

Choose a tag to compare

@dwgebler dwgebler released this 07 Jul 10:31

Changed

  • geblang test now 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 matching KNOWN_DIVERGENCES.md row to run on the evaluator; the
    runner cross-validates the two and reports how many files ran that way.
  • instanceof is 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) == SomeClass comparisons are unchanged (still by name). The
    types chapter documents the distinction.
  • Deferred calls now evaluate their callee, receiver, and arguments when the
    defer statement 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 catchable RuntimeError. Previously one runtime silently ignored the
    arguments. Zero-argument parent() 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.staticMethod and
    let m = instance.method produce 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) and mod.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 async function called through a stored value or an
    imported overload set returns a Task like a direct call does; in a
    mixed set the selected overload decides (async yields a Task, 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 the collections equivalents) 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 and assertEquals now 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, and
    assertEquals agrees with == for complex numbers.
  • web.http: a file response built by serveFile survives
    http.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/__done or __iter from a class in another
    module iterates with for-in on the VM.
  • defer accepts module-qualified functions (defer mod.cleanup()) and
    nested-selector static methods on the VM.
  • del works on variables whose class is declared in another module.
  • Cross-module interface hierarchies (Sub extends Base in another module)
    match instanceof Base on the VM.
  • Running a test file's test.run on the VM reports failures with the same
    clean class-prefixed message and frames as geblang 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 at
    del or program exit like a local class does; previously it could fire
    eagerly right after construction, and del never 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
    like s.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.elapsed and metrics.duration now return the documented
    milliseconds as a float with sub-millisecond precision (previously raw
    nanoseconds as an integer). The now() token they take is unchanged.
  • Variables mutated inside a try body keep their values when an exception
    unwinds to the handler, and finally effects always run; previously the
    standard runtime rolled the frame's locals back to their try-entry values.
  • A select statement 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) with b defaulted).
  • reflect.parameters and reflect.returnType work on function values,
    including functions passed across module boundaries; reflect.fields
    reports declared field types and lossless decorator arguments (floats and
    lists included) for cross-module instances; reflect.interfaces returns
    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.mock patches 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), and getBytes returns the raw payload without a
    decode and re-encode round trip.

Geblang 1.31.1

Choose a tag to compare

@dwgebler dwgebler released this 02 Jul 16:34

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 singleThread flag).
  • 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 the web.http module 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 with resource's __enter or __exit is now caught by
    its real class with the clean message (for example catch (ValueError e)),
    instead of a flattened RuntimeError. A with resource whose __enter/__exit
    is inherited from a class in another module now runs it (it was silently
    skipped).
  • When a with body and its __exit both raise, the __exit error now
    propagates and replaces the body's, consistently on both backends (Python
    __exit__ semantics). A with body that returns is likewise overridden by an
    __exit that 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 //admin can 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 with geblang -m, in addition to permissions declared in
    geblang.yaml. Top-level, run, and module help now document the repeatable
    FFI permission flag and its required library path or glob.
  • The redis client 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 naming getBytes, and a new getBytes(key) returns the raw bytes.
    This also fixes replies whose bytes span more than one socket read.
  • The messaging backends (sqs, sns, stomp) now raise a clear error when a
    bytes payload 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.
  • llm image 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.
  • instanceof against a builtin type name that also names an imported module
    (such as bytes or string) is now accepted in expression position (return,
    argument, assignment), not only inside if/while conditions.
  • assertEquals now treats the type value from typeof(x) as equal to its name
    string or to a class value of the same name, matching the == operator, so
    assertEquals("bytes", typeof(x)) and assertEquals(SomeClass, typeof(inst))
    both pass.
  • json.parseAs no longer fails with "class index out of range" when
    deserializing a class whose parent is in another module, including in
    geblang build binaries.

1.31.0 Releaase

Choose a tag to compare

@dwgebler dwgebler released this 30 Jun 21:59

Debugging

  • The VS Code debugger now sets breakpoints inside concurrent worker bodies:
    async.run / async.all / async.race workers, 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.

Performance

  • String index (s[i]), substring / slice, and length() 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.

Geblang 1.30.1

Choose a tag to compare

@dwgebler dwgebler released this 29 Jun 20:30
  • reflect.method(instance, name) now returns null on the bytecode backend
    when instance belongs to another module and the method does not exist,
    matching the evaluator instead of reporting unknown class.

Geblang 1.30.0

Choose a tag to compare

@dwgebler dwgebler released this 29 Jun 19:51

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.Store
    or another explicit synchronized handle for those cases.
  • http.serve, http.listen, and net.serve accept an opts.shareHandler
    flag: 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> versus Box<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 defer inside 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 const or let declared 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, or net.serve is
    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,
    or net.serve call 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 messaging module (RabbitMQ and Kafka) runs on the bytecode backend;
    those calls previously failed there with unsupported native call.

Geblang 1.29.2

Choose a tag to compare

@dwgebler dwgebler released this 27 Jun 01:22

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.

Geblang 1.29.1

Choose a tag to compare

@dwgebler dwgebler released this 26 Jun 22:18

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 is udp (default), tcp, or local;
    udp and tcp work on Linux, macOS/BSD, and Windows, while local (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 exported main and 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 Release

Choose a tag to compare

@dwgebler dwgebler released this 25 Jun 16:25
  • Numeric literals now support scientific notation. Unsuffixed exponent
    literals are exact decimal values (1e3, 1.5e-3, 2E8); add f for
    IEEE 754 floats (1e308f).
  • Enums can now be backed by scalar string or int values:
    enum Status: string { Active = "active"; }. Backed variants expose a
    read-only .value, EnumName.from(value) returns the matching variant or
    throws, and EnumName.tryFrom(value) returns the variant or null.
    Backing values must be unique literals, and variants cannot mix backing
    values with associated data.