Skip to main content

lex_syntax/
syntax.rs

1//! Syntax tree (the parser's direct output, before canonicalization in §5).
2//!
3//! These nodes hew closely to source syntax; canonicalization (§5.3) folds
4//! `if` into `Match`, `?` into its match expansion, etc.
5
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
9pub struct Program {
10    pub items: Vec<Item>,
11    /// Comments at the top of the file, before the first item. Each
12    /// entry is one line of source, including the leading `#`.
13    /// Preserved by the formatter (`lex fmt`); stripped by the
14    /// canonicalizer so they never participate in SigId.
15    #[serde(default, skip_serializing_if = "Vec::is_empty")]
16    pub leading_comments: Vec<String>,
17    /// Comments after the last item (or all of the file, if `items`
18    /// is empty). Same semantics as `leading_comments`.
19    #[serde(default, skip_serializing_if = "Vec::is_empty")]
20    pub trailing_comments: Vec<String>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
24pub enum Item {
25    Import(Import),
26    TypeDecl(TypeDecl),
27    FnDecl(FnDecl),
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
31pub struct Import {
32    pub reference: String,
33    pub alias: String,
34    /// Comments immediately preceding this import (one line each, `#`
35    /// preserved). Preserved by the formatter; ignored everywhere
36    /// else.
37    #[serde(default, skip_serializing_if = "Vec::is_empty")]
38    pub leading_comments: Vec<String>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
42pub struct TypeDecl {
43    pub name: String,
44    pub params: Vec<String>,
45    pub definition: TypeExpr,
46    /// Comments immediately preceding this declaration. See `Import`
47    /// for semantics.
48    #[serde(default, skip_serializing_if = "Vec::is_empty")]
49    pub leading_comments: Vec<String>,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
53pub struct FnDecl {
54    pub name: String,
55    pub type_params: Vec<String>,
56    pub params: Vec<Param>,
57    pub effects: Vec<Effect>,
58    /// Optional open-row tail on the declared effect row (effect-row poly):
59    /// `-> [io, net | E] T` names a row variable `E` (one of `type_params`)
60    /// standing for "plus any further effects", making the function
61    /// effect-row-polymorphic. `None` for the usual closed row. Serialized
62    /// only when present so pre-feature signatures (and their SigId content
63    /// hashes) stay byte-identical.
64    #[serde(default, skip_serializing_if = "Option::is_none")]
65    pub effect_row_var: Option<String>,
66    pub return_type: TypeExpr,
67    pub body: Block,
68    /// Optional `examples { call(a, b) => expected, ... }` block (#369).
69    /// Each case binds the function on literal-or-pure arguments and
70    /// declares the value the body is expected to produce. Pure-only in
71    /// v1: a function carrying examples must declare no effects.
72    /// Serialized as an empty `Vec` so the JSON shape stays compatible
73    /// with pre-#369 signatures when the block is absent.
74    #[serde(default, skip_serializing_if = "Vec::is_empty")]
75    pub examples: Vec<Example>,
76    /// Comments immediately preceding this fn declaration. See
77    /// `Import` for semantics.
78    #[serde(default, skip_serializing_if = "Vec::is_empty")]
79    pub leading_comments: Vec<String>,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
83pub struct Example {
84    /// Arguments passed to the function, in declaration order.
85    pub args: Vec<Expr>,
86    /// Value the body is expected to produce.
87    pub expected: Expr,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
91pub struct Param {
92    pub name: String,
93    pub ty: TypeExpr,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
97pub struct Effect {
98    pub name: String,
99    pub arg: Option<EffectArg>,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
103pub enum EffectArg {
104    Str(String),
105    Int(i64),
106    Ident(String),
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
110pub enum TypeExpr {
111    /// Named primitive or constructor application (`Int`, `Result[Int, Str]`, `T`).
112    /// We resolve which it is during type-checking, not parsing.
113    Named { name: String, args: Vec<TypeExpr> },
114    Record(Vec<TypeField>),
115    Tuple(Vec<TypeExpr>),
116    Function {
117        params: Vec<TypeExpr>,
118        effects: Vec<Effect>,
119        /// Open-row tail variable, e.g. the `E` in `(Int) -> [io | E] Int`.
120        /// See `FnDecl::effect_row_var`. Serialized only when present.
121        #[serde(default, skip_serializing_if = "Option::is_none")]
122        effect_row_var: Option<String>,
123        ret: Box<TypeExpr>,
124    },
125    Union(Vec<UnionVariant>),
126    /// Record type with one or more spread bases (#363): `{ ...TypeName, field :: Type }`.
127    /// Resolved to a flat `Ty::Record` during type-checking.
128    RecordWithSpreads {
129        spreads: Vec<String>,
130        fields: Vec<TypeField>,
131    },
132    /// Refinement type (#209): a base type plus a predicate the
133    /// inhabitant must satisfy. `Int{x | x > 0 and x <= balance}`
134    /// parses with `base = Named { name: "Int", args: [] }`,
135    /// `binding = "x"`, and `predicate = (x > 0) and (x <= balance)`.
136    /// Slice 1 stores the refinement; the type checker treats the
137    /// refined type as its base. Slice 2 wires up static discharge
138    /// via the spec-checker's gate evaluator; slice 3 adds the
139    /// residual runtime check at call boundaries.
140    Refined {
141        base: Box<TypeExpr>,
142        binding: String,
143        predicate: Box<Expr>,
144    },
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
148pub struct TypeField {
149    pub name: String,
150    pub ty: TypeExpr,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
154pub struct UnionVariant {
155    pub name: String,
156    /// `None` = tag-only (`Empty`); `Some(payload)` = constructor with payload.
157    pub payload: Option<TypeExpr>,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
161pub struct Block {
162    pub statements: Vec<Statement>,
163    pub result: Box<Expr>,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
167pub enum Statement {
168    Let { name: String, ty: Option<TypeExpr>, value: Expr },
169    Expr(Expr),
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
173pub enum Expr {
174    Lit(Literal),
175    Var(String),
176    Block(Block),
177    Call { callee: Box<Expr>, args: Vec<Expr> },
178    Pipe { left: Box<Expr>, right: Box<Expr> },
179    /// `expr?` postfix.
180    Try(Box<Expr>),
181    /// `expr.field`
182    Field { value: Box<Expr>, field: String },
183    BinOp { op: BinOp, lhs: Box<Expr>, rhs: Box<Expr> },
184    UnaryOp { op: UnaryOp, expr: Box<Expr> },
185    If { cond: Box<Expr>, then_block: Block, else_block: Block },
186    Match { scrutinee: Box<Expr>, arms: Vec<Arm> },
187    RecordLit(Vec<RecordLitField>),
188    TupleLit(Vec<Expr>),
189    ListLit(Vec<Expr>),
190    /// A bare constructor name (`None`, `Empty`) or constructor call (`Ok(x)`).
191    /// Since we cannot distinguish a constructor from a variable at parse
192    /// time, the parser emits `Var`/`Call` and the type checker resolves it.
193    /// This variant is kept for the canonicalizer to lift detected
194    /// constructors into.
195    Constructor { name: String, args: Vec<Expr> },
196    Lambda(Box<Lambda>),
197    /// Inline type ascription `(expr :: Type)`. The declared type is checked
198    /// against the inferred type at type-check time; at runtime it compiles
199    /// identically to the inner expression (type-only annotation, erased at
200    /// bytecode level). (#319)
201    Ascription { value: Box<Expr>, ty: TypeExpr },
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
205pub struct Lambda {
206    pub params: Vec<Param>,
207    pub return_type: TypeExpr,
208    pub effects: Vec<Effect>,
209    /// Open-row tail variable on the lambda's declared effects. See
210    /// `FnDecl::effect_row_var`. Serialized only when present.
211    #[serde(default, skip_serializing_if = "Option::is_none")]
212    pub effect_row_var: Option<String>,
213    pub body: Block,
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
217pub struct RecordLitField {
218    pub name: String,
219    pub value: Expr,
220}
221
222#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
223pub enum BinOp {
224    Add, Sub, Mul, Div, Mod,
225    Eq, Neq, Lt, Lte, Gt, Gte,
226    And, Or,
227}
228
229impl BinOp {
230    pub fn precedence(self) -> u8 {
231        use BinOp::*;
232        match self {
233            Or => 1,
234            And => 2,
235            Eq | Neq | Lt | Lte | Gt | Gte => 3,
236            Add | Sub => 4,
237            Mul | Div | Mod => 5,
238        }
239    }
240
241    pub fn as_str(self) -> &'static str {
242        use BinOp::*;
243        match self {
244            Add => "+", Sub => "-", Mul => "*", Div => "/", Mod => "%",
245            Eq => "==", Neq => "!=", Lt => "<", Lte => "<=", Gt => ">", Gte => ">=",
246            And => "and", Or => "or",
247        }
248    }
249}
250
251#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
252pub enum UnaryOp { Neg, Not }
253
254#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
255pub struct Arm {
256    pub pattern: Pattern,
257    pub body: Expr,
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
261pub enum Pattern {
262    Lit(Literal),
263    /// Bare ident — either a binder or (during canonicalization) a tag-only constructor.
264    Var(String),
265    Wild,
266    Constructor { name: String, args: Vec<Pattern> },
267    Record { fields: Vec<RecordPatField>, rest: bool },
268    Tuple(Vec<Pattern>),
269}
270
271#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
272pub struct RecordPatField {
273    pub name: String,
274    /// `None` means shorthand `{ name }` => `{ name: name }`.
275    pub pattern: Option<Pattern>,
276}
277
278#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
279pub enum Literal {
280    Int(i64),
281    Float(f64),
282    Str(String),
283    Bytes(Vec<u8>),
284    Bool(bool),
285    Unit,
286}