Skip to main content

aver/
ast.rs

1/// Source line number (1-based). 0 = synthetic/unknown.
2pub type SourceLine = usize;
3
4/// AST node with source location. Line-agnostic equality: two `Spanned` values
5/// are equal iff their inner nodes are equal, regardless of line.
6#[derive(Debug, Clone)]
7pub struct Spanned<T> {
8    pub node: T,
9    pub line: SourceLine,
10}
11
12impl<T: PartialEq> PartialEq for Spanned<T> {
13    fn eq(&self, other: &Self) -> bool {
14        self.node == other.node
15    }
16}
17
18impl<T> Spanned<T> {
19    pub fn new(node: T, line: SourceLine) -> Self {
20        Self { node, line }
21    }
22
23    /// Create a Spanned with line=0 (synthetic/generated AST, no source location).
24    pub fn bare(node: T) -> Self {
25        Self { node, line: 0 }
26    }
27}
28
29#[derive(Debug, Clone, PartialEq)]
30pub enum Literal {
31    Int(i64),
32    Float(f64),
33    Str(String),
34    Bool(bool),
35    Unit,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq)]
39pub enum BinOp {
40    Add,
41    Sub,
42    Mul,
43    Div,
44    Eq,
45    Neq,
46    Lt,
47    Gt,
48    Lte,
49    Gte,
50}
51
52#[derive(Debug, Clone, PartialEq)]
53pub struct MatchArm {
54    pub pattern: Pattern,
55    pub body: Box<Spanned<Expr>>,
56}
57
58#[derive(Debug, Clone, PartialEq)]
59pub enum Pattern {
60    Wildcard,
61    Literal(Literal),
62    Ident(String),
63    /// Empty list pattern: `[]`
64    EmptyList,
65    /// Cons-like list pattern: `[head, ..tail]`
66    Cons(String, String),
67    /// Tuple pattern: `(a, b)` / `(_, x)` / nested tuples.
68    Tuple(Vec<Pattern>),
69    /// Constructor pattern: fully-qualified name + list of binding names.
70    /// Built-ins: Result.Ok(x), Result.Err(x), Option.Some(x), Option.None.
71    /// User-defined: Shape.Circle(r), Shape.Rect(w, h), Shape.Point.
72    Constructor(String, Vec<String>),
73}
74
75#[derive(Debug, Clone, PartialEq)]
76pub enum StrPart {
77    Literal(String),
78    Parsed(Box<Spanned<Expr>>),
79}
80
81#[derive(Debug, Clone, PartialEq)]
82pub enum Expr {
83    Literal(Literal),
84    Ident(String),
85    Attr(Box<Spanned<Expr>>, String),
86    FnCall(Box<Spanned<Expr>>, Vec<Spanned<Expr>>),
87    BinOp(BinOp, Box<Spanned<Expr>>, Box<Spanned<Expr>>),
88    Match {
89        subject: Box<Spanned<Expr>>,
90        arms: Vec<MatchArm>,
91    },
92    Constructor(String, Option<Box<Spanned<Expr>>>),
93    ErrorProp(Box<Spanned<Expr>>),
94    InterpolatedStr(Vec<StrPart>),
95    List(Vec<Spanned<Expr>>),
96    Tuple(Vec<Spanned<Expr>>),
97    /// Map literal: `{"a" => 1, "b" => 2}`
98    MapLiteral(Vec<(Spanned<Expr>, Spanned<Expr>)>),
99    /// Record creation: `User(name = "Alice", age = 30)`
100    RecordCreate {
101        type_name: String,
102        fields: Vec<(String, Spanned<Expr>)>,
103    },
104    /// Record update: `User.update(base, field = newVal, ...)`
105    RecordUpdate {
106        type_name: String,
107        base: Box<Spanned<Expr>>,
108        updates: Vec<(String, Spanned<Expr>)>,
109    },
110    /// Tail-position call to a function in the same SCC (self or mutual recursion).
111    /// Produced by the TCO transform pass before type-checking.
112    TailCall(Box<(String, Vec<Spanned<Expr>>)>),
113    /// Independent product: `(a, b, c)!` or `(a, b, c)?!`.
114    /// Elements are independent effectful expressions evaluated with no guaranteed order.
115    /// `unwrap=true` (`?!`): all elements must be Result; unwraps Ok values, propagates first Err.
116    /// `unwrap=false` (`!`): returns raw tuple of results.
117    /// Produces a replay group (effects matched by branch_path + effect_occurrence + type + args).
118    IndependentProduct(Vec<Spanned<Expr>>, bool),
119    /// Compiled variable lookup: `env[last][slot]` — O(1) instead of HashMap scan.
120    /// Produced by the resolver pass for locals inside function bodies.
121    Resolved(u16),
122}
123
124#[derive(Debug, Clone, PartialEq)]
125pub enum Stmt {
126    Binding(String, Option<String>, Spanned<Expr>),
127    Expr(Spanned<Expr>),
128}
129
130#[derive(Debug, Clone, PartialEq)]
131pub enum FnBody {
132    Block(Vec<Stmt>),
133}
134
135impl FnBody {
136    pub fn from_expr(expr: Spanned<Expr>) -> Self {
137        Self::Block(vec![Stmt::Expr(expr)])
138    }
139
140    pub fn stmts(&self) -> &[Stmt] {
141        match self {
142            Self::Block(stmts) => stmts,
143        }
144    }
145
146    pub fn stmts_mut(&mut self) -> &mut Vec<Stmt> {
147        match self {
148            Self::Block(stmts) => stmts,
149        }
150    }
151
152    pub fn tail_expr(&self) -> Option<&Spanned<Expr>> {
153        match self.stmts().last() {
154            Some(Stmt::Expr(expr)) => Some(expr),
155            _ => None,
156        }
157    }
158
159    pub fn tail_expr_mut(&mut self) -> Option<&mut Spanned<Expr>> {
160        match self.stmts_mut().last_mut() {
161            Some(Stmt::Expr(expr)) => Some(expr),
162            _ => None,
163        }
164    }
165}
166
167/// Compile-time resolution metadata for a function body.
168/// Produced by `resolver::resolve_fn` — maps local variable names to slot indices
169/// so the interpreter can use `Vec<Value>` instead of `HashMap` lookups.
170#[derive(Debug, Clone, PartialEq)]
171pub struct FnResolution {
172    /// Total number of local slots needed (params + bindings in body).
173    pub local_count: u16,
174    /// Map from local variable name → slot index in the local `Slots` frame.
175    pub local_slots: std::sync::Arc<std::collections::HashMap<String, u16>>,
176}
177
178#[derive(Debug, Clone, PartialEq)]
179pub struct FnDef {
180    pub name: String,
181    pub line: usize,
182    pub params: Vec<(String, String)>,
183    pub return_type: String,
184    pub effects: Vec<Spanned<String>>,
185    pub desc: Option<String>,
186    pub body: std::sync::Arc<FnBody>,
187    /// `None` for unresolved (REPL, module sub-interpreters).
188    pub resolution: Option<FnResolution>,
189}
190
191#[derive(Debug, Clone, PartialEq)]
192pub struct Module {
193    pub name: String,
194    pub line: usize,
195    pub depends: Vec<String>,
196    pub exposes: Vec<String>,
197    pub exposes_opaque: Vec<String>,
198    pub exposes_line: Option<usize>,
199    pub intent: String,
200}
201
202#[derive(Debug, Clone, PartialEq)]
203pub enum VerifyGivenDomain {
204    /// Integer range domain in verify law: `1..50` (inclusive).
205    IntRange { start: i64, end: i64 },
206    /// Explicit domain values in verify law: `[v1, v2, ...]`.
207    Explicit(Vec<Spanned<Expr>>),
208}
209
210#[derive(Debug, Clone, PartialEq)]
211pub struct VerifyGiven {
212    pub name: String,
213    pub type_name: String,
214    pub domain: VerifyGivenDomain,
215}
216
217#[derive(Debug, Clone, PartialEq)]
218pub struct VerifyLaw {
219    pub name: String,
220    pub givens: Vec<VerifyGiven>,
221    /// Optional precondition for the law template, written as `when <bool-expr>`.
222    pub when: Option<Spanned<Expr>>,
223    /// Template assertion from source before given-domain expansion.
224    pub lhs: Spanned<Expr>,
225    pub rhs: Spanned<Expr>,
226    /// Per-sample substituted guards for `when`, aligned with `VerifyBlock.cases`.
227    pub sample_guards: Vec<Spanned<Expr>>,
228}
229
230/// Source range for AST nodes that need location tracking.
231/// Used by verify case spans: `cases[i] <-> case_spans[i]`.
232#[derive(Debug, Clone, PartialEq, Default)]
233pub struct SourceSpan {
234    pub line: usize,
235    pub col: usize,
236    pub end_line: usize,
237    pub end_col: usize,
238}
239
240#[derive(Debug, Clone, PartialEq)]
241pub enum VerifyKind {
242    Cases,
243    Law(Box<VerifyLaw>),
244}
245
246#[derive(Debug, Clone, PartialEq)]
247pub struct VerifyBlock {
248    pub fn_name: String,
249    pub line: usize,
250    pub cases: Vec<(Spanned<Expr>, Spanned<Expr>)>,
251    pub case_spans: Vec<SourceSpan>,
252    /// Per-case given bindings for law verify (empty for Cases kind).
253    pub case_givens: Vec<Vec<(String, Spanned<Expr>)>>,
254    pub kind: VerifyKind,
255}
256
257impl VerifyBlock {
258    /// Construct a VerifyBlock with default (zero) spans for each case.
259    /// Use when source location tracking is not needed (codegen, tests).
260    pub fn new_unspanned(
261        fn_name: String,
262        line: usize,
263        cases: Vec<(Spanned<Expr>, Spanned<Expr>)>,
264        kind: VerifyKind,
265    ) -> Self {
266        let case_spans = vec![SourceSpan::default(); cases.len()];
267        Self {
268            fn_name,
269            line,
270            cases,
271            case_spans,
272            case_givens: vec![],
273            kind,
274        }
275    }
276
277    pub fn iter_cases_with_spans(
278        &self,
279    ) -> impl Iterator<Item = (&(Spanned<Expr>, Spanned<Expr>), &SourceSpan)> {
280        debug_assert_eq!(self.cases.len(), self.case_spans.len());
281        self.cases.iter().zip(&self.case_spans)
282    }
283}
284
285#[derive(Debug, Clone, PartialEq)]
286pub struct DecisionBlock {
287    pub name: String,
288    pub line: usize,
289    pub date: String,
290    pub reason: String,
291    pub chosen: Spanned<DecisionImpact>,
292    pub rejected: Vec<Spanned<DecisionImpact>>,
293    pub impacts: Vec<Spanned<DecisionImpact>>,
294    pub author: Option<String>,
295}
296
297#[derive(Debug, Clone, PartialEq, Eq, Hash)]
298pub enum DecisionImpact {
299    Symbol(String),
300    Semantic(String),
301}
302
303impl DecisionImpact {
304    pub fn text(&self) -> &str {
305        match self {
306            DecisionImpact::Symbol(s) | DecisionImpact::Semantic(s) => s,
307        }
308    }
309
310    pub fn as_context_string(&self) -> String {
311        match self {
312            DecisionImpact::Symbol(s) => s.clone(),
313            DecisionImpact::Semantic(s) => format!("\"{}\"", s),
314        }
315    }
316}
317
318/// A variant in a sum type definition.
319/// e.g. `Circle(Float)` → `TypeVariant { name: "Circle", fields: ["Float"] }`
320#[derive(Debug, Clone, PartialEq)]
321pub struct TypeVariant {
322    pub name: String,
323    pub fields: Vec<String>, // type annotations (e.g. "Float", "String")
324}
325
326/// A user-defined type definition.
327#[derive(Debug, Clone, PartialEq)]
328pub enum TypeDef {
329    /// `type Shape` with variants Circle(Float), Rect(Float, Float), Point
330    Sum {
331        name: String,
332        variants: Vec<TypeVariant>,
333        line: usize,
334    },
335    /// `record User` with fields name: String, age: Int
336    Product {
337        name: String,
338        fields: Vec<(String, String)>,
339        line: usize,
340    },
341}
342
343#[derive(Debug, Clone, PartialEq)]
344pub enum TopLevel {
345    Module(Module),
346    FnDef(FnDef),
347    Verify(VerifyBlock),
348    Decision(DecisionBlock),
349    Stmt(Stmt),
350    TypeDef(TypeDef),
351}