Skip to content

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.

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.

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 are kv values, require bodies, and invariant bodies. Precedence, loosest to tightest (matching parser.rs):

expr ::= arrow
arrow ::= 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.

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)

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.

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.