Cheatsheet: embedding Logos

Copy Markdown

Quick reference for common library tasks. For a language-syntax cheatsheet, see LOGOS_CHEATSHEET.md.

Setup

TaskCode
New isolated runtime (with stdlib loaded)runtime = Logos.new_runtime()
New runtime without stdlib (Layer 1 only)runtime = Logos.Runtime.new()
Load stdlib into a bare runtimeLogos.Stdlib.load!(runtime)

Evaluating

TaskCode
Evaluate one form{:ok, val, env} = Logos.eval_string(src, runtime)
Evaluate many top-level forms{:ok, val, env} = Logos.eval_string_sequence(src, runtime)
Thread lexical continuity across callspass the returned env into the next call
Read a form without evaluating{:ok, form} = Logos.Reader.read(src, runtime)
Read every top-level form{:ok, forms} = Logos.Reader.read_all(src, runtime)
Macroexpand onlyLogos.Macroexpand.expand(form, MapSet.new(), runtime)
Print a value back to textLogos.Printer.print(value)

Namespaces & Vars

TaskCode
Ensure a namespace existsLogos.Namespace.ensure!(runtime, "my-ns")
Intern a Var directlyLogos.Namespace.intern!(runtime, ns, name, value, meta \\ %{})
Read a Var's valueLogos.Namespace.get_var_value(runtime, ns, name)
Mutate a Var's valueLogos.Namespace.set_var_value!(runtime, ns, name, value)
Check if a name is publicLogos.Namespace.public?(runtime, ns, name)
List every namespaceLogos.Namespace.all(runtime)
Current namespace (this process)Logos.Runtime.current_ns(runtime)
Switch namespace (this process)Logos.Runtime.set_current_ns!(runtime, "my-ns")

Calling into Logos from Elixir

TaskCode
Apply an already-evaluated Fn/primitiveLogos.Eval.apply_fn(callable, arg_values, runtime)
Get a defined function, then call it{:ok, f} = Logos.Namespace.get_var_value(rt, ns, name)<br>Logos.Eval.apply_fn(f, args, rt)

Exposing Elixir functions to Logos

TaskCode
Register a host function directly (recommended today -- see gaps)Logos.Namespace.intern!(rt, ns, "my-fn", {:host_fn, MyMod, :my_fun})
Add an import-allowlist entry (dotted names work, e.g. "String.upcase")edit @entries in lib/logos/interop/allowlist.ex

Concurrency

TaskCode
Spawn a Logos closure as a real processpid = Logos.Process.spawn(fn_value, runtime)
Spawn + linkLogos.Process.spawn_link(fn_value, runtime)
Spawn + monitorLogos.Process.spawn_monitor(fn_value, runtime)
Send a messageLogos.Process.send(pid_or_atom, msg)
This (Elixir) process as a Logos.PidLogos.Process.self()

Dev tooling

TaskCode
Start a local REPLmix logos.repl
Run a script filemix logos.run path/to/script.logos arg1 arg2
Run an inline expressionmix logos.run -e "(+ 1 2)"
Format files in placemix logos.format "lib/**/*.logos"
Check formatting (CI)mix logos.format --check-formatted "lib/**/*.logos"
Attach a remote REPLmix logos.remsh --sname r1 --cookie secret --target host@node --runtime my-runtime
Register a runtime for logos.remshLogos.Runtime.Registry.register("my-runtime", runtime)

Errors

ShapeMeaning
{:ok, value, env}success
{:error, {:unbound_symbol, name}}symbol has no binding
{:error, {:not_a_number, args}}arithmetic primitive got a non-number
{:error, {:arity_mismatch, got, expected_shapes}}wrong number of args to a Logos fn -- expected_shapes is a list, one entry per fn clause (more than one for a multi-arity fn): an integer for a fixed-arity clause, {:at_least, n} for a variadic one
{:error, {:uncaught_throw, tag, value}}a (throw ...) escaped every try
{:error, {:not_callable, value}}tried to call something that isn't a Fn/primitive