defmodule Lattice do @moduledoc """ High-level Elixir interface for the Lattice DSL runtime. ## Usage # Create a runtime {:ok, rt} = Lattice.new() # Evaluate expressions {:ok, 6} = Lattice.eval(rt, "1 + 2 + 3") {:ok, "hello world"} = Lattice.eval(rt, ~s("hello" + " " + "world")) # Define and call functions {:ok, _} = Lattice.eval(rt, ~s( def add(a: Int, b: Int) -> Int { a + b } )) {:ok, 7} = Lattice.call(rt, "add", [3, 4]) ## SQL Support # Create a runtime with SQL support {:ok, rt} = Lattice.new(sql: true) # Query Lattice data with SQL {:ok, _} = Lattice.eval(rt, ~s( let users = [ {id: 1, name: "Alice", age: 30}, {id: 2, name: "Bob", age: 25} ] )) {:ok, result} = Lattice.eval(rt, ~s(SQL("SELECT * FROM users WHERE age > 25"))) ## Value Marshaling Values are automatically converted between Elixir and Lattice: | Lattice Type | Elixir Type | |--------------|-------------| | null | :null atom | | Bool | boolean | | Int | integer | | Float | float | | String | binary | | Path | {:lattice_path, binary} | | List | list | | Map | map | """ alias Lattice.Native @doc """ Create a new Lattice runtime. ## Options - `:sql` - Enable SQL support (DuckDB). Default: `false` - `:llm` - Enable LLM support. Default: `false` ## Examples {:ok, rt} = Lattice.new() {:ok, rt} = Lattice.new(sql: true) {:ok, rt} = Lattice.new(llm: true) {:ok, rt} = Lattice.new(sql: true, llm: true) """ def new(opts \\ []) do sql = Keyword.get(opts, :sql, false) llm = Keyword.get(opts, :llm, false) cond do sql and llm -> Native.new_runtime_with_all() sql -> Native.new_runtime_with_sql() llm -> Native.new_runtime_with_llm() true -> Native.new_runtime() end end @doc """ Evaluate Lattice source code. Returns `{:ok, value}` on success or `{:error, reason}` on failure. ## Examples {:ok, 42} = Lattice.eval(rt, "40 + 2") {:ok, "hello"} = Lattice.eval(rt, ~s("hello")) {:ok, [1, 2, 3]} = Lattice.eval(rt, "[1, 2, 3]") """ def eval(runtime, source) do Native.eval(runtime, source) end @doc """ Evaluate with pre-bound variables. Bindings should be a keyword list or list of `{name, value}` tuples. ## Examples {:ok, 30} = Lattice.eval_with_bindings(rt, "x + y", [{"x", 10}, {"y", 20}]) {:ok, 30} = Lattice.eval_with_bindings(rt, "x + y", x: 10, y: 20) """ def eval_with_bindings(runtime, source, bindings) do # Convert keyword list to list of tuples with string keys binding_tuples = Enum.map(bindings, fn {key, value} when is_atom(key) -> {Atom.to_string(key), value} {key, value} when is_binary(key) -> {key, value} end) Native.eval_with_bindings(runtime, source, binding_tuples) end @doc """ Call a Lattice function by name. ## Examples # First define a function Lattice.eval(rt, "def double(x: Int) -> Int { x * 2 }") # Then call it {:ok, 10} = Lattice.call(rt, "double", [5]) """ def call(runtime, name, args) do Native.call_function(runtime, name, args) end @doc """ Get all registered type schemas. Returns a list of type schema maps that can be used to generate Elixir structs or validate data. """ def get_types(runtime) do Native.get_types(runtime) end @doc """ Get all function signatures. Returns a list of function signature maps with name, params, return type, and metadata (is_llm, is_async). """ def get_function_signatures(runtime) do Native.get_function_signatures(runtime) end @doc """ Check if a function exists. """ def has_function?(runtime, name) do Native.has_function(runtime, name) end @doc """ Get a global variable. Returns `{:ok, value}` if found, `{:error, "not_found"}` otherwise. """ def get_global(runtime, name) do Native.get_global(runtime, name) end @doc """ Set a global variable. """ def set_global(runtime, name, value) do Native.set_global(runtime, name, value) end @doc """ Reset the runtime, clearing all state. """ def reset(runtime) do Native.reset(runtime) end end