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_params: Vec<TypeParam>,
144        type_expr: TypeExpr,
145    },
146    SpawnExpr {
147        body: Vec<SNode>,
148    },
149    /// Duration literal: 500ms, 5s, 30m, 2h, 1d, 1w
150    DurationLiteral(u64),
151    /// Range expression: `start to end` (inclusive) or `start to end exclusive` (half-open)
152    RangeExpr {
153        start: Box<SNode>,
154        end: Box<SNode>,
155        inclusive: bool,
156    },
157    /// Guard clause: guard condition else { body }
158    GuardStmt {
159        condition: Box<SNode>,
160        else_body: Vec<SNode>,
161    },
162    RequireStmt {
163        condition: Box<SNode>,
164        message: Option<Box<SNode>>,
165    },
166    /// Defer statement: defer { body } — runs body at scope exit.
167    DeferStmt {
168        body: Vec<SNode>,
169    },
170    /// Deadline block: deadline DURATION { body }
171    DeadlineBlock {
172        duration: Box<SNode>,
173        body: Vec<SNode>,
174    },
175    /// Yield expression: yields control to host, optionally with a value.
176    YieldExpr {
177        value: Option<Box<SNode>>,
178    },
179    /// Mutex block: mutual exclusion for concurrent access.
180    MutexBlock {
181        body: Vec<SNode>,
182    },
183    /// Break out of a loop.
184    BreakStmt,
185    /// Continue to next loop iteration.
186    ContinueStmt,
187
188    Parallel {
189        mode: ParallelMode,
190        /// For Count mode: the count expression. For Each/Settle: the list expression.
191        expr: Box<SNode>,
192        variable: Option<String>,
193        body: Vec<SNode>,
194        /// Optional trailing `with { max_concurrent: N, ... }` option block.
195        /// A vec (rather than a dict) preserves source order for error
196        /// reporting and keeps parsing cheap. Only `max_concurrent` is
197        /// currently honored; unknown keys are rejected by the parser.
198        options: Vec<(String, SNode)>,
199    },
200
201    SelectExpr {
202        cases: Vec<SelectCase>,
203        timeout: Option<(Box<SNode>, Vec<SNode>)>,
204        default_body: Option<Vec<SNode>>,
205    },
206
207    FunctionCall {
208        name: String,
209        args: Vec<SNode>,
210    },
211    MethodCall {
212        object: Box<SNode>,
213        method: String,
214        args: Vec<SNode>,
215    },
216    /// Optional method call: `obj?.method(args)` — returns nil if obj is nil.
217    OptionalMethodCall {
218        object: Box<SNode>,
219        method: String,
220        args: Vec<SNode>,
221    },
222    PropertyAccess {
223        object: Box<SNode>,
224        property: String,
225    },
226    /// Optional chaining: `obj?.property` — returns nil if obj is nil.
227    OptionalPropertyAccess {
228        object: Box<SNode>,
229        property: String,
230    },
231    SubscriptAccess {
232        object: Box<SNode>,
233        index: Box<SNode>,
234    },
235    SliceAccess {
236        object: Box<SNode>,
237        start: Option<Box<SNode>>,
238        end: Option<Box<SNode>>,
239    },
240    BinaryOp {
241        op: String,
242        left: Box<SNode>,
243        right: Box<SNode>,
244    },
245    UnaryOp {
246        op: String,
247        operand: Box<SNode>,
248    },
249    Ternary {
250        condition: Box<SNode>,
251        true_expr: Box<SNode>,
252        false_expr: Box<SNode>,
253    },
254    Assignment {
255        target: Box<SNode>,
256        value: Box<SNode>,
257        /// None = plain `=`, Some("+") = `+=`, etc.
258        op: Option<String>,
259    },
260    ThrowStmt {
261        value: Box<SNode>,
262    },
263
264    /// Enum variant construction: EnumName.Variant(args)
265    EnumConstruct {
266        enum_name: String,
267        variant: String,
268        args: Vec<SNode>,
269    },
270    /// Struct construction: StructName { field: value, ... }
271    StructConstruct {
272        struct_name: String,
273        fields: Vec<DictEntry>,
274    },
275
276    InterpolatedString(Vec<StringSegment>),
277    StringLiteral(String),
278    /// Raw string literal `r"..."` — no escape processing.
279    RawStringLiteral(String),
280    IntLiteral(i64),
281    FloatLiteral(f64),
282    BoolLiteral(bool),
283    NilLiteral,
284    Identifier(String),
285    ListLiteral(Vec<SNode>),
286    DictLiteral(Vec<DictEntry>),
287    /// Spread expression `...expr` inside list/dict literals.
288    Spread(Box<SNode>),
289    /// Try operator: expr? — unwraps Result.Ok or propagates Result.Err.
290    TryOperator {
291        operand: Box<SNode>,
292    },
293    /// Try-star operator: `try* EXPR` — evaluates EXPR; on throw, runs
294    /// pending finally blocks up to the enclosing catch and rethrows
295    /// the original value. On success, evaluates to EXPR's value.
296    /// Lowered per spec/HARN_SPEC.md as:
297    ///   { let _r = try { EXPR }
298    ///     guard is_ok(_r) else { throw unwrap_err(_r) }
299    ///     unwrap(_r) }
300    TryStar {
301        operand: Box<SNode>,
302    },
303
304    Block(Vec<SNode>),
305    Closure {
306        params: Vec<TypedParam>,
307        body: Vec<SNode>,
308        /// When true, this closure was written as `fn(params) { body }`.
309        /// The formatter preserves this distinction.
310        fn_syntax: bool,
311    },
312}
313
314/// Parallel execution mode.
315#[derive(Debug, Clone, Copy, PartialEq)]
316pub enum ParallelMode {
317    /// `parallel N { i -> ... }` — run N concurrent tasks.
318    Count,
319    /// `parallel each list { item -> ... }` — map over list concurrently.
320    Each,
321    /// `parallel settle list { item -> ... }` — map with error collection.
322    Settle,
323}
324
325#[derive(Debug, Clone, PartialEq)]
326pub struct MatchArm {
327    pub pattern: SNode,
328    /// Optional guard: `pattern if condition -> { body }`.
329    pub guard: Option<Box<SNode>>,
330    pub body: Vec<SNode>,
331}
332
333#[derive(Debug, Clone, PartialEq)]
334pub struct SelectCase {
335    pub variable: String,
336    pub channel: Box<SNode>,
337    pub body: Vec<SNode>,
338}
339
340#[derive(Debug, Clone, PartialEq)]
341pub struct DictEntry {
342    pub key: SNode,
343    pub value: SNode,
344}
345
346/// An enum variant declaration.
347#[derive(Debug, Clone, PartialEq)]
348pub struct EnumVariant {
349    pub name: String,
350    pub fields: Vec<TypedParam>,
351}
352
353/// A struct field declaration.
354#[derive(Debug, Clone, PartialEq)]
355pub struct StructField {
356    pub name: String,
357    pub type_expr: Option<TypeExpr>,
358    pub optional: bool,
359}
360
361/// An interface method signature.
362#[derive(Debug, Clone, PartialEq)]
363pub struct InterfaceMethod {
364    pub name: String,
365    pub type_params: Vec<TypeParam>,
366    pub params: Vec<TypedParam>,
367    pub return_type: Option<TypeExpr>,
368}
369
370/// A type annotation (optional, for runtime checking).
371#[derive(Debug, Clone, PartialEq)]
372pub enum TypeExpr {
373    /// A named type: int, string, float, bool, nil, list, dict, closure,
374    /// or a user-defined type name.
375    Named(String),
376    /// A union type: `string | nil`, `int | float`.
377    Union(Vec<TypeExpr>),
378    /// A dict shape type: `{name: string, age: int, active?: bool}`.
379    Shape(Vec<ShapeField>),
380    /// A list type: `list<int>`.
381    List(Box<TypeExpr>),
382    /// A dict type with key and value types: `dict<string, int>`.
383    DictType(Box<TypeExpr>, Box<TypeExpr>),
384    /// A lazy iterator type: `iter<int>`. Yields values of the inner type
385    /// via the combinator/sink protocol (`VmValue::Iter` at runtime).
386    Iter(Box<TypeExpr>),
387    /// A generic type application: `Option<int>`, `Result<string, int>`.
388    Applied { name: String, args: Vec<TypeExpr> },
389    /// A function type: `fn(int, string) -> bool`.
390    FnType {
391        params: Vec<TypeExpr>,
392        return_type: Box<TypeExpr>,
393    },
394    /// The bottom type: the type of expressions that never produce a value
395    /// (return, throw, break, continue).
396    Never,
397    /// A string-literal type: `"pass"`, `"fail"`. Assignable to `string`.
398    /// Used in unions to represent enum-like discriminated values.
399    LitString(String),
400    /// An int-literal type: `0`, `1`, `-1`. Assignable to `int`.
401    LitInt(i64),
402}
403
404/// A field in a dict shape type.
405#[derive(Debug, Clone, PartialEq)]
406pub struct ShapeField {
407    pub name: String,
408    pub type_expr: TypeExpr,
409    pub optional: bool,
410}
411
412/// A binding pattern for destructuring in let/var/for-in.
413#[derive(Debug, Clone, PartialEq)]
414pub enum BindingPattern {
415    /// Simple identifier: `let x = ...`
416    Identifier(String),
417    /// Dict destructuring: `let {name, age} = ...`
418    Dict(Vec<DictPatternField>),
419    /// List destructuring: `let [a, b] = ...`
420    List(Vec<ListPatternElement>),
421    /// Pair destructuring for `for (a, b) in iter { ... }`. The iter must
422    /// yield `VmValue::Pair` values. Not valid in let/var bindings.
423    Pair(String, String),
424}
425
426/// A field in a dict destructuring pattern.
427#[derive(Debug, Clone, PartialEq)]
428pub struct DictPatternField {
429    /// The dict key to extract.
430    pub key: String,
431    /// Renamed binding (if different from key), e.g. `{name: alias}`.
432    pub alias: Option<String>,
433    /// True for `...rest` (rest pattern).
434    pub is_rest: bool,
435    /// Default value if the key is missing (nil), e.g. `{name = "default"}`.
436    pub default_value: Option<Box<SNode>>,
437}
438
439/// An element in a list destructuring pattern.
440#[derive(Debug, Clone, PartialEq)]
441pub struct ListPatternElement {
442    /// The variable name to bind.
443    pub name: String,
444    /// True for `...rest` (rest pattern).
445    pub is_rest: bool,
446    /// Default value if the index is out of bounds (nil), e.g. `[a = 0]`.
447    pub default_value: Option<Box<SNode>>,
448}
449
450/// Declared variance of a generic type parameter.
451///
452/// - `Invariant` (default, no marker): the parameter appears in both
453///   input and output positions, or mutable state. `T<A>` and `T<B>`
454///   are unrelated unless `A == B`.
455/// - `Covariant` (`out T`): the parameter appears only in output
456///   positions (produced, not consumed). `T<Sub>` flows into
457///   `T<Super>`.
458/// - `Contravariant` (`in T`): the parameter appears only in input
459///   positions (consumed, not produced). `T<Super>` flows into
460///   `T<Sub>`.
461#[derive(Debug, Clone, Copy, PartialEq, Eq)]
462pub enum Variance {
463    Invariant,
464    Covariant,
465    Contravariant,
466}
467
468/// A generic type parameter on a function or pipeline declaration.
469#[derive(Debug, Clone, PartialEq)]
470pub struct TypeParam {
471    pub name: String,
472    pub variance: Variance,
473}
474
475impl TypeParam {
476    /// Construct an invariant type parameter (the default for
477    /// unannotated `<T>`).
478    pub fn invariant(name: impl Into<String>) -> Self {
479        Self {
480            name: name.into(),
481            variance: Variance::Invariant,
482        }
483    }
484}
485
486/// A where-clause constraint on a generic type parameter.
487#[derive(Debug, Clone, PartialEq)]
488pub struct WhereClause {
489    pub type_name: String,
490    pub bound: String,
491}
492
493/// A parameter with an optional type annotation and optional default value.
494#[derive(Debug, Clone, PartialEq)]
495pub struct TypedParam {
496    pub name: String,
497    pub type_expr: Option<TypeExpr>,
498    pub default_value: Option<Box<SNode>>,
499    /// If true, this is a rest parameter (`...name`) that collects remaining arguments.
500    pub rest: bool,
501}
502
503impl TypedParam {
504    /// Create an untyped parameter.
505    pub fn untyped(name: impl Into<String>) -> Self {
506        Self {
507            name: name.into(),
508            type_expr: None,
509            default_value: None,
510            rest: false,
511        }
512    }
513
514    /// Create a typed parameter.
515    pub fn typed(name: impl Into<String>, type_expr: TypeExpr) -> Self {
516        Self {
517            name: name.into(),
518            type_expr: Some(type_expr),
519            default_value: None,
520            rest: false,
521        }
522    }
523
524    /// Extract just the names from a list of typed params.
525    pub fn names(params: &[TypedParam]) -> Vec<String> {
526        params.iter().map(|p| p.name.clone()).collect()
527    }
528
529    /// Return the index of the first parameter with a default value, or None.
530    pub fn default_start(params: &[TypedParam]) -> Option<usize> {
531        params.iter().position(|p| p.default_value.is_some())
532    }
533}