# 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: > > 1. operator precedence & associativity > 2. canonical AST JSON shape (v0) > 3. 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**: 1. **Primary** * literals (`10`, `3.14`, `"text"`, `none`, `some(x)`, `ok(x)`, `err(e)`) * identifiers (`foo`) * parenthesized expression (`(expr)`) * block expression (`{ ... }`) (when allowed as `Expr`) 2. **Call** (left-associative) * `callee(arg1, arg2, ...)` 3. **Unary prefix** (right-associative) * `-expr` * `!expr` * `as` casts are **not unary**; see level 6. 4. **Multiplicative** (left-associative) * `*`, `/`, `%` 5. **Additive** (left-associative) * `+`, `-` 6. **Cast** (left-associative) * `expr as Type` 7. **Comparison** (non-associative) * `<`, `<=`, `>`, `>=` 8. **Equality** (non-associative) * `==`, `!=` 9. **Logical AND** (left-associative) * `&&` 10. **Logical OR** (left-associative) * `||` 11. **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 < c` is an error * `a == b == c` is an error Diagnostic: `E_PARSE_NON_ASSOC`. ### 1.4 Notes on `when` `when` binds weaker than all binary operators. Example: ```pbs 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 `kind` and `span` * Spans are byte offsets into the file content ### 2.2 Span encoding ```json {"file":"main.pbs","start":12,"end":18} ``` Where: * `start` is inclusive * `end` is exclusive ### 2.3 Root ```json { "kind": "File", "span": {"file":"...","start":0,"end":123}, "imports": [ ... ], "decls": [ ... ] } ``` ### 2.4 Import node ```json { "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 ```json { "kind": "ServiceDecl", "span": {"file":"...","start":0,"end":50}, "vis": "pub", "name": "Audio", "extends": null, "members": [ ... ] } ``` A service member (method signature only in v0): ```json { "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 ```json { "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) ```json { "kind": "TypeDecl", "span": {"file":"...","start":0,"end":100}, "vis": "pub", "typeKind": "struct", "name": "Vector", "body": {"kind":"TypeBody","members":[ ... ]} } ``` ### 2.6 Blocks and statements Block: ```json { "kind": "Block", "span": {"file":"...","start":0,"end":20}, "stmts": [ ... ], "tail": null } ``` * `stmts` are statements. * `tail` is an optional final expression (only if your parser supports expression blocks). Statement kinds (v0 minimum): * `LetStmt` * `ExprStmt` * `ReturnStmt` Let: ```json { "kind": "LetStmt", "span": {"file":"...","start":0,"end":20}, "name": "x", "isMut": false, "ty": null, "init": {"kind":"IntLit", "value": 10, "span": ...} } ``` Return: ```json { "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: ```json {"kind":"TypeName","name":"int"} ``` Generics: ```json {"kind":"TypeApp","base":"optional","args":[{"kind":"TypeName","name":"int"}]} ``` ### 2.8 Canonical JSON ordering When writing AST JSON, always order fields as: 1. `kind` 2. `span` 3. semantic fields (stable order) This makes diffs deterministic. --- ## 3) Diagnostic Codes (v0) ### 3.1 Diagnostic format All diagnostics must be serializable to canonical JSON: ```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 character * `E_LEX_UNTERMINATED_STRING` — string literal not closed * `E_PARSE_UNEXPECTED_TOKEN` — token not expected in current context * `E_PARSE_EXPECTED_TOKEN` — missing required token * `E_PARSE_NON_ASSOC` — chained comparison/equality (non-associative) ### 3.3 Symbol/Resolve errors (E_RESOLVE_*) * `E_RESOLVE_UNDEFINED` — undefined identifier * `E_RESOLVE_DUPLICATE_SYMBOL` — duplicate symbol in same namespace * `E_RESOLVE_NAMESPACE_COLLISION` — name exists in both type and value namespaces * `E_RESOLVE_VISIBILITY` — symbol not visible from this scope/module * `E_RESOLVE_INVALID_IMPORT` — import spec/path invalid ### 3.4 Type errors (E_TYPE_*) * `E_TYPE_MISMATCH` — type mismatch * `E_TYPE_UNKNOWN_TYPE` — unknown type name * `E_TYPE_MUTABILITY` — mutability violation * `E_TYPE_RETURN_PATH` — not all paths return a value * `E_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 binding * `W_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.