x-lang

Reflection

x-lang: computational expressions over a minimal, type-agnostic engine.

How to look inside a running system: what each kind of thing is, the doors that answer questions about it, and where the detail lives. This page names the doors and links their owners; signatures, argument types and examples are what (help Name/method) and the generated reference answer, and they are not repeated here.

What reflection means here

The engine is a CPU: it executes what it is given and checks nothing. Every structure it runs on is built from the same atoms and pairs as a program’s own data, laid out as the contracts the engine ships describe (The Engine Contract): the words of an object, the rows of the base, the instruction set. The library reads those layouts, which is how the printer, the module loader and the collector’s hooks are written in x-lang, and it is how a program can read them too.

So there are two kinds of door. The named doors are classes: each checks what it is handed, answers a value or () for a miss, and is documented. The raw doors read and write words at the contract’s offsets, check nothing, and are the maintenance hatch. This page walks the named doors by level and names the hatch once, at the end.

Finding out what exists

The library documents itself: every (doc …) form files an entry, and the same registry serves the prompt, the editor and the reference generator.

Standard Library says how the library is laid out and how a call is spelled; the Object System says how a class documents its members.

A value and its type

Every value carries a type label, and the type it names is a structure a program can read: its name, its handler stacks for evaluation, calling, reading, writing and iteration, its conversions and its operator table.

The model is in Type System and the normative form in the Specification; the sandboxing tutorial restyles a built-in and puts it back.

A class and its members

A class is a value, and so is an instance’s class.

The table and the words for members are in the Object System.

A callable

A closure is made of its parameter list, its body forms and the environment it was made in; an operative is the same with the name it binds the caller’s environment to; a wrapped combiner holds the combiner it wraps; an engine primitive has no state at all. Fn reads them:

wrap, unwrap, fn and op themselves are in Primitives and core forms, and the words entry and state in the Glossary.

An environment

An environment is a first-class value: one pair, its bindings and the environment it was made in. A frame’s bindings are an alist of names and values; the root’s are a tree, and the root has no parent. Every call makes a child of the closure’s environment, an operative receives its caller’s as a value, and eval with an environment makes it current. That is engine law 10 in Engine Laws.

An environment is not for write: its chain ends at the root, which binds closures whose environment is the root again, so printing never finishes. (Env names e) is the question to ask of one, and (Env parent e) the step to the frame outside it. Env is the evaluator’s environment, not the process’s, which Sys environ and Sys getenv read. Module scope, and what leaves a module and how, is in Modules.

Modules

The module registry is data: which modules the session knows and which have loaded, what each provides, which module owns a name, and where a module’s names live.

How a module’s names are reached from outside is in Modules.

Where a form came from

The reader stamps the first cell of each form it reads with the line it began on and the file it came from, and include files each path it loads under an id. Source reads the stamps and the registry; Io reads the snapshot a raise takes.

A program the wrapper streams onto standard input, through -c or -f, is read behind the wrapper’s own forms: its lines are stream lines and its file is standard input. A file reached by include carries its own lines and its path.

The base, the heap and the raw hatch

A base is a whole interpreter: an environment, a type registry, a reader and a catalogue of instructions. A program can make one, drive it, and read any of its rows by name.

Below the named doors is the hatch. (Obj ref o i) and (Obj set! o i v) read and write an object’s data slots, (Obj meta-ref o i) its meta words, (Obj ->ptr o) its address, and Ptr reads and writes words at any address. The offsets they take are the engine’s, in its obj-layout.x, and a raw door checks nothing: a slot read as an object that is not one walks off, as the engine’s own first does on a non-pair. The sandboxing tutorial reads a child this way, and Primitives and core forms lists what the coordinates reach.

What the doors do not answer

There is no backtrace: the engine keeps the save stack and the handler chain and no record of the calls between them. The running base is not a value; Base cell reads a child. A closure prints as #<fn>, and nothing about it is shown unless asked for through Fn.

The tools that read the system through these doors, the profiler, the coverage tool, the linter, the image writer and the reference generator, are described in Dev tools.