Skip to main content

harn_parser/
ast.rs

1use harn_lexer::{Span, StringSegment};
2
3/// A node wrapped with source location information.
4#[derive(Debug, Clone, PartialEq)]
5pub struct Spanned<T> {
6    pub node: T,
7    pub span: Span,
8}
9
10impl<T> Spanned<T> {
11    pub fn new(node: T, span: Span) -> Self {
12        Self { node, span }
13    }
14
15    pub fn dummy(node: T) -> Self {
16        Self {
17            node,
18            span: Span::dummy(),
19        }
20    }
21}
22
23/// A spanned AST node — the primary unit throughout the compiler.
24pub type SNode = Spanned<Node>;
25
26/// Helper to wrap a node with a span.
27pub fn spanned(node: Node, span: Span) -> SNode {
28    SNode::new(node, span)
29}
30
31/// AST nodes for the Harn language.
32#[derive(Debug, Clone, PartialEq)]
33pub enum Node {
34    Pipeline {
35        name: String,
36        params: Vec<String>,
37        body: Vec<SNode>,
38        extends: Option<String>,
39        is_pub: bool,
40    },
41    LetBinding {
42        pattern: BindingPattern,
43        type_ann: Option<TypeExpr>,
44        value: Box<SNode>,
45    },
46    VarBinding {
47        pattern: BindingPattern,
48        type_ann: Option<TypeExpr>,
49        value: Box<SNode>,
50    },
51    OverrideDecl {
52        name: String,
53        params: Vec<String>,
54        body: Vec<SNode>,
55    },
56    ImportDecl {
57        path: String,
58    },
59    /// Selective import: import { foo, bar } from "module"
60    SelectiveImport {
61        names: Vec<String>,
62        path: String,
63    },
64    EnumDecl {
65        name: String,
66        type_params: Vec<TypeParam>,
67        variants: Vec<EnumVariant>,
68        is_pub: bool,
69    },
70    StructDecl {
71        name: String,
72        type_params: Vec<TypeParam>,
73        fields: Vec<StructField>,
74        is_pub: bool,
75    },
76    InterfaceDecl {
77        name: String,
78        type_params: Vec<TypeParam>,
79        associated_types: Vec<(String, Option<TypeExpr>)>,
80        methods: Vec<InterfaceMethod>,
81    },
82    /// Impl block: impl TypeName { fn method(self, ...) { ... } ... }
83    ImplBlock {
84        type_name: String,
85        methods: Vec<SNode>,
86    },
87
88    IfElse {
89        condition: Box<SNode>,
90        then_body: Vec<SNode>,
91        else_body: Option<Vec<SNode>>,
92    },
93    ForIn {
94        pattern: BindingPattern,
95        iterable: Box<SNode>,
96        body: Vec<SNode>,
97    },
98    MatchExpr {
99        value: Box<SNode>,
100        arms: Vec<MatchArm>,
101    },
102    WhileLoop {
103        condition: Box<SNode>,
104        body: Vec<SNode>,
105    },
106    Retry {
107        count: Box<SNode>,
108        body: Vec<SNode>,
109    },
110    ReturnStmt {
111        value: Option<Box<SNode>>,
112    },
113    TryCatch {
114        body: Vec<SNode>,
115        error_var: Option<String>,
116        error_type: Option<TypeExpr>,
117        catch_body: Vec<SNode>,
118        finally_body: Option<Vec<SNode>>,
119    },
120    /// Try expression: try { body } — returns Result.Ok(value) or Result.Err(error).
121    TryExpr {
122        body: Vec<SNode>,
123    },
124    FnDecl {
125        name: String,
126        type_params: Vec<TypeParam>,
127        params: Vec<TypedParam>,
128        return_type: Option<TypeExpr>,
129        where_clauses: Vec<WhereClause>,
130        body: Vec<SNode>,
131        is_pub: bool,
132    },
133    ToolDecl {
134        name: String,
135        description: Option<String>,
136        params: Vec<TypedParam>,
137        return_type: Option<TypeExpr>,
138        body: Vec<SNode>,
139        is_pub: bool,
140    },
141    TypeDecl {
142        name: String,
143        type_expr: TypeExpr,
144    },
145    SpawnExpr {
146        body: Vec<SNode>,
147    },
148    /// Duration literal: 500ms, 5s, 30m, 2h, 1d, 1w
149    DurationLiteral(u64),
150    /// Range expression: `start to end` (inclusive) or `start to end exclusive` (half-open)
151    RangeExpr {
152        start: Box<SNode>,
153        end: Box<SNode>,
154        inclusive: bool,
155    },
156    /// Guard clause: guard condition else { body }
157    GuardStmt {
158        condition: Box<SNode>,
159        else_body: Vec<SNode>,
160    },
161    RequireStmt {
162        condition: Box<SNode>,
163        message: Option<Box<SNode>>,
164    },
165    /// Defer statement: defer { body } — runs body at scope exit.
166    DeferStmt {
167        body: Vec<SNode>,
168    },
169    /// Deadline block: deadline DURATION { body }
170    DeadlineBlock {
171        duration: Box<SNode>,
172        body: Vec<SNode>,
173    },
174    /// Yield expression: yields control to host, optionally with a value.
175    YieldExpr {
176        value: Option<Box<SNode>>,
177    },
178    /// Mutex block: mutual exclusion for concurrent access.
179    MutexBlock {
180        body: Vec<SNode>,
181    },
182    /// Break out of a loop.
183    BreakStmt,
184    /// Continue to next loop iteration.
185    ContinueStmt,
186
187    Parallel {
188        mode: ParallelMode,
189        /// For Count mode: the count expression. For Each/Settle: the list expression.
190        expr: Box<SNode>,
191        variable: Option<String>,
192        body: Vec<SNode>,
193        /// Optional trailing `with { max_concurrent: N, ... }` option block.
194        /// A vec (rather than a dict) preserves source order for error
195        /// reporting and keeps parsing cheap. Only `max_concurrent` is
196        /// currently honored; unknown keys are rejected by the parser.
197        options: Vec<(String, SNode)>,
198    },
199
200    SelectExpr {
201        cases: Vec<SelectCase>,
202        timeout: Option<(Box<SNode>, Vec<SNode>)>,
203        default_body: Option<Vec<SNode>>,
204    },
205
206    FunctionCall {
207        name: String,
208        args: Vec<SNode>,
209    },
210    MethodCall {
211        object: Box<SNode>,
212        method: String,
213        args: Vec<SNode>,
214    },
215    /// Optional method call: `obj?.method(args)` — returns nil if obj is nil.
216    OptionalMethodCall {
217        object: Box<SNode>,
218        method: String,
219        args: Vec<SNode>,
220    },
221    PropertyAccess {
222        object: Box<SNode>,
223        property: String,
224    },
225    /// Optional chaining: `obj?.property` — returns nil if obj is nil.
226    OptionalPropertyAccess {
227        object: Box<SNode>,
228        property: String,
229    },
230    SubscriptAccess {
231        object: Box<SNode>,
232        index: Box<SNode>,
233    },
234    SliceAccess {
235        object: Box<SNode>,
236        start: Option<Box<SNode>>,
237        end: Option<Box<SNode>>,
238    },
239    BinaryOp {
240        op: String,
241        left: Box<SNode>,
242        right: Box<SNode>,
243    },
244    UnaryOp {
245        op: String,
246        operand: Box<SNode>,
247    },
248    Ternary {
249        condition: Box<SNode>,
250        true_expr: Box<SNode>,
251        false_expr: Box<SNode>,
252    },
253    Assignment {
254        target: Box<SNode>,
255        value: Box<SNode>,
256        /// None = plain `=`, Some("+") = `+=`, etc.
257        op: Option<String>,
258    },
259    ThrowStmt {
260        value: Box<SNode>,
261    },
262
263    /// Enum variant construction: EnumName.Variant(args)
264    EnumConstruct {
265        enum_name: String,
266        variant: String,
267        args: Vec<SNode>,
268    },
269    /// Struct construction: StructName { field: value, ... }
270    StructConstruct {
271        struct_name: String,
272        fields: Vec<DictEntry>,
273    },
274
275    InterpolatedString(Vec<StringSegment>),
276    StringLiteral(String),
277    /// Raw string literal `r"..."` — no escape processing.
278    RawStringLiteral(String),
279    IntLiteral(i64),
280    FloatLiteral(f64),
281    BoolLiteral(bool),
282    NilLiteral,
283    Identifier(String),
284    ListLiteral(Vec<SNode>),
285    DictLiteral(Vec<DictEntry>),
286    /// Spread expression `...expr` inside list/dict literals.
287    Spread(Box<SNode>),
288    /// Try operator: expr? — unwraps Result.Ok or propagates Result.Err.
289    TryOperator {
290        operand: Box<SNode>,
291    },
292
293    Block(Vec<SNode>),
294    Closure {
295        params: Vec<TypedParam>,
296        body: Vec<SNode>,
297        /// When true, this closure was written as `fn(params) { body }`.
298        /// The formatter preserves this distinction.
299        fn_syntax: bool,
300    },
301}
302
303/// Parallel execution mode.
304#[derive(Debug, Clone, Copy, PartialEq)]
305pub enum ParallelMode {
306    /// `parallel N { i -> ... }` — run N concurrent tasks.
307    Count,
308    /// `parallel each list { item -> ... }` — map over list concurrently.
309    Each,
310    /// `parallel settle list { item -> ... }` — map with error collection.
311    Settle,
312}
313
314#[derive(Debug, Clone, PartialEq)]
315pub struct MatchArm {
316    pub pattern: SNode,
317    /// Optional guard: `pattern if condition -> { body }`.
318    pub guard: Option<Box<SNode>>,
319    pub body: Vec<SNode>,
320}
321
322#[derive(Debug, Clone, PartialEq)]
323pub struct SelectCase {
324    pub variable: String,
325    pub channel: Box<SNode>,
326    pub body: Vec<SNode>,
327}
328
329#[derive(Debug, Clone, PartialEq)]
330pub struct DictEntry {
331    pub key: SNode,
332    pub value: SNode,
333}
334
335/// An enum variant declaration.
336#[derive(Debug, Clone, PartialEq)]
337pub struct EnumVariant {
338    pub name: String,
339    pub fields: Vec<TypedParam>,
340}
341
342/// A struct field declaration.
343#[derive(Debug, Clone, PartialEq)]
344pub struct StructField {
345    pub name: String,
346    pub type_expr: Option<TypeExpr>,
347    pub optional: bool,
348}
349
350/// An interface method signature.
351#[derive(Debug, Clone, PartialEq)]
352pub struct InterfaceMethod {
353    pub name: String,
354    pub type_params: Vec<TypeParam>,
355    pub params: Vec<TypedParam>,
356    pub return_type: Option<TypeExpr>,
357}
358
359/// A type annotation (optional, for runtime checking).
360#[derive(Debug, Clone, PartialEq)]
361pub enum TypeExpr {
362    /// A named type: int, string, float, bool, nil, list, dict, closure,
363    /// or a user-defined type name.
364    Named(String),
365    /// A union type: `string | nil`, `int | float`.
366    Union(Vec<TypeExpr>),
367    /// A dict shape type: `{name: string, age: int, active?: bool}`.
368    Shape(Vec<ShapeField>),
369    /// A list type: `list<int>`.
370    List(Box<TypeExpr>),
371    /// A dict type with key and value types: `dict<string, int>`.
372    DictType(Box<TypeExpr>, Box<TypeExpr>),
373    /// A lazy iterator type: `iter<int>`. Yields values of the inner type
374    /// via the combinator/sink protocol (`VmValue::Iter` at runtime).
375    Iter(Box<TypeExpr>),
376    /// A generic type application: `Option<int>`, `Result<string, int>`.
377    Applied { name: String, args: Vec<TypeExpr> },
378    /// A function type: `fn(int, string) -> bool`.
379    FnType {
380        params: Vec<TypeExpr>,
381        return_type: Box<TypeExpr>,
382    },
383    /// The bottom type: the type of expressions that never produce a value
384    /// (return, throw, break, continue).
385    Never,
386}
387
388/// A field in a dict shape type.
389#[derive(Debug, Clone, PartialEq)]
390pub struct ShapeField {
391    pub name: String,
392    pub type_expr: TypeExpr,
393    pub optional: bool,
394}
395
396/// A binding pattern for destructuring in let/var/for-in.
397#[derive(Debug, Clone, PartialEq)]
398pub enum BindingPattern {
399    /// Simple identifier: `let x = ...`
400    Identifier(String),
401    /// Dict destructuring: `let {name, age} = ...`
402    Dict(Vec<DictPatternField>),
403    /// List destructuring: `let [a, b] = ...`
404    List(Vec<ListPatternElement>),
405    /// Pair destructuring for `for (a, b) in iter { ... }`. The iter must
406    /// yield `VmValue::Pair` values. Not valid in let/var bindings.
407    Pair(String, String),
408}
409
410/// A field in a dict destructuring pattern.
411#[derive(Debug, Clone, PartialEq)]
412pub struct DictPatternField {
413    /// The dict key to extract.
414    pub key: String,
415    /// Renamed binding (if different from key), e.g. `{name: alias}`.
416    pub alias: Option<String>,
417    /// True for `...rest` (rest pattern).
418    pub is_rest: bool,
419    /// Default value if the key is missing (nil), e.g. `{name = "default"}`.
420    pub default_value: Option<Box<SNode>>,
421}
422
423/// An element in a list destructuring pattern.
424#[derive(Debug, Clone, PartialEq)]
425pub struct ListPatternElement {
426    /// The variable name to bind.
427    pub name: String,
428    /// True for `...rest` (rest pattern).
429    pub is_rest: bool,
430    /// Default value if the index is out of bounds (nil), e.g. `[a = 0]`.
431    pub default_value: Option<Box<SNode>>,
432}
433
434/// A generic type parameter on a function or pipeline declaration.
435#[derive(Debug, Clone, PartialEq)]
436pub struct TypeParam {
437    pub name: String,
438}
439
440/// A where-clause constraint on a generic type parameter.
441#[derive(Debug, Clone, PartialEq)]
442pub struct WhereClause {
443    pub type_name: String,
444    pub bound: String,
445}
446
447/// A parameter with an optional type annotation and optional default value.
448#[derive(Debug, Clone, PartialEq)]
449pub struct TypedParam {
450    pub name: String,
451    pub type_expr: Option<TypeExpr>,
452    pub default_value: Option<Box<SNode>>,
453    /// If true, this is a rest parameter (`...name`) that collects remaining arguments.
454    pub rest: bool,
455}
456
457impl TypedParam {
458    /// Create an untyped parameter.
459    pub fn untyped(name: impl Into<String>) -> Self {
460        Self {
461            name: name.into(),
462            type_expr: None,
463            default_value: None,
464            rest: false,
465        }
466    }
467
468    /// Create a typed parameter.
469    pub fn typed(name: impl Into<String>, type_expr: TypeExpr) -> Self {
470        Self {
471            name: name.into(),
472            type_expr: Some(type_expr),
473            default_value: None,
474            rest: false,
475        }
476    }
477
478    /// Extract just the names from a list of typed params.
479    pub fn names(params: &[TypedParam]) -> Vec<String> {
480        params.iter().map(|p| p.name.clone()).collect()
481    }
482
483    /// Return the index of the first parameter with a default value, or None.
484    pub fn default_start(params: &[TypedParam]) -> Option<usize> {
485        params.iter().position(|p| p.default_value.is_some())
486    }
487}