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