Logos.Primitives (Logos v0.2.0)

Copy Markdown

Layer 1: the Elixir-implemented primitive functions every Logos program is ultimately built on -- arithmetic, comparison, collections, namespace/Var management, macros, concurrency, and dev-tooling introspection. (String operations are not primitives here; they reach Logos through importing allowlisted Elixir functions -- see Logos.Interop.Allowlist.) Every primitive takes (args, runtime) uniformly, where args is the already-evaluated argument list and runtime is the calling Logos.Runtime.

Each primitive is interned as an ordinary Var in the logos.core namespace by install!/1, with its value set to a {:primitive, name} marker rather than a raw Elixir function. This means primitive lookup goes through exactly the same symbol-resolution path as any other Var (Logos.Eval.resolve_symbol_location/2): a bare reference to + in the user namespace resolves via user's implicit logos.core refer, finds the Var, sees its value is a {:primitive, name} marker, and Logos.Eval.apply_fn/3 dispatches accordingly to apply/3 below.

Besides the familiar arithmetic/comparison/list core (+ - * / = < > <= >= first rest cons list throw) and namespace machinery (intern-var!/in-ns/require/use/import, set-macro!/macro?, gensym, push-thread-binding!/pop-thread-binding! -- backing core.logos's binding macro, see those primitives' own comments), two small "collection primitives" -- vector and to-list -- exist to make core.logos's let constructible from list/cons alone (see that file's comments); concat/list->vector/list->map/ list->set support real syntax-quote desugaring (lib/logos/reader/actions.ex), which needs a genuine n-ary concat and a way to turn the resulting flat list back into a vector/map/set for syntax-quoted [...]/{...}/#{...} literals.

Error convention

A primitive that can't do what was asked (division by zero, wrong arity, an unbound symbol upstream, ...) raises Logos.EvalError (Logos.Eval) rather than returning an {:error, reason} tuple -- reason is exactly what that tuple's second element always was. This lets Logos-level try/catch intercept a primitive-level failure the same way it already catches an explicit (throw ...) -- see Logos.Eval's eval_try/3. Logos.eval_string/3's outermost boundary rescues it back into {:error, reason}, so the public top-level API's contract is unaffected.

Sorted collections and transients

compare, sorted-map/sorted-map-by, sorted-set/sorted-set-by/ sorted-set-put/sorted-set-remove, and transient/conj!/assoc!/ dissoc!/disj!/pop!/persistent! are Elixir-level of necessity, not by choice: Logos.SortedMap/Logos.SortedSet (genuine new value types, mirroring Logos.Vector) need real access to their own sorted association-list representation, and Logos.Transient needs real :ets mutation -- see those three modules' own moduledocs for the full reasoning (why a sorted association list rather than a balanced tree, why a :private ETS table rather than a process+message design like atom). logos_compare/2 (this module's private ordering logic, shared by compare and every sorted collection's default comparator) and comparator_fn/2 (wraps a sorted-map-by/sorted-set-by caller- supplied Logos function into the plain 2-arity Elixir function those two value types need, via Logos.Eval.apply_fn/3 -- the same call-back mechanism apply itself already uses, no new evaluator machinery) sit at the very end of this file, alongside the pre-existing numeric-tower comparison helpers they build on.

Concurrency primitives

spawn/spawn-link/spawn-monitor/link/unlink/monitor/ demonitor/trap-exits!/exit/self/send/receive-match! dispatch to Logos.Process -- see that module's moduledoc for the real BEAM-native mechanics (genuine processes, real receive/after, real links/monitors). pid->atom wraps the %Logos.Pid{} a spawn call returns into a %Logos.Atom{}: concurrency.logos's atom function is an ordinary Lisp function (not built on Elixir's Agent) that spawns a stateful loop process, and since Logos has no generic "construct an arbitrary struct" facility from Lisp code itself, this one-line identity wrapper is the minimal Elixir-level plumbing that requires -- the atom's actual stateful behaviour lives entirely in concurrency.logos's atom-loop.

Summary

Functions

Applies the primitive named name to already-evaluated args, against runtime. Called by Logos.Eval.apply_fn/3 whenever the callable value is a {:primitive, name} marker. Raises Logos.EvalError on failure -- see moduledoc.

Every primitive's own {shape, doc} pair, keyed by name -- see @doc_data's own comment.

Interns every Layer-1 primitive as a {:primitive, name}-valued Var, grouped by namespace (logos.core's existing set, plus the new logos.map), with real :doc metadata from @doc_data/docs/0. Logos.Stdlib.load!/1 refers logos.map into logos.core after this runs, the same way it refers the .logos-file namespaces -- see that module's moduledoc.

Functions

apply(name, args, runtime)

@spec apply(String.t(), [term()], Logos.Runtime.t()) :: term()

Applies the primitive named name to already-evaluated args, against runtime. Called by Logos.Eval.apply_fn/3 whenever the callable value is a {:primitive, name} marker. Raises Logos.EvalError on failure -- see moduledoc.

docs()

@spec docs() :: %{required(String.t()) => {String.t(), String.t()}}

Every primitive's own {shape, doc} pair, keyed by name -- see @doc_data's own comment.

install!(runtime)

@spec install!(Logos.Runtime.t()) :: :ok

Interns every Layer-1 primitive as a {:primitive, name}-valued Var, grouped by namespace (logos.core's existing set, plus the new logos.map), with real :doc metadata from @doc_data/docs/0. Logos.Stdlib.load!/1 refers logos.map into logos.core after this runs, the same way it refers the .logos-file namespaces -- see that module's moduledoc.