Grammar
The grammar is expressed in EBNF. Newlines between statements are implicit separators
(the parser skips them with skipNewlines()). Semicolons are accepted as alternate
separators in statement-list contexts only. The consume() helper also skips newlines
before checking the expected token.
Top-level
program ::= top_level_list
top_level_list ::= (NEWLINE)* [top_level (top_level_sep top_level)* [top_level_sep]] (NEWLINE)*
top_level_sep ::= NEWLINE+ | ';' NEWLINE*
top_level ::= import_decl
| attributed_decl
| pipeline_decl
| statement
attributed_decl ::= attribute+ (pipeline_decl | fn_decl | tool_decl
| skill_decl | eval_pack_decl | struct_decl
| enum_decl | type_decl | interface_decl
| impl_block)
attribute ::= '@' attr_name ['(' attr_arg (',' attr_arg)* [','] ')']
attr_arg ::= [attr_name ':'] attr_value
attr_name ::= IDENTIFIER | 'retry'
attr_value ::= STRING_LITERAL | RAW_STRING | INT_LITERAL
| FLOAT_LITERAL | 'true' | 'false' | 'nil'
| IDENTIFIER | '-' INT_LITERAL | '-' FLOAT_LITERAL
import_decl ::= ['pub'] 'import' STRING_LITERAL
| ['pub'] 'import' '*' 'as' IDENTIFIER 'from' STRING_LITERAL
| ['pub'] 'import' '{' IDENTIFIER (',' IDENTIFIER)* '}'
'from' STRING_LITERAL
| ['pub'] 'import' IDENTIFIER ('::' IDENTIFIER)* '::'
'{' IDENTIFIER (',' IDENTIFIER)* '}'
pipeline_decl ::= ['pub'] 'pipeline' IDENTIFIER '(' param_list ')'
['->' type_expr]
['extends' IDENTIFIER] '{' block '}'
param_list ::= (IDENTIFIER (',' IDENTIFIER)*)?
block ::= statement_list
statement_list ::= (NEWLINE)* [statement (statement_sep statement)* [statement_sep]] (NEWLINE)*
statement_sep ::= NEWLINE+ | ';' NEWLINE*
fn_decl ::= ['pub'] 'fn' IDENTIFIER [generic_params]
'(' fn_param_list ')' ['->' type_expr]
[where_clause] '{' block '}'
where_clause ::= 'where' where_bound (',' where_bound)*
where_bound ::= IDENTIFIER ':' type_expr ('+' type_expr)*
type_decl ::= ['pub'] 'type' IDENTIFIER [generic_params] '=' type_expr
enum_decl ::= ['pub'] 'enum' IDENTIFIER [generic_params] '{'
(enum_variant | ',' | NEWLINE)* '}'
enum_variant ::= IDENTIFIER ['(' fn_param_list ')']
struct_decl ::= ['pub'] 'struct' IDENTIFIER [generic_params]
'{' struct_field* '}'
struct_field ::= IDENTIFIER ['?'] ':' type_expr
impl_block ::= 'impl' IDENTIFIER '{' (fn_decl | NEWLINE)* '}'
interface_decl ::= 'interface' IDENTIFIER [generic_params] '{'
(interface_assoc_type | interface_method)* '}'
interface_assoc_type ::= 'type' IDENTIFIER ['=' type_expr]
interface_method ::= 'fn' IDENTIFIER [generic_params]
'(' fn_param_list ')' ['->' type_expr]
Standard library modules
Imports starting with std/ load embedded stdlib modules:
import "std/text"— text processing (extract_paths, parse_cells, filter_test_cells, truncate_head_tail, detect_compile_error, has_got_want, format_test_errors, int_to_string, float_to_string, parse_int_or, parse_float_or, truncate_text, truncate_middle, single_line_or, prefix_lines, indent)import "std/ansi"— terminal styling helpers (ansi_enabled, ansi_style, ansi_color, ansi_strip, ansi_visible_len, ansi_link, ansi_success, ansi_warn, ansi_error)import "std/table"— deterministic plain-text and Markdown table rendering (render_table, render_markdown_table, render_kv_table)import "std/diff"— line diff, unified diff, colored diff, diff stat, and host-backed structural review helpers (diff_lines, unified_diff, colorize_diff, diff_summary, render_diff_stat, structural_diff)import "std/edit"— pure old/new text patch helpers (edit_apply_old_new_patch, edit_splice_lines, edit_changed_regions, edit_validate_changed_regions)import "std/artifact/web"— safe helpers for small generated HTML/CSS/JS artifacts (web_artifact_extract, web_artifact_text_fallback, web_artifact_validate, web_artifact_apply_patch)import "std/ui_resource"— MCP Apps-compatibleui://resource envelopes, text/structured fallbacks, host capability negotiation, and message envelopes (ui_resource, ui_tool_meta, ui_tool_meta_to_mcp, ui_text_fallback, ui_structured_fallback, ui_tool_result, ui_tool_result_validate, ui_host_capabilities, ui_host_supports_apps, ui_select_for_host, ui_tool_call_envelope, ui_context_update_envelope, ui_resource_csp_header, ui_resource_sandbox_attr)import "std/tui"— terminal presentation helpers (page, terminal_width, rule, clear)import "std/collections"— collection utilities (filter_nil, store_stale, store_refresh)import "std/slug"— memorable non-secret names and slug helpers (random_slug, slug_from, deterministic_slug, slug, slugify)import "std/fs"— file-system convenience helpers built on host primitives (ensure_parent_dir, read_json, read_json_result, read_json_typed, read_json_typed_result, write_json, read_yaml, write_yaml, read_toml, write_toml, write_lines, append_line, touch, find_files, relative_path, is_file, is_dir, file_size)import "std/os"— environment and host diagnostic helpers (os_info, env_bool, env_int, env_list, require_env, which, command_exists)import "std/gha"— GitHub Actions workflow command helpers (gha_escape_data, gha_annotation, gha_notice, gha_warning, gha_error, gha_env_block, gha_write_output, gha_write_env, gha_append_summary)import "std/async"— polling and retry helpers (wait_for, retry_until, retry_predicate_with_backoff, circuit_call)import "std/signal"— cooperative process interruption helpers (on_interrupt, off_interrupt, interrupted, with_interrupt)import "std/vision"— deterministic OCR helpers (ocr(image, options?))import "std/web"— deterministic web-source and grounding helpers (web_fetch, web_search, verify_imports, web_grounding_tools, web_parse_html, robots_allowed, sitemap_urls)import "std/io"— terminal IO helpers (is_tty, read_line, read_password, write_stderr)import "std/prompt_library"— reusable prompt fragments, cache metadata, tenant-scoped k-means hotspot proposals, and review-queue recordsimport "std/agent_state"— durable session-scoped state helpers (agent_state_init, agent_state_resume, agent_state_write, agent_state_read, agent_state_list, agent_state_delete, agent_state_handoff)import "std/agent/progress"— agent progress narration and task-list helpers (agent_progress, agent_progress_tool)import "std/agent/scratchpad"— live session-local agent working memory helpers (agent_scratchpad_options, agent_scratchpad_init, agent_scratchpad_recitation_fragment, agent_scratchpad_reorganize, agent_scratchpad_reorganize_if_due)import "std/agent/fact"— typed fact envelopes over durable memory (store_fact, recall_facts, invalidate_facts)import "std/agent/probe"— probe-first verification primitive: run a snippet (eval/typecheck) and record the outcome as an Observation fact (probe, probe_eval, probe_typecheck)import "std/memory"— append-only durable memory helpers (memory_store, memory_recall, memory_summarize, memory_forget)import "std/trust"— TrustGraph query and policy helpers (query, record, score, policy_for, verify_chain)import "std/corrections"— replay-for-teaching correction records (query, record)import "std/postgres"— Postgres persistence helpers (pg_pool, pg_connect, pg_query, pg_query_one, pg_execute, pg_transaction, pg_close, pg_stmt_cache_clear, pg_mock_pool, pg_mock_calls)import "std/llm/handlers"— LLM call-handler middleware (with_circuit_breaker)import "std/llm/prompts"— deterministic prompt builders and system prompt fragment helpers (system_prompt_part, system_before, system_after, with_system_fragments, system_prelude)import "std/personas/prelude"— reusable persona orchestration helpers (verify_then_act, bounded_loop, cheap_classify_then_escalate, parallel_sweep_with_circuit_breaker, with_audit_receipt, with_approval_gate)import "std/personas/bulletins"— transparent profile bulletin proposals and host-owned decisions (bulletin_propose, bulletin_emit, bulletin_accept, bulletin_reject, bulletin_expire, bulletin_supersede, bulletin_decide, bulletin_apply_decisions, bulletin_partition, bulletin_active, bulletin_render_for_prompt, bulletin_dedupe)
These modules are compiled into the interpreter binary and require no filesystem access.
Statements
statement ::= let_binding
| const_binding
| var_binding
| if_else
| for_in
| match_expr
| while_loop
| retry_block
| parallel_block
| parallel_each
| parallel_settle
| defer_block
| return_stmt
| throw_stmt
| override_decl
| try_catch
| fn_decl
| enum_decl
| struct_decl
| impl_block
| interface_decl
| type_decl
| guard_stmt
| require_stmt
| deadline_block
| mutex_block
| block_statement
| select_expr
| break_stmt
| continue_stmt
| expression_statement
let_binding ::= 'let' binding_pattern [':' type_expr] '=' expression
const_binding ::= 'const' IDENTIFIER [':' type_expr] '=' expression
var_binding ::= 'var' binding_pattern [':' type_expr] '=' expression
if_else ::= 'if' expression '{' block '}'
['else' (if_else | '{' block '}')]
for_in ::= 'for' binding_pattern 'in' expression '{' block '}'
match_expr ::= 'match' expression '{' match_arm* '}'
match_arm ::= expression ['if' expression] '->' '{' block '}'
while_loop ::= 'while' expression '{' block '}'
retry_block ::= 'retry' ['(' expression ')'] expression? '{' block '}'
parallel_block ::= 'parallel' '(' expression ')' [parallel_options] '{' [IDENTIFIER '->'] block '}'
parallel_each ::= 'parallel' 'each' expression [parallel_options] '{' IDENTIFIER '->' block '}'
parallel_settle ::= 'parallel' 'settle' expression [parallel_options] '{' IDENTIFIER '->' block '}'
parallel_options ::= 'with' '{' 'max_concurrent' ':' expression '}'
defer_block ::= 'defer' '{' block '}'
return_stmt ::= 'return' [expression]
throw_stmt ::= 'throw' expression
override_decl ::= 'override' IDENTIFIER '(' param_list ')' '{' block '}'
try_catch ::= 'try' '{' block '}'
['catch' [('(' IDENTIFIER [':' type_expr] ')') | IDENTIFIER]
'{' block '}']
['finally' '{' block '}']
try_star_expr ::= 'try' '*' unary_expr
guard_stmt ::= 'guard' expression 'else' '{' block '}'
require_stmt ::= 'require' expression [',' expression]
deadline_block ::= 'deadline' primary '{' block '}'
mutex_block ::= 'mutex' '{' block '}'
block_statement ::= 'block' '{' block '}'
select_expr ::= 'select' '{'
(IDENTIFIER 'from' expression '{' block '}'
| 'timeout' expression '{' block '}'
| 'default' '{' block '}')+
'}'
break_stmt ::= 'break'
continue_stmt ::= 'continue'
generic_params ::= '<' generic_param (',' generic_param)* '>'
generic_param ::= ['in' | 'out'] IDENTIFIER
where_clause ::= 'where' where_bound (',' where_bound)*
where_bound ::= IDENTIFIER ':' IDENTIFIER ('+' IDENTIFIER)*
fn_param_list ::= (fn_param (',' fn_param)*)? [',' rest_param]
| rest_param
fn_param ::= IDENTIFIER [':' type_expr] ['=' expression]
rest_param ::= '...' IDENTIFIER
type_expr ::= IDENTIFIER
| IDENTIFIER '<' type_expr (',' type_expr)* '>'
| '[' type_expr ']'
| 'fn' '(' [type_expr (',' type_expr)*] ')' '->' type_expr
| '{' shape_field (',' shape_field)* '}'
| type_expr '|' type_expr
| type_expr '?'
| STRING_LITERAL
| INT_LITERAL
Postfix `?` is sugar for `T | nil`: `int?` is identical to `int | nil`,
including narrowing rules. `?` binds tighter than `&` and `|`, so
`A & B?` parses as `A & (B | nil)` and `A | B?` flattens to
`A | B | nil`. Note that `??` is the nil-coalescing operator and is
lexed as a single token, so writing two `?`s in a row never stacks
optionals; redundancies in the surface form (`T? | nil`) are
deduplicated to `T | nil` during parsing. The formatter and the
`prefer-optional-shorthand` lint rule rewrite the explicit
`T | nil` form into `T?` whenever the inner type prints as a type
primary (i.e. it isn't itself a union, intersection, or `fn(...)
-> ...` type).
A rest parameter (`...name`) must be the last parameter in the list. At call
time, any arguments beyond the positional parameters are collected into a list
and bound to the rest parameter name. If no extra arguments are provided, the
rest parameter is an empty list. A type annotation on a rest parameter describes
each extra argument, and the binding inside the function has the corresponding
list type: `...nums: int` accepts only integer extras and binds `nums` as
`list<int>`.
```harn
fn sum(...nums) {
let total = 0
for n in nums {
total = total + n
}
return total
}
sum(1, 2, 3) // 6
fn log(level, ...parts) {
log("[${level}] ${join(parts, " ")}")
}
log("INFO", "server", "started") // [INFO] server started
expression_statement ::= expression
| assignable '=' expression
| assignable ('+=' | '-=' | '*=' | '/=' | '%=') expression
assignable ::= IDENTIFIER
| postfix_property
| postfix_subscript
binding_pattern ::= IDENTIFIER
| '{' dict_pattern_fields '}'
| '[' list_pattern_elements ']'
dict_pattern_fields ::= dict_pattern_field (',' dict_pattern_field)*
dict_pattern_field ::= '...' IDENTIFIER
| IDENTIFIER [':' IDENTIFIER]
list_pattern_elements ::= list_pattern_element (',' list_pattern_element)*
list_pattern_element ::= '...' IDENTIFIER
| IDENTIFIER
The expression_statement rule handles both bare expressions (function calls, method calls)
and assignments. An assignment is recognized when the left-hand side is an identifier
followed by =.
block { ... } introduces an explicit lexical scope. The block word is
contextual: it remains available as an identifier except when immediately followed by {
at statement position. Bare { ... } is not a block statement because braces already
introduce dict literals in expression position, including dict-valued function tails.
Expressions (by precedence, lowest to highest)
expression ::= pipe_expr
pipe_expr ::= range_expr ('|>' range_expr)*
range_expr ::= ternary_expr ['to' ternary_expr ['exclusive']]
ternary_expr ::= logical_or ['?' ternary_expr ':' ternary_expr]
logical_or ::= logical_and ('||' logical_and)*
logical_and ::= equality ('&&' equality)*
equality ::= comparison (('==' | '!=') comparison)*
comparison ::= additive
(('<' | '>' | '<=' | '>=' | 'in' | 'not in') additive)*
additive ::= nil_coal_expr (('+' | '-') nil_coal_expr)*
nil_coal_expr ::= multiplicative ('??' multiplicative)*
multiplicative ::= unary (('*' | '/' | '%') unary)*
unary ::= ('!' | '-') unary | power_expr
power_expr ::= postfix ['**' unary]
postfix ::= primary (member_access
| optional_member_access
| subscript_access
| optional_subscript_access
| slice_access
| call
| try_unwrap)*
member_access ::= '.' IDENTIFIER ['(' arg_list ')']
optional_member_access
::= '?.' IDENTIFIER ['(' arg_list ')']
subscript_access ::= '[' expression ']'
optional_subscript_access
::= '?.[' expression ']'
| '?[' expression ']' (* legacy spelling *)
slice_access ::= '[' [expression] ':' [expression] ']'
call ::= '(' arg_list ')'
| type_args '(' arg_list ')'
(* type_args require an identifier base *)
type_args ::= '<' type_expr (',' type_expr)* '>'
try_unwrap ::= '?' (* expr? on Result *)
A non-generic call may follow any value-producing postfix expression, so
factory(x)(y) calls the value returned by factory(x). The opening ( must
be on the same source line as the callee; const f = factory\n(y) remains two
statements. Generic type arguments remain available only on identifier calls.
Primary expressions
primary ::= STRING_LITERAL
| INTERPOLATED_STRING
| INT_LITERAL
| FLOAT_LITERAL
| DURATION_LITERAL
| 'true' | 'false' | 'nil'
| IDENTIFIER
| '(' expression ')'
| list_literal
| dict_or_closure
| parallel_block
| parallel_each
| parallel_settle
| retry_block
| if_else
| match_expr
| deadline_block
| 'spawn' '{' block '}'
| 'fn' '(' fn_param_list ')' '{' block '}'
| 'try' '{' block '}'
```text
list_literal ::= '[' (list_element (',' list_element)*)? ']'
list_element ::= '...' expression | expression
dict_or_closure ::= '{' '}'
| '{' closure_param_list '->' block '}'
| '{' dict_entries '}'
closure_param_list ::= fn_param_list
dict_entries ::= dict_entry (',' dict_entry)*
dict_entry ::= (IDENTIFIER | STRING_LITERAL | '[' expression ']')
':' expression
| '...' expression
arg_list ::= (arg_element (',' arg_element)*)?
arg_element ::= '...' expression | expression
Dict keys written as bare identifiers are converted to string literals
(e.g., {name: "x"} becomes {"name": "x"}). String-literal keys may contain
dots or other non-identifier characters; the formatter keeps identifier string
keys bare and keeps non-identifier string keys quoted (for example,
{"a.b.c": "x", k: "y"}). Computed keys use bracket syntax: {[expr]: value}.