Grammar & AST
This is the normative surface reference for .fiqh. It mirrors SPEC.md, lexer.rs, parser.rs,
and ast.rs; where they disagree, the source is authoritative. Everything here is exactly what the
Playground parses.
EBNF notation: ::= definition, | alternation, ( ) grouping, * zero-or-more, + one-or-more,
? optional. Terminals in "quotes", token classes in UPPERCASE.
Lexical structure
Section titled “Lexical structure”UTF-8 source. The lexer emits a flat token stream; whitespace and comments are discarded, but every
token carries a Span { line, col } (1-based).
COMMENT ::= "//" <any except newline>* (* line *) | "/*" <any>* "*/" (* block, non-nested *)IDENT ::= [A-Za-z_] [A-Za-z0-9_]*NUMBER ::= [0-9] [0-9_]* (* '_' ignored: 10_000 == 10000; value is u64 *)STRING ::= '"' ( ESC | <any except '"' or '\'> )* '"'ESC ::= '\' ( 'n' | 't' | '"' | '\' | <any> )Punctuation / operators (longest match): { } ( ) : ; , . and == != <= >= < > = + - * / ->.
Two-character operators win over their one-character prefixes. A bare ! (not !=), any other
unexpected character, an unterminated string, or an unterminated block comment is a lexical error.
Identifiers are not reserved: section keywords (meta, parties, …) and class names are
ordinary identifiers disambiguated by grammatical position.
Grammar
Section titled “Grammar”spec ::= "instrument" IDENT ":" IDENT "{" section* "}" (* name ":" class *)
section ::= meta | parties | capital | returns | risk | oracle | zakat | contingency | dispute | invariant | rescission | lifecycle
meta ::= "meta" "{" kv* "}"risk ::= "risk" "{" kv* "}"oracle ::= "oracle" "{" kv* "}" (* consensus valuation config *)zakat ::= "zakat" "{" kv* "}" (* zakat al-tijarah layer *)contingency ::= "contingency" "{" kv* "}" (* jawa'ih abatement + faraid *)dispute ::= "dispute" "{" kv* "}"parties ::= "parties" "{" party* "}"capital ::= "capital" "{" capitem* "}"returns ::= "returns" "{" block* "}"rescission ::= "rescission" "{" block* "}"lifecycle ::= "lifecycle" "{" step* "}"invariant ::= "invariant" IDENT "{" expr "}"
kv ::= IDENT ":" expr ";"party ::= IDENT ":" IDENT IDENT* ";" (* name ":" role flag* e.g. valuer : oracle independent; *)capitem ::= IDENT ":" NUMBER "bps" ";" (* a party share *) | "require" expr ";" (* a free constraint, carried for docs *)block ::= IDENT "{" kv* "}" (* e.g. rent { … }, buyout { … }, khiyar_al_shart { … } *)step ::= IDENT ( "(" IDENT ")" )? ";" (* a lifecycle action, optional single arg *)invariant may appear many times; sections may be omitted; section order is not significant.
meta/parties/capital/risk/lifecycle are conventionally present, but their requirement is
a semantic matter, not a grammatical one.
Expressions
Section titled “Expressions”Expressions are kv values, require bodies, and invariant bodies. Precedence, loosest to
tightest (matching parser.rs):
expr ::= arrowarrow ::= cmp ( "->" cmp )* (* left-assoc; models a transfer A -> B *)cmp ::= add ( ( "==" | "!=" | "<=" | ">=" | "<" | ">" ) add )? (* NON-associative *)add ::= mul ( ( "+" | "-" ) mul )*mul ::= primary ( ( "*" | "/" ) primary )*primary ::= STRING | NUMBER UNIT? (* UNIT is an IDENT right after: 8000 bps, 1 per_bps_period *) | path | "(" expr ")"path ::= IDENT ( "." IDENT )* (* bank.share, oracle.fairValue *)cmp is deliberately non-associative — a == b == c is a parse error. Expressions are never
evaluated: the engine reasons over their structure (a path, an identifier, a comparison), not a
runtime value. That is what lets a check be a pure, deterministic statement about the contract’s
shape.
Abstract syntax (ast.rs)
Section titled “Abstract syntax (ast.rs)”Every node carries a Span.
Spec { name: String, class: String, sections: Vec<Section>, span }
enum Section { Meta(Vec<Kv>), Parties(Vec<Party>), Capital(Vec<CapItem>), Returns(Vec<RetBlock>), Risk(Vec<Kv>), Oracle(Vec<Kv>), Dispute(Vec<Kv>), Invariant(Invariant), Rescission(Vec<RescBlock>), Lifecycle(Vec<Step>),}
Kv { key: String, value: Expr, span }Party { name: String, role: String, flags: Vec<String>, span }CapItem = Share { party: String, bps: u64 } | Require(Expr)Composite contracts (bundle)
Section titled “Composite contracts (bundle)”A second top-level form models al-ʿuqūd al-murakkabah — several legs reasoned about as one graph:
bundle InahDisguised { meta { basis: "two sales composing a loan"; regime: islamic; } parties { bank: financier; customer: client; } legs { sale: murabahah { from: bank; to: customer; asset: widget; payment: deferred; price: 11000; } buyback: bay { from: customer; to: bank; asset: widget; payment: spot; price: 10000; } }}The engine builds the asset-flow graph across legs and detects cycles
(Composition). Note: bundle checking is a CLI/engine path
(deducible check); the in-browser Playground checks single instrument specs, so a
bundle there reports a parse result rather than INAH-1.
Diagnostics & semantics
Section titled “Diagnostics & semantics”After parsing, the engine resolves the regime (islamic default, or common_law) and the
rule-base, then runs the per-class invariant pass plus any opted-in section passes. See
Diagnostics for every code, and The five C’s for the
section algorithms.