360 lines
7.0 KiB
Markdown
360 lines
7.0 KiB
Markdown
# 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.
|