C implementation of Io's first-class Message. An IoMessage is the AST node and the runtime perform record rolled into one: it holds a name (IoSymbol), an args List of sub-message trees, an optional cachedResult (literal or inline-cache value), a `next` pointer to the rest of the chain, an inline slot-lookup cache (inlineCacheValue / inlineCacheContext, marked with the containing tag version), and source-location fields (label, lineNumber). Evaluation goes through IoMessage_locals_performOn_, which delegates to the iterative evaluator in IoState_iterative.c whenever a frame exists; a bootstrap-only recursive fallback handles the short window before the first eval loop starts. Most Io-visible getters/setters are thin wrappers around IoMessage_raw*_ helpers; the raw helpers are also how IoMessage_parser and IoMessage_opShuffle build and rewrite trees without running the perform machinery.
Registered as the tag's activateFunc so that when a Message is stored in a slot and looked up, evaluating it re-runs its chain in the caller's locals. Simply delegates to IoMessage_locals_performOn_ — the target argument is ignored because a Message activation evaluates the message tree at `self`, not the containing slot's receiver.
Appends `m` to the args List with an IOREF retain. The one primitive every tree builder (parser, opShuffler, C extensions) ultimately calls through.
Appends a fresh anonymous Message as a new arg and stores `v` on it as a cachedResult. Lets C code inject pre-computed values into an arg list without the parser/shuffler being involved.
Core printer. Appends the message name, optionally followed by "(arg1, arg2, ...)" built via recursive calls, then either stops (follow=0) or walks the `next` chain inserting spaces between messages and newlines between semicolon-terminated statements.
Returns a copy of `self` whose args have been evaluated (in the sender context, or an optional explicit context arg from `m`) and stashed as cachedResults so the caller can hand the resulting message off without triggering re-evaluation. If the tree is already fully constant the original is returned directly (no copy needed).
Formats a message as "file:line name" for a single stack-trace entry. Used by the error/backtrace path to render frames without dumping the full message tree.
Raises an Io-level error if the message has fewer than `n` args. Named receiver is included in the message so stack traces identify the offending call site.
Shallow-copies name, args, next, cachedResult, and source location from `other` into `self`. The args List is rebuilt with IOREFed element pointers (no deep copy of the sub-trees). Use IoMessage_deepCopyOf_ if recursive duplication is needed.
Recursively duplicates the entire message tree: a fresh IoMessage for `self`, deep-copied args, deep-copied next chain, same name and cachedResult. This is the semantics behind Io's `Message clone` and is also used by the assign rewriter in IoMessage_opShuffle when it must splice a subtree into a new argument position.
Returns a freshly allocated UArray containing the decompiled source text of the message and every message linked via `next`. Caller owns the UArray.
Like IoMessage_description but stops at `self`'s args — does not follow the next chain. Useful for rendering a single call site in a stack trace without dumping the rest of its expression.
Parses the arg list of a `foreach`-style call into its three logical pieces (optional index slot name, value slot name, body message). If fewer than two args are present it reports the count error via assertArgCount and bails out with NULLs, since IoState_error_ no longer longjmps and the caller must not continue into the arg-access code.
Registered as the tag's freeFunc. Frees the args List and the IoMessageData payload. The GC-managed IoObject header is released by the collector; name/next/cachedResult are marked elsewhere, not freed here.
Converts a literal token into its runtime Io value and stores it as the message's cachedResult so evaluation can return the value without a re-parse. Handles the four literal token types plus the three reserved words nil/true/false that are identifiers at the lexer level. The cachedResult short-circuits the evaluator's performFunc lookup in IoMessage_locals_performOn_.
Predicate: true if `self` has no cached result — i.e. evaluating it will hit the normal slot-lookup path.
Sets the source label on `self` and recursively on every arg and every message in the `next` chain. Used by the parser to stamp a file name across a freshly compiled tree in one pass.
Evaluates arg n and coerces to a C truthy/falsey int using Io's semantics: nil and false are 0, everything else is 1. Skips type validation deliberately — any Io value can act as a condition.
Evaluates arg n in `locals` and requires the result be an IoNumber. On mismatch, raises via the shared error helper and returns ioNil so the caller can early-exit without longjmp. All typed-arg accessors below follow the same pattern.
Shared error reporter for the typed-arg accessors below. Raises
"argument N to method 'foo' must be a
The canonical C-level evaluator entry point. When an eval loop is running (state->currentFrame set) it delegates to the iterative evaluator in IoState_iterative.c so C stack depth stays bounded. The remaining body is a recursive fallback used only during VM bootstrap before the first frame exists; it walks the chain node-by-node, dispatching through the tag's performFunc and honouring MESSAGE_STOP_STATUS_ / errorRaised unwinding signals.
Typed arg accessor requiring an IoSeq (mutable or immutable). Sister helpers cover symbols, mutable sequences, blocks, dates, messages, lists, and maps — each wraps valueArgAt_ with an ISfoo test and a shared error path.
Typed arg accessor with an extra non-negativity check — useful for counts and lengths. A negative value raises rather than silently wrapping to a huge unsigned quantity.
Evaluates arg n and forces the result through `asString` so callers that want a description (not strict type checking) don't need to branch on the value's type.
Registered as the tag's markFunc. Walks every GC-visible pointer: name symbol, cachedResult, each arg, next, label, and the inline slot-cache value/context. Missing an entry here would let a reachable message dangle and crash on next perform.
Recursive predicate used by IoMessage_asMessageWithEvaluatedArgs to decide whether any node in the tree has a non-cached arg that would need to be run. Skips allocation when the entire tree is already constant.
Convenience constructor: look up the registered proto and clone it. Preferred entry point in C when you need a fresh, nameless message to populate.
Drives the full compile pipeline: lex -> parse -> opShuffle -> label. The collector is paused over the whole run so intermediate IoMessages aren't collected while the tree is being built. The lexer is freed before return; the caller receives a ready-to-evaluate message root.
Convenience wrapper that interns `label` as an IoSymbol and delegates to IoMessage_newFromText_labelSymbol_. This is the usual entry point for C code that has a bare const char * filename to attach to a message tree (e.g. IoState_doCString_).
Top-level parse entry point. Reports a compile error via IoState_error_ if the lexer flagged one, consumes a leading TERMINATOR, then recurses into IoMessage_newParseNextMessageChain for the actual chain. Returns a synthesized nil message for an empty input, and a compile error if tokens remain after the chain (dangling punctuation).
Parses one message chain: optional name, optional parenthesised args, optional attached next message, and any number of semicolon-separated continuations (each stitched together with an explicit ";" message that uses state->semicolonSymbol). This mirrors the grammar that IoMessage_opShuffle later rewrites for operator precedence.
Builds the Message tag with clone/free/mark/activate function pointers. The activateFunc is what distinguishes Messages from most other tagged primitives — it makes them callable as slot values.
Fresh message with just a name set. The symbol should already be interned via IoState_symbolWithCString_; the raw setter handles the IOREF retain.
Fresh message named `symbol` whose single argument carries `arg` as its cached result. Convenience for constructors that want to emit `setSlot("name", value)`-style expressions without building the arg list by hand.
As newWithName but also stamps a source label so error messages and stack traces mention the right file.
Builds a cached message: evaluating it yields `v` without any slot lookup because cachedResult short-circuits the perform path. This is how the parser synthesises pre-evaluated literal and nil/true/false nodes, and how the opShuffler drops constant arguments into rewritten assign expressions.
Io-visible entry point. Walks the message tree iteratively using an explicit `expressions` worklist instead of C recursion — each Levels_attach call may push sub-expressions (the tails of assign rewrites, plus each message's args) for later processing. Returns self with the tree rewritten in place.
C-side trigger used by IoMessage_newFromText_labelSymbol_ to kick off operator shuffling via the Io message `opShuffle` sent to `self`. This indirection lets Io code override opShuffle; the guard against noShufflingSymbol skips shuffling for synthesised messages whose name signals that they already arrived in normal form.
Consumes the OPENPAREN and all comma-separated argument chains, appending each parsed sub-chain to self's args list. Each arg is itself a full message chain, recursively parsed by IoMessage_newParseNextMessageChain. Closes on CLOSEPAREN; missing close paren and missing-argument cases would be flagged by the lexer earlier via IoLexer_readMessage_error.
Pops the current token and installs it as the message's name. Runs IoMessage_ifPossibleCacheToken_ so literals pick up their cached value, and records lineNumber/charNumber for diagnostics and stack traces.
Parses the next message in the chain (after the current name/args) and links it via IoMessage_rawSetNext_. This is what turns `a b c` into three linked messages rather than three separate top-level expressions.
Debug-style print: builds a full description UArray and sends it to the VM's print hook. No trailing newline.
As IoMessage_print but follows with a newline. Used by the REPL.
Creates the Message proto: allocates IoMessageData with a fresh args List, installs placeholder name/label symbols, tags it, and wires up the Io-visible method table. Every subsequently-created Message is a clone of this proto via IoMessage_new -> IoMessage_rawClone.
Returns the nth arg sub-message with a stackRetain so it stays alive for the duration of the current retain pool. Returns NULL if n is out of range — callers must check.
Registered as the tag's cloneFunc. Gives the new message its own IoMessageData and args List so sibling clones never share arg references. Copies name and label; leaves cachedResult / next / source location cleared — the parser or caller fills those in.
Copies line number and label from `other` to `self`. Used by the opShuffle rewriter when it synthesises new messages (e.g. the grouping "()" wrappers) so the new nodes point back at the original source position for debug output.
True when the message is the synthesised ";" statement separator. Uses pointer identity against the interned semicolonSymbol rather than a strcmp, so it's free.
Walks to the last message in the chain. O(n); used by code that needs to append to an existing chain.
Walks `next` until the next message is an EOL (or chain end) and returns the message just before that. Used to rewrite statement-local tails without crossing a `;` boundary.
Walks `next` past any number of ";" EOL nodes and returns the first non-EOL message. Used by code that wants the next "real" action without caring about statement boundaries.
Attaches `v` as the cached result (NULL clears). IOREFed to keep it alive across collections. Reading back via DATA()->cachedResult is the fast-path the evaluator checks before running the normal perform.
Installs the source label symbol with IOREF retention. Paired with IoMessage_label_ which also recurses into children.
Installs the message's name symbol and clears the cached isSpecialForm flag — the evaluator re-derives specialness (if/while/for/...) from the new name on the next lookup.
Sets the `next` pointer with IOREF retention; NULL clears it. If IOMESSAGE_HASPREV is defined, also updates the reverse link on `m` so parent-lookup stays consistent. Called from the parser and opShuffler to splice chain nodes without running perform.
Sets the reverse `previous` link under IOMESSAGE_HASPREV builds, with an IOREF retain and the symmetric forward wiring on `m`. A no-op when the reverse-chain feature is disabled at compile time.
Integer-specialised variant of setCachedArg_to_. Allocates the arg slot without boxing the int upfront, then stamps an IONUMBER on it — the early-exit ordering avoids boxing if the arg list still needs to grow.
Stores `v` as the cached result of the nth arg, auto-extending the args list with fresh anonymous messages if it's shorter than n. Used by asMessageWithEvaluatedArgs to fill out evaluated-arg positions by index.
Builds the default assign-operator -> method-name table: `:=` becomes setSlot, `=` becomes updateSlot, `::=` becomes newSlot. Levels_attach consults this before the binary-op table; matching entries trigger the `a := b` -> `setSlot("a", b)` rewrite.
Builds the default binary operator precedence table as an IoMap of symbol->number. Lower numbers bind tighter; `**` (exponent, level 1) is tightest, assignment ops (level 13) and `return` (level 14) the loosest. The returned map is stored under OperatorTable operators and can be mutated from Io via the public OperatorTable API.
Wires `msg` into the current level according to its type: ATTACH links via `next`, ARG appends as an argument to the level's message, NEW adopts it as the level's message, UNUSED is a no-op. Used as the primitive by Level_attachAndReplace and the ARG-push path.
Combines Level_attach with a state transition to ATTACH so subsequent messages chain via `next`. The canonical "ordinary message" update for a Level as the shuffler walks a chain of non-operator messages.
Closes a Level as it is popped: severs the message's `next` link (the sub-tree is now a complete operator subexpression) and flattens any synthetic single-arg "()" wrapper that was inserted earlier to imitate C-style grouping. Finally marks the slot UNUSED so Levels_reset's next sweep sees a clean pool.
Puts a freshly-pushed Level into ARG mode so the next shuffled message is appended as the first argument of `msg` (the operator). The recorded precedence is what Levels_popDownTo compares against to unwind.
Core shuffler step: decides what to do with the next message in the chain. Three branches: (1) assign operator — rewrite the attaching message into setSlot/updateSlot/newSlot with the slot name quoted as a cached-result argument, then splice the value expression into the new argument list, pushing the tail onto `expressions` for later; (2) end-of-line `;` — pop down to the lowest precedence so a new statement starts fresh; (3) non-assign operator — wrap any preset args in a synthetic "()" grouping so `a +(b,c)` parses sanely, pop higher-or- equal precedence levels, then push a new ARG level. Otherwise attach as a normal message.
Attaches `msg` (an operator) onto the current top level, then pushes a new ARG-type level awaiting the operator's right-hand side at the given precedence. This is how `a + b * c` pushes a `*` level above the `+` level so `b` and `c` get grouped first. Errors out if the IO_OP_MAX_LEVEL-deep pool would overflow.
Releases the stack List and the Levels struct. The Level pool is inline so it needs no separate free.
True if the symbol is a key in the assign-operator map (i.e. `:=`, `=`, or `::=` by default). Levels_attach uses this to branch into the assign-rewrite path before trying precedence-based handling.
Looks up the precedence for `messageSymbol` in the binary operator map. Returns -1 if the symbol isn't an operator. Validates that the mapped value is a Number in [0, IO_OP_MAX_LEVEL) and raises a compile error otherwise — user code can edit OperatorTable operators from Io so defensive checking is required.
Resolves an assign operator like `:=` to the slot-method name to emit (e.g. setSlot). Special-cases `:=` with an uppercase slot-name first character to return setSlotWithType so `MyType := X` becomes setSlotWithType("MyType", X). Raises a compile error if the configured value is not a Symbol.
Allocates a fresh Levels and binds it to the OperatorTable object reachable from `msg` (falling back to Core's OperatorTable, and creating one from scratch if neither has one). The two maps are then cached on the Levels so Levels_attach can do constant-time precedence lookups as it walks the chain.
Finishes the current expression: pops every Level on the stack through Level_finish (wiring up any remaining open operator sub-trees), then resets the pool for the next top-level expression. Called by IoMessage_opShuffle between expressions popped from its worklist.
Pops Levels off the stack until the top's precedence is strictly greater than targetLevel, or until we hit a pending ARG level (which is never popped implicitly — its consumer must supply the next token). Level_finish runs on each popped level to wire up the completed sub-tree.
Returns the Levels pool to the state expected at the start of a fresh expression: every slot UNUSED except level 0, which is a NEW-type sentinel with the maximum precedence so any operator binds tighter. Called between top-level expressions by Levels_nextMessage.
Fetches the named operator map slot from an OperatorTable object, lazily creating and installing one via the supplied factory if it's missing. Used to resolve both the precedence map and the assign-operator map with a single helper.