7.0 KiB
PBS v0 Canonical Addenda
Purpose: eliminate ambiguity for Junie and for golden tests.
This document is normative for PBS Frontend v0 and complements:
- PBS Frontend Spec v0 — Implementer Edition
- Junie PR Plan
These addenda define:
- operator precedence & associativity
- canonical AST JSON shape (v0)
- canonical diagnostic codes (v0)
1) Operator Precedence and Associativity (v0)
1.1 Guiding rule
PBS v0 prioritizes minimal ambiguity and easy parsing.
- Most operators are left-associative.
- Assignment is not an expression in v0 (no
=operator expressions). - Member access and indexing are not part of v0 surface syntax unless already defined elsewhere.
1.2 Precedence table
From highest to lowest:
-
Primary
- literals (
10,3.14,"text",none,some(x),ok(x),err(e)) - identifiers (
foo) - parenthesized expression (
(expr)) - block expression (
{ ... }) (when allowed asExpr)
- literals (
-
Call (left-associative)
callee(arg1, arg2, ...)
-
Unary prefix (right-associative)
-expr!exprascasts are not unary; see level 6.
-
Multiplicative (left-associative)
*,/,%
-
Additive (left-associative)
+,-
-
Cast (left-associative)
expr as Type
-
Comparison (non-associative)
<,<=,>,>=
-
Equality (non-associative)
==,!=
-
Logical AND (left-associative)
&&
-
Logical OR (left-associative)
||
-
Control expressions (special)
if ... { ... } else { ... }(expression form only if your v0 allows it; otherwise statement)when { ... }(expression)
1.3 Non-associative rule
Comparison and equality are non-associative:
a < b < cis an errora == b == cis an error
Diagnostic: E_PARSE_NON_ASSOC.
1.4 Notes on when
when binds weaker than all binary operators.
Example:
let x = a + b when { ... };
Parses as:
let x = (a + b) when { ... };
2) Canonical AST JSON (v0)
2.1 Canonicalization goals
Canonical AST JSON is used for:
- golden tests
- frontend determinism validation
- diff-friendly debugging
Rules:
- JSON keys are stable and ordered (when writing JSON)
- All nodes include
kindandspan - Spans are byte offsets into the file content
2.2 Span encoding
{"file":"main.pbs","start":12,"end":18}
Where:
startis inclusiveendis exclusive
2.3 Root
{
"kind": "File",
"span": {"file":"...","start":0,"end":123},
"imports": [ ... ],
"decls": [ ... ]
}
2.4 Import node
{
"kind": "Import",
"span": {"file":"...","start":0,"end":20},
"spec": {"kind":"ImportSpec","path":["Foo","Bar"]},
"from": "./lib.pbs"
}
ImportSpec.path is an array of identifiers.
2.5 Declarations
2.5.1 Service
{
"kind": "ServiceDecl",
"span": {"file":"...","start":0,"end":50},
"vis": "pub",
"name": "Audio",
"extends": null,
"members": [ ... ]
}
A service member (method signature only in v0):
{
"kind": "ServiceFnSig",
"span": {"file":"...","start":0,"end":10},
"name": "play",
"params": [ {"name":"sound","ty": {"kind":"TypeName","name":"Sound"}} ],
"ret": {"kind":"TypeName","name":"void"}
}
2.5.2 Function
{
"kind": "FnDecl",
"span": {"file":"...","start":0,"end":80},
"name": "main",
"params": [],
"ret": null,
"else": null,
"body": {"kind":"Block", ... }
}
2.5.3 TypeDecl (struct/contract/error)
{
"kind": "TypeDecl",
"span": {"file":"...","start":0,"end":100},
"vis": "pub",
"typeKind": "struct",
"name": "Vector",
"body": {"kind":"TypeBody","members":[ ... ]}
}
2.6 Blocks and statements
Block:
{
"kind": "Block",
"span": {"file":"...","start":0,"end":20},
"stmts": [ ... ],
"tail": null
}
stmtsare statements.tailis an optional final expression (only if your parser supports expression blocks).
Statement kinds (v0 minimum):
LetStmtExprStmtReturnStmt
Let:
{
"kind": "LetStmt",
"span": {"file":"...","start":0,"end":20},
"name": "x",
"isMut": false,
"ty": null,
"init": {"kind":"IntLit", "value": 10, "span": ...}
}
Return:
{
"kind": "ReturnStmt",
"span": {"file":"...","start":0,"end":10},
"expr": null
}
2.7 Expressions
All expressions include kind and span.
Minimal v0 expression node kinds:
IntLit{ value: i64 }FloatLit{ value: f64 }BoundedLit{ value: u32 }StringLit{ value: string }Ident{ name: string }Call{ callee: Expr, args: Expr[] }Unary{ op: "-"|"!", expr: Expr }Binary{ op: string, left: Expr, right: Expr }Cast{ expr: Expr, ty: TypeRef }IfExpr{ cond: Expr, then: Block, els: Block }(if expression is supported)WhenExpr{ arms: WhenArm[] }
Type references:
{"kind":"TypeName","name":"int"}
Generics:
{"kind":"TypeApp","base":"optional","args":[{"kind":"TypeName","name":"int"}]}
2.8 Canonical JSON ordering
When writing AST JSON, always order fields as:
kindspan- semantic fields (stable order)
This makes diffs deterministic.
3) Diagnostic Codes (v0)
3.1 Diagnostic format
All diagnostics must be serializable to canonical JSON:
{
"severity": "error",
"code": "E_PARSE_UNEXPECTED_TOKEN",
"message": "Unexpected token '}'",
"span": {"file":"main.pbs","start":12,"end":13}
}
Severity is one of: error, warning.
3.2 Parse/Lex errors (E_PARSE_*)
E_LEX_INVALID_CHAR— invalid characterE_LEX_UNTERMINATED_STRING— string literal not closedE_PARSE_UNEXPECTED_TOKEN— token not expected in current contextE_PARSE_EXPECTED_TOKEN— missing required tokenE_PARSE_NON_ASSOC— chained comparison/equality (non-associative)
3.3 Symbol/Resolve errors (E_RESOLVE_*)
E_RESOLVE_UNDEFINED— undefined identifierE_RESOLVE_DUPLICATE_SYMBOL— duplicate symbol in same namespaceE_RESOLVE_NAMESPACE_COLLISION— name exists in both type and value namespacesE_RESOLVE_VISIBILITY— symbol not visible from this scope/moduleE_RESOLVE_INVALID_IMPORT— import spec/path invalid
3.4 Type errors (E_TYPE_*)
E_TYPE_MISMATCH— type mismatchE_TYPE_UNKNOWN_TYPE— unknown type nameE_TYPE_MUTABILITY— mutability violationE_TYPE_RETURN_PATH— not all paths return a valueE_TYPE_INVALID_CAST— invalid cast
3.5 Lowering errors (E_LOWER_*)
E_LOWER_UNSUPPORTED— feature not supported in v0 lowering
3.6 Warnings (W_*)
Warnings are allowed in v0 but should be used sparingly.
W_UNUSED_LET— unused local bindingW_SHADOWING— local shadows another binding
Implementation Notes (Non-normative)
- Keep these addenda in
spec/in the repo. - Use them to drive golden tests for AST and diagnostics.
- If a future change alters canonical AST, bump a version and regenerate goldens deliberately.