Zero-dependency Elixir parser, streaming engine, runtime evaluator, schema validator, and prompt generator for the OpenUI 1.0 Language (openui-lang).
OpenUILang compiles a declarative UI component catalog into a fast, zero-dependency parser and evaluator capable of turning streaming LLM tokens (root = Dashboard(...)) into validated, reactive component trees at ~50% fewer tokens than equivalent JSON.
Used as the core language engine for openui_tui (OpenUI TUI Maker & Studio for Elixir).
Features
- Zero Runtime Package Dependencies: Powered by a native Zig NIF (
native/openui_nif.zig) for lexing, Pratt expression parsing, stateful stream buffering, AST materialization, and JSON Schema validation, paired with OTP 27+:jsonstdlib. - Line-Oriented Streaming Parser (
OpenUILang.stream_parser/2): Incrementally scans, parses, and materializes the UI tree on every incoming LLM token chunk (~6.9 µs/chunk) via a statefulErlNifResourceType(ZigStreamResource) with zero-copy BEAM term caching so Terminal UIs (openui_tui) or Phoenix LiveViews can render progressive frames in real time. - Positional-to-Named Schema Binding (
OpenUILang.compile_schema/1): Binds compact positional calls (Stat("Throughput", $live_ops, "+12%", Sparkline($history, "info"))) to JSON Schemapropertiesin exact document key insertion order (OpenUILang.decode_json!/1). - Statement-Level Diff & Patch Merging (
OpenUILang.merge_statements/3): Allows LLMs to emit surgical 1–3 line statement patches (kpis = Row([...])) instead of regenerating an entire UI program, automatically pruning orphaned subtrees. - Reactive Runtime Evaluator (
OpenUILang.evaluate_result/2):- Reactive
$statebindings ($slab = "1",@Set($slab, "2"),@Reset($slab)). - Collection & string built-ins (
@Each,@Filter,@Sort,@Count,@Sum,@Avg,@Min,@Max,@Round,@Concat,@Join,@If,@Default). - Action plans (
@ToAssistant,@OpenUrl,@Run,@Set,@Reset,@Sequence).
- Reactive
- Stream Demultiplexer & Flat Symbol Extractor (
OpenUILang.demux/1,OpenUILang.parse_symbols/1): Separates conversational Markdown prose from fenced/unfencedopenui-langcode and extracts a flat symbol table with{:ref, id}and{:var, "$name"}nodes for fixed-width TUI layout engines. - Round-Trip Serializer & Prompt Generator:
OpenUILang.json_to_openui/3converts evaluated element trees back into canonicalopenui-langsource code.OpenUILang.generate_system_prompt/1generates compact LLM system prompts directly from your component library JSON schema.
Installation
Add openui_lang to your list of dependencies in mix.exs (requires zig in PATH to compile the native NIF):
def deps do
[
{:openui_lang, "~> 1.0"}
]
endQuick Start
1. Compile a Component Library Schema
Positional arguments in openui-lang map to JSON Schema properties in document key order. Use OpenUILang.decode_json!/1 to preserve property insertion order before calling OpenUILang.compile_schema/1:
schema =
File.read!("library.json")
|> OpenUILang.decode_json!()
|> OpenUILang.compile_schema()2. Parse & Evaluate an openui-lang Program
code = """
$selected_env = "prod"
root = Dashboard("Cluster Health", "hel1-dc2", [banner, kpis, controls])
banner = Alert("success", "All nodes healthy")
kpis = Row([
Stat("Requests/s", "14.2k", "+8%", Sparkline([11, 12, 13, 14], "info")),
Stat("Error Rate", "0.01%", "-0.02%", Sparkline([2, 1, 1, 0], "ok"))
])
controls = Form([
Select("Environment", $selected_env, ["prod", "staging", "dev"])
], [
Button("Inspect Environment", @ToAssistant("Show node breakdown for " + $selected_env))
])
"""
parse_result = OpenUILang.parse(code, schema)
evaluated =
OpenUILang.evaluate_result(parse_result, %{
cat: schema,
functions: %{
"Upper" => fn %{"text" => text} -> String.upcase(to_string(text || "")) end
}
})
evaluated["root"] # Evaluated element tree (%{"type" => "Dashboard", "props" => ...})
evaluated["state"] # %{"$selected_env" => "prod"}
evaluated["errors"] # []
evaluated["errorObjects"] # Structured diagnostics with source, code, component, path, and hint3. Progressive Streaming from LLM Token Chunks
sp = OpenUILang.stream_parser(schema)
{partial_parse, sp} = OpenUILang.push_chunk(sp, "root = Stack([Card(\"Hello\", ", false)
{final_parse, _sp} = OpenUILang.push_chunk(sp, "[Text(\"World\")])])\n", true)
evaluated = OpenUILang.evaluate_result(final_parse, %{cat: schema})4. Incremental Statement Patching (merge_statements/3)
When evolving a UI in chat, merge new or updated statements into the existing program while automatically pruning orphaned subtrees:
base_code = """
root = Panel("Overview", [kpis], "info")
kpis = Row([Stat("CPU", "42%")])
"""
patch_code = """
kpis = Row([Stat("CPU", "38%"), Stat("RAM", "6.4 GB")])
"""
merged = OpenUILang.merge_statements(base_code, patch_code)5. Demultiplexing Mixed Chat Prose & OpenUI Code
mixed_llm_output = """
I added a real-time memory stat card to the KPI row:
```openui
kpis = Row([Stat("CPU", "38%"), Stat("RAM", "6.4 GB")])
```
"""
%{prose: prose, code: openui_code} = OpenUILang.demux(mixed_llm_output)
symbols = OpenUILang.parse_symbols(openui_code)
# symbols["kpis"] => {"Row", [[{"Stat", ["CPU", "38%"]}, {"Stat", ["RAM", "6.4 GB"]}]]}Architecture Overview
| Module | File | Responsibility |
|---|---|---|
OpenUILang | lib/openui_lang.ex | Public facade for parsing, streaming, evaluating, merging, demuxing, and prompt generation |
OpenUILang.ZigNIF | lib/openui_lang/zig_nif.ex • native/openui_nif.zig | Native Zig NIF engine for lexing, Pratt parsing, stateful stream buffering (ZigStreamResource), AST materialization, and JSON Schema validation |
OpenUILang.AST | lib/openui_lang/ast.ex | AST node constructors and helpers |
OpenUILang.Lexer | lib/openui_lang/lexer.ex | Statement splitter, string/comment scanner, and tokenizer |
OpenUILang.Statements | lib/openui_lang/statements.ex | Top-level assignment, query, mutation, and $state declaration classifier |
OpenUILang.Expressions | lib/openui_lang/expressions.ex | Pratt expression parser for literals, operators, lambdas, components, and @ builtins/actions |
OpenUILang.Parser | lib/openui_lang/parser.ex | Batch & streaming parser orchestrator and flat symbol table extractor (parse_symbols/1) |
OpenUILang.Materialize | lib/openui_lang/materialize.ex | Reference resolution, cycle detection, orphan tracking, and schema validation |
OpenUILang.Schema | lib/openui_lang/schema.ex | Order-preserving JSON decoder (decode_json!/1), schema compiler, and positional-to-named prop mapper |
OpenUILang.Evaluator | lib/openui_lang/evaluator.ex | Runtime evaluator for $state bindings, expressions, and @Action plans |
OpenUILang.Builtins | lib/openui_lang/builtins.ex | Standard library built-in functions (@Each, @Filter, @Sort, @Sum, @Avg, @If, etc.) |
OpenUILang.Rules | lib/openui_lang/rules.ex | Form and property validation rule engine (validate_rules/2) |
OpenUILang.Merge | lib/openui_lang/merge.ex | Statement-level diff merger (merge_statements/3) with unreachable-statement garbage collection |
OpenUILang.Message | lib/openui_lang/message.ex | OpenUI message envelope builder (build_message/1), parser (parse_message/1), and prose/code demuxer (demux/1) |
OpenUILang.Prompt | lib/openui_lang/prompt.ex | Generates OpenUI 1.0 LLM system prompts from component catalog JSON specs |
OpenUILang.Serialize | lib/openui_lang/serialize.ex | Round-trip serializer (json_to_openui/3) from evaluated element trees back to openui-lang |
OpenUILang.Error | lib/openui_lang/error.ex | Structured diagnostic and error-hint builder |
Running Tests
Run the standalone test suite:
mix test
Companion Project
openui_tui: Conversational Terminal UI (TUI) Studio, Jido Agentic UI Builder, and 1Hz Live Telemetry Engine built onopenui_lang.
License
MIT