LMCode Grammar, dsl.schema: 2 & DSLB v6 Bytecode
Synthesized from lmcode/spec/language.md, grammar.ebnf, schema.md,
and bytecode.md. Where prose and dsl.ts / dslvm.hpp ever differ,
the compiler and C++ reference verifier are authoritative.
1. EBNF Grammar Summary
chunk = { fn_decl | rule_decl | stmt } ;
fn_decl = "fn" IDENT "(" [ param_list ] ")" "->" TYPE block ;
rule_decl = "@" IDENT "fn" IDENT "(" [ param_list ] ")" "->" TYPE "implements" IDENT block ;
param_list = param { "," param } ;
param = IDENT ":" TYPE ;
block = "{" { stmt } "}" ;
stmt = let_stmt | assign_stmt | return_write | if_stmt | switch_stmt
| for_range_stmt | for_each_stmt | while_stmt
| "break" ";" | "continue" ";" | "return" [ expr ] ";" | expr ";" ;
let_stmt = "let" IDENT "=" expr ";" ;
assign_stmt = IDENT "=" expr ";" ;
return_write = "$return" [ "." IDENT | "[" expr "]" ] "=" ( expr | "null" ) ";" ;
if_stmt = "if" "(" expr ")" block [ "else" ( if_stmt | block ) ] ;
switch_stmt = "switch" "(" expr ")" "{" { switch_case } [ "default" ":" block ] "}" ;
switch_case = "case" case_const ":" block ;
for_range_stmt = "for" "(" IDENT "in" "range" "(" expr "," expr ")" ")" block ;
for_each_stmt = "for" "(" IDENT "in" expr ")" block ;
while_stmt = "while" "(" expr ")" block ;
2. Operator Precedence & Type Rules
| Level | Operators | Static Type Rule & Semantics |
|---|---|---|
| 1 (loosest) | a || b | Both bool; short-circuit evaluation. |
| 2 | a && b | Both bool; short-circuit evaluation. |
| 3 | ==, != | Same primitive type or same enum type; host objects cannot be compared. |
| 4 | <, <=, >, >= | Both int or both float; returns bool. |
| 5 | +, - | Both int, both float, two strings (+ concatenates), or schema-mapped object operator (+, -, r+, r-). |
| 6 | *, /, % | Both int or both float (% is two ints only), or schema-mapped * / r*. |
| 7 | !a, -a | ! requires bool; unary - requires int, float, or schema-mapped neg. |
| 8 | obj.method(args) | Postfix method call chain; resolved against effective schema. |
| 9 (tightest) | Primary | Literals, locals, context objects, fn(args), built-ins (float, int, str, math), qw/qi/qf, %(...), json <<~TAG, (expr). |
3. Host Schema Format (dsl.schema: 2)
Every host supplies a JSON document with "dsl.schema": 2 and a valid identifier "name".
Before typechecking, parseDslSchema retains the exact raw bytes as schema.bytes
(for the SHA-256 header hash) and merges the bundled lmcode_std_v3 catalog into the effective schema.
{
"dsl.schema": 2,
"name": "sample_shop_v1",
"types": {
"Money": {
"operators": { "+": "plus", "-": "minus", "*": "times", "r*": "times", "neg": "neg" },
"methods": {
"cents": [{ "params": [], "returns": "int" }],
"plus": [{ "params": ["Money"], "returns": "Money" }],
"minus": [{ "params": ["Money"], "returns": "Money" }],
"times": [{ "params": ["int"], "returns": "Money" }],
"neg": [{ "params": [], "returns": "Money" }]
}
},
"Shop": {
"methods": {
"add": [{ "params": ["string", "Money"], "returns": "self" }],
"total": [{ "params": [], "returns": "Money" }]
}
},
"Ledger": {
"methods": {
"record": [{ "params": ["Money"], "returns": "void" }]
}
}
},
"functions": {
"money": [{ "params": ["int"], "returns": "Money" }, { "params": ["float"], "returns": "Money" }]
},
"context": { "shop": "Shop", "ledger": "Ledger" },
"enums": { "Currency": ["USD", "EUR", "JPY"] },
"conversions": [{ "from": "int", "to": "Money", "via": "money" }],
"interfaces": {
"Adjuster": { "params": [{ "name": "m", "type": "Money" }, { "name": "qty", "type": "int" }], "returns": "Money" },
"Score": { "params": [{ "name": "m", "type": "Money" }], "returns": "int" }
},
"result": { "type": "Json", "nullable": true }
}
4. DSLB v6 Binary File Layout
All multi-byte integers are big-endian with zero padding. The file must end immediately after the last entry (trailing bytes or truncated reads are rejected at load time).
| Section | Binary Layout | Verifier Invariant |
|---|---|---|
| 1. Header (38 B) | u8[4] "DSLB", u16 version (6), u8[32] sha256(schema.bytes) | Magic must be DSLB, version must be 6, SHA-256 must match host's raw schema bytes. |
| 2. Constants | u32 count, then per entry: u8 tag (1=string), u32 byte_len, u8[byte_len] | Shared across body and all rule entries; only tag 1 is valid. |
| 3. Imports | u16 count, then u32 const_idx per import | Each constant is a canonical signature (fn NAME(...) -> R or method Type.name(...) -> R) bound at load time. |
| 4. Context | u16 count, then (u32 name_const, u32 type_const) | Every context variable and object type must match the host registry. |
| 5. Body Region | locals + u16 max_stack + u32 code_len + code + line_table | Code must end with HALT (0x11) at loop depth 0; line table sorted by pc. |
| 6. Rule Entries | u16 entry_count, then per entry: u32 name, u32 annotation, u32 interface, u16 nparams, region | Unique entry names; parameter locals 0..nparams-1 match interface types; HALT leaves 0 (void) or 1 value. |
5. Complete DSLB v6 Instruction Set (0x01..0x36)
| Hex | Mnemonic | Operands | Stack Delta | Semantics |
|---|---|---|---|---|
0x01 | PUSH_INT | i64 (8B) | +1 | Push 64-bit two's-complement integer constant. |
0x02 | PUSH_STR | u32 const (4B) | +1 | Push string constant from pool. |
0x03 | PUSH_BOOL | u8 0|1 (1B) | +1 | Push boolean constant. |
0x04 | LOAD_LOCAL | u16 slot (2B) | +1 | Push local slot value (error if read before store). |
0x05 | STORE_LOCAL | u16 slot (2B) | -1 | Pop top of stack into local slot (forbidden on hidden loop slots). |
0x06 | LOAD_CTX | u16 ctx (2B) | +1 | Push host-bound context object handle. |
0x07 | CALL_FN | u16 imp, u8 argc (3B) | -argc + R | Invoke resolved host function import. |
0x08 | CALL_METHOD | u16 imp, u8 argc (3B) | -(argc+1) + R | Invoke resolved method import on receiver below args. |
0x09 / 0x0A | EQ / NE | 0B | -1 | Same-type primitive/enum equality or inequality. |
0x0B | NOT | 0B | 0 | Boolean negation. |
0x0C | JUMP | i32 rel (4B) | 0 | Unconditional forward jump. |
0x0D / 0x0E | JUMP_IF_FALSE / TRUE | i32 rel (4B) | -1 | Pop bool and jump forward if false / true. |
0x0F / 0x10 | DUP / POP | 0B | +1 / -1 | Duplicate or discard top stack value. |
0x11 | HALT | 0B | 0 | Terminate execution of body or entry region. |
0x12 | PUSH_FLOAT | f64 (8B) | +1 | Push IEEE-754 binary64 constant. |
0x13..0x16 | ADD_I / SUB_I / MUL_I / DIV_I | 0B | -1 | Wrapping int64 arithmetic; DIV_I truncates toward zero (0 divisor errors). |
0x17..0x1A | ADD_F / SUB_F / MUL_F / DIV_F | 0B | -1 | IEEE-754 binary64 arithmetic. |
0x1B / 0x1C | NEG_I / NEG_F | 0B | 0 | Negate integer or float. |
0x1D..0x20 | LT_I / LE_I / GT_I / GE_I | 0B | -1 | Signed int64 ordering comparisons. |
0x21..0x24 | LT_F / LE_F / GT_F / GE_F | 0B | -1 | IEEE binary64 ordering comparisons. |
0x25 | I2F | 0B | 0 | Widen int to float (float(i)). |
0x26 | CONCAT | 0B | -1 | Concatenate two byte strings. |
0x27..0x29 | I2S / F2S / B2S | 0B | 0 | Convert int, float (%.15g), or bool to string. |
0x2F | FOR_RANGE | u16 cnt, u16 end, i32 exit (8B) | -2 | Pop end & start into hidden int slots; jump forward to exit if start >= end. |
0x31 | FOR_NEXT | u16 end, i32 back (6B) | 0 | Increment paired counter and branch back while counter < end. |
0x32 | MATH | u8 fn_id (1B) | 1 - argc | Built-in math operation (IDs 0..17 below). |
0x33 | MOD_I | 0B | -1 | Signed int64 remainder (dividend's sign). |
0x34 | WHILE_BEGIN | 0B | 0 | Marks structured while-loop head before condition evaluation. |
0x35 | WHILE_TEST | i32 exit (4B) | -1 | Pop bool and jump forward past paired LOOP_BACK when false. |
0x36 | LOOP_BACK | i32 back (4B) | 0 | The only general backward branch; targets paired WHILE_BEGIN. |
6. Built-In MATH Opcode IDs (0x32)
0: sin(f), 1: cos(f), 2: sqrt(f), 3: abs(f), 4: floor(f),
5: pow(f,f), 6: min(f,f), 7: max(f,f), 8: clamp(f,f,f),
9: lerp(f,f,f), 10: ease(f), 11: ease_in(f), 12: ease_out(f),
13: int(f)->i, 14: abs(i), 15: min(i,i), 16: max(i,i), 17: clamp(i,i,i).
7. Loader Error Codes
| Error Code | Condition |
|---|---|
LOAD_BAD_BYTECODE | Malformed header/section, unknown opcode, backward jump, unpaired loop, hidden-slot write, stack depth mismatch, or another verifier failure. |
LOAD_SCHEMA_MISMATCH | Bytecode header SHA-256 does not match the SHA-256 of the host's raw schema bytes. |
LOAD_UNBOUND | An imported function/method signature or context variable is not registered by the host. |