Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

meta - interpreter identity and reflection

Enable with use meta;. Holds facts about the running Jennifer interpreter itself - the build version, which Go toolchain compiled it, and similar information that programs typically log for bug reports, embed in error messages, or branch on for build-specific behaviour - plus a small reflection surface for invoking a top-level method by a runtime string name (meta.call / meta.defined and their entry-program siblings meta.callMain / meta.definedMain).

This is distinct from os (which is about the host environment - operating system, CPU architecture, environment variables) and from the rest of the standard library (which is about user data).

use io;
use meta;

io.printf("Jennifer %s (%s build)\n",
    meta.VERSION, meta.BUILD);

Constants

NameKindValue
meta.VERSIONstringThe interpreter’s build version. See format below.
meta.BUILDstringWhich Go toolchain compiled the interpreter: "go" or "tinygo".
meta.SYSMODDIRstringThe resolved system module directory (where bare imports look). Resolved from --sysmoddir > JENNIFER_SYSMODDIR > the compile-time default; jennifer version -v shows the layers.

VERSION string format

The build pipeline derives meta.VERSION from git describe --tags --long:

Repository statemeta.VERSION value
HEAD is exactly on a semver tag"0.4.0"
HEAD is N commits past the most recent tag"0.4.0-dev+2.1023204"
No tags exist yet"0.0.0-dev+<N>.<shortsha>"
Built without git (or outside a repo)"dev"

The dev+ prefix is intentional: any non-tagged build is a development build, and the N.shortsha suffix lets you trace which commit produced it.

BUILD values

meta.BUILD distinguishes which Go variant compiled the interpreter binary. Useful because TinyGo has subtly different runtime behaviour from standard Go (different GC, different scheduler tuning, different stdlib subset) - a program that needs build-specific behaviour or just wants to log “which interpreter is this for the bug report” can branch on this value.

ValueMeaning
"go"Built with the standard Go toolchain (gc) - the default jennifer binary
"tinygo"Built with TinyGo - the constrained jennifer-tiny binary

make build produces both binaries: the default jennifer (standard Go, meta.BUILD == "go") and jennifer-tiny (TinyGo, meta.BUILD == "tinygo"). make build-go and make build-tinygo produce only one each. go run against the source also reports "go". If a future alternative compiler shows up, its identifier passes through directly rather than being normalised - so the constant always reports honestly what built the binary.

Reflection - calling a method by name

Jennifer has no first-class functions: a bare call greet(...) is resolved at compile time, so you cannot dispatch on a name computed at runtime. meta.call closes that gap - it invokes a top-level user method by a runtime string, the general form of what testing.run does for tests.

CallReturns
meta.call(name, args...)the method’s return valueInvoke the method name with the given arguments (arity + declared types checked, as at a normal call site).
meta.defined(name)boolWhether a method name exists - validate a name before calling it.
meta.callMain(name, args...)the method’s return valueLike call, but resolves against the entry program’s methods.
meta.definedMain(name)boolLike defined, against the entry program.
use io;
use meta;

func greet(name as string) { return "hi " + $name; }

io.printf("%s\n", meta.call("greet", "ada"));   # hi ada
io.printf("%t\n", meta.defined("nope"));         # false

Unlike testing.run, meta.call is transparent: it does not catch exit, and every sentinel a normal call can raise - a runtime error, a thrown Error, exit - propagates to the caller, catchable with try / catch.

callMain / definedMain - reaching the entry program

Modules run on isolated sub-interpreters, so a meta.call inside a module reaches that module’s own methods, not the program that imported it. The *Main variants cross that boundary: they resolve against the entry program’s top-level methods. This is what lets a framework module dispatch to handlers the application defined - the web module registers routes against handler names and calls them with meta.callMain. Called from the entry program itself, callMain / definedMain coincide with the plain forms (the entry program is its own host). Struct arguments are re-tagged across the boundary automatically, so a module can pass one of its own struct values (e.g. a web.Context) to an entry-program handler that declares it.

Build flow

meta.VERSION is set at build time by a small codegen step.

The Makefile runs scripts/gen-version.sh before tinygo build / go build, writing a generated internal/version/version_gen.go whose init() assigns version.Version to the string from scripts/version.sh. The meta library then mirrors that into the interpreter as the meta.VERSION constant.

You don’t need to run the codegen step manually if you build via make build (TinyGo) or make build-go (Go). A bare go test ./... skips codegen and uses the default "dev" baked into version.go.

Codegen rather than go build -ldflags -X is used because TinyGo 0.41 silently ignores -X. The generated file is .gitignored.

Roadmap

meta is a new library and intentionally small. Future candidates if they earn their slot: build time, git SHA (separated from the version string), REPL-vs-script mode, runtime GC stats, scheduler diagnostics. The library exists in part to give those a natural home when they land.

See also: os.md, index.md.