Releases: dwgebler/geblang
Releases · dwgebler/geblang
Release list
Geblang 1.33.0
- Regex match results now carry position information. The dicts returned
byre.match/re.matchAll,pcre.match/pcre.matchAll, and the
match/matchAllmethods on both compiledPatternclasses gain
three fields:span([start, end]of the whole match),spans
(one[start, end]per capture group, aligned withgroups,null
for a group that did not participate), andnamedSpans(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
Tooling
- The language server now resolves exports from imported project modules.
Withimport 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.
Geblang 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.staticMethodand
let 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, and
assertEqualsagrees with==for complex numbers. web.http: a file response built byserveFilesurvives
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/__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 at
delor 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.fields
reports 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.
Geblang 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-requestsingleThreadflag). - 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 rendereduncaught ...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/__exit
is 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 in
geblang.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 a
bytespayload 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, so
assertEquals("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 in
geblang buildbinaries.
1.31.0 Releaase
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.
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.
Geblang 1.30.1
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.
Geblang 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; usestore.Store
or another explicit synchronized handle for those cases. http.serve,http.listen, andnet.serveaccept anopts.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
examplemath.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 exampleBox<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.
Geblang 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.
Geblang 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 Release
- Numeric literals now support scientific notation. Unsuffixed exponent
literals are exactdecimalvalues (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.