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    // Declarations
35    Pipeline {
36        name: String,
37        params: Vec<String>,
38        body: Vec<SNode>,
39        extends: Option<String>,
40        is_pub: bool,
41    },
42    LetBinding {
43        pattern: BindingPattern,
44        type_ann: Option<TypeExpr>,
45        value: Box<SNode>,
46    },
47    VarBinding {
48        pattern: BindingPattern,
49        type_ann: Option<TypeExpr>,
50        value: Box<SNode>,
51    },
52    OverrideDecl {
53        name: String,
54        params: Vec<String>,
55        body: Vec<SNode>,
56    },
57    ImportDecl {
58        path: String,
59    },
60    /// Selective import: import { foo, bar } from "module"
61    SelectiveImport {
62        names: Vec<String>,
63        path: String,
64    },
65    EnumDecl {
66        name: String,
67        type_params: Vec<TypeParam>,
68        variants: Vec<EnumVariant>,
69        is_pub: bool,
70    },
71    StructDecl {
72        name: String,
73        type_params: Vec<TypeParam>,
74        fields: Vec<StructField>,
75        is_pub: bool,
76    },
77    InterfaceDecl {
78        name: String,
79        type_params: Vec<TypeParam>,
80        associated_types: Vec<(String, Option<TypeExpr>)>,
81        methods: Vec<InterfaceMethod>,
82    },
83    /// Impl block: impl TypeName { fn method(self, ...) { ... } ... }
84    ImplBlock {
85        type_name: String,
86        methods: Vec<SNode>,
87    },
88
89    // Control flow
90    IfElse {
91        condition: Box<SNode>,
92        then_body: Vec<SNode>,
93        else_body: Option<Vec<SNode>>,
94    },
95    ForIn {
96        pattern: BindingPattern,
97        iterable: Box<SNode>,
98        body: Vec<SNode>,
99    },
100    MatchExpr {
101        value: Box<SNode>,
102        arms: Vec<MatchArm>,
103    },
104    WhileLoop {
105        condition: Box<SNode>,
106        body: Vec<SNode>,
107    },
108    Retry {
109        count: Box<SNode>,
110        body: Vec<SNode>,
111    },
112    ReturnStmt {
113        value: Option<Box<SNode>>,
114    },
115    TryCatch {
116        body: Vec<SNode>,
117        error_var: Option<String>,
118        error_type: Option<TypeExpr>,
119        catch_body: Vec<SNode>,
120        finally_body: Option<Vec<SNode>>,
121    },
122    /// Try expression: try { body } — returns Result.Ok(value) or Result.Err(error).
123    TryExpr {
124        body: Vec<SNode>,
125    },
126    FnDecl {
127        name: String,
128        type_params: Vec<TypeParam>,
129        params: Vec<TypedParam>,
130        return_type: Option<TypeExpr>,
131        where_clauses: Vec<WhereClause>,
132        body: Vec<SNode>,
133        is_pub: bool,
134    },
135    ToolDecl {
136        name: String,
137        description: Option<String>,
138        params: Vec<TypedParam>,
139        return_type: Option<TypeExpr>,
140        body: Vec<SNode>,
141        is_pub: bool,
142    },
143    TypeDecl {
144        name: String,
145        type_expr: TypeExpr,
146    },
147    SpawnExpr {
148        body: Vec<SNode>,
149    },
150    /// Duration literal: 500ms, 5s, 30m, 2h, 1d, 1w
151    DurationLiteral(u64),
152    /// Range expression: start upto end (exclusive) or start thru end (inclusive)
153    RangeExpr {
154        start: Box<SNode>,
155        end: Box<SNode>,
156        inclusive: bool,
157    },
158    /// Guard clause: guard condition else { body }
159    GuardStmt {
160        condition: Box<SNode>,
161        else_body: Vec<SNode>,
162    },
163    RequireStmt {
164        condition: Box<SNode>,
165        message: Option<Box<SNode>>,
166    },
167    /// Defer statement: defer { body } — runs body at scope exit.
168    DeferStmt {
169        body: Vec<SNode>,
170    },
171    /// Deadline block: deadline DURATION { body }
172    DeadlineBlock {
173        duration: Box<SNode>,
174        body: Vec<SNode>,
175    },
176    /// Yield expression: yields control to host, optionally with a value.
177    YieldExpr {
178        value: Option<Box<SNode>>,
179    },
180    /// Mutex block: mutual exclusion for concurrent access.
181    MutexBlock {
182        body: Vec<SNode>,
183    },
184    /// Break out of a loop.
185    BreakStmt,
186    /// Continue to next loop iteration.
187    ContinueStmt,
188
189    // Concurrency
190    Parallel {
191        mode: ParallelMode,
192        /// For Count mode: the count expression. For Each/Settle: the list expression.
193        expr: Box<SNode>,
194        variable: Option<String>,
195        body: Vec<SNode>,
196        /// Optional trailing `with { max_concurrent: N, ... }` option block.
197        /// A vec (rather than a dict) preserves source order for error
198        /// reporting and keeps parsing cheap. Only `max_concurrent` is
199        /// currently honored; unknown keys are rejected by the parser.
200        options: Vec<(String, SNode)>,
201    },
202
203    SelectExpr {
204        cases: Vec<SelectCase>,
205        timeout: Option<(Box<SNode>, Vec<SNode>)>,
206        default_body: Option<Vec<SNode>>,
207    },
208
209    // Expressions
210    FunctionCall {
211        name: String,
212        args: Vec<SNode>,
213    },
214    MethodCall {
215        object: Box<SNode>,
216        method: String,
217        args: Vec<SNode>,
218    },
219    /// Optional method call: `obj?.method(args)` — returns nil if obj is nil.
220    OptionalMethodCall {
221        object: Box<SNode>,
222        method: String,
223        args: Vec<SNode>,
224    },
225    PropertyAccess {
226        object: Box<SNode>,
227        property: String,
228    },
229    /// Optional chaining: `obj?.property` — returns nil if obj is nil.
230    OptionalPropertyAccess {
231        object: Box<SNode>,
232        property: String,
233    },
234    SubscriptAccess {
235        object: Box<SNode>,
236        index: Box<SNode>,
237    },
238    SliceAccess {
239        object: Box<SNode>,
240        start: Option<Box<SNode>>,
241        end: Option<Box<SNode>>,
242    },
243    BinaryOp {
244        op: String,
245        left: Box<SNode>,
246        right: Box<SNode>,
247    },
248    UnaryOp {
249        op: String,
250        operand: Box<SNode>,
251    },
252    Ternary {
253        condition: Box<SNode>,
254        true_expr: Box<SNode>,
255        false_expr: Box<SNode>,
256    },
257    Assignment {
258        target: Box<SNode>,
259        value: Box<SNode>,
260        /// None = plain `=`, Some("+") = `+=`, etc.
261        op: Option<String>,
262    },
263    ThrowStmt {
264        value: Box<SNode>,
265    },
266
267    /// Enum variant construction: EnumName.Variant(args)
268    EnumConstruct {
269        enum_name: String,
270        variant: String,
271        args: Vec<SNode>,
272    },
273    /// Struct construction: StructName { field: value, ... }
274    StructConstruct {
275        struct_name: String,
276        fields: Vec<DictEntry>,
277    },
278
279    // Literals
280    InterpolatedString(Vec<StringSegment>),
281    StringLiteral(String),
282    /// Raw string literal `r"..."` — no escape processing.
283    RawStringLiteral(String),
284    IntLiteral(i64),
285    FloatLiteral(f64),
286    BoolLiteral(bool),
287    NilLiteral,
288    Identifier(String),
289    ListLiteral(Vec<SNode>),
290    DictLiteral(Vec<DictEntry>),
291    /// Spread expression `...expr` inside list/dict literals.
292    Spread(Box<SNode>),
293    /// Try operator: expr? — unwraps Result.Ok or propagates Result.Err.
294    TryOperator {
295        operand: Box<SNode>,
296    },
297
298    // Blocks
299    Block(Vec<SNode>),
300    Closure {
301        params: Vec<TypedParam>,
302        body: Vec<SNode>,
303        /// When true, this closure was written as `fn(params) { body }`.
304        /// The formatter preserves this distinction.
305        fn_syntax: bool,
306    },
307}
308
309/// Parallel execution mode.
310#[derive(Debug, Clone, Copy, PartialEq)]
311pub enum ParallelMode {
312    /// `parallel N { i -> ... }` — run N concurrent tasks.
313    Count,
314    /// `parallel each list { item -> ... }` — map over list concurrently.
315    Each,
316    /// `parallel settle list { item -> ... }` — map with error collection.
317    Settle,
318}
319
320#[derive(Debug, Clone, PartialEq)]
321pub struct MatchArm {
322    pub pattern: SNode,
323    /// Optional guard: `pattern if condition -> { body }`.
324    pub guard: Option<Box<SNode>>,
325    pub body: Vec<SNode>,
326}
327
328#[derive(Debug, Clone, PartialEq)]
329pub struct SelectCase {
330    pub variable: String,
331    pub channel: Box<SNode>,
332    pub body: Vec<SNode>,
333}
334
335#[derive(Debug, Clone, PartialEq)]
336pub struct DictEntry {
337    pub key: SNode,
338    pub value: SNode,
339}
340
341/// An enum variant declaration.
342#[derive(Debug, Clone, PartialEq)]
343pub struct EnumVariant {
344    pub name: String,
345    pub fields: Vec<TypedParam>,
346}
347
348/// A struct field declaration.
349#[derive(Debug, Clone, PartialEq)]
350pub struct StructField {
351    pub name: String,
352    pub type_expr: Option<TypeExpr>,
353    pub optional: bool,
354}
355
356/// An interface method signature.
357#[derive(Debug, Clone, PartialEq)]
358pub struct InterfaceMethod {
359    pub name: String,
360    pub type_params: Vec<TypeParam>,
361    pub params: Vec<TypedParam>,
362    pub return_type: Option<TypeExpr>,
363}
364
365/// A type annotation (optional, for runtime checking).
366#[derive(Debug, Clone, PartialEq)]
367pub enum TypeExpr {
368    /// A named type: int, string, float, bool, nil, list, dict, closure,
369    /// or a user-defined type name.
370    Named(String),
371    /// A union type: `string | nil`, `int | float`.
372    Union(Vec<TypeExpr>),
373    /// A dict shape type: `{name: string, age: int, active?: bool}`.
374    Shape(Vec<ShapeField>),
375    /// A list type: `list<int>`.
376    List(Box<TypeExpr>),
377    /// A dict type with key and value types: `dict<string, int>`.
378    DictType(Box<TypeExpr>, Box<TypeExpr>),
379    /// A generic type application: `Option<int>`, `Result<string, int>`.
380    Applied { name: String, args: Vec<TypeExpr> },
381    /// A function type: `fn(int, string) -> bool`.
382    FnType {
383        params: Vec<TypeExpr>,
384        return_type: Box<TypeExpr>,
385    },
386    /// The bottom type: the type of expressions that never produce a value
387    /// (return, throw, break, continue).
388    Never,
389}
390
391/// A field in a dict shape type.
392#[derive(Debug, Clone, PartialEq)]
393pub struct ShapeField {
394    pub name: String,
395    pub type_expr: TypeExpr,
396    pub optional: bool,
397}
398
399/// A binding pattern for destructuring in let/var/for-in.
400#[derive(Debug, Clone, PartialEq)]
401pub enum BindingPattern {
402    /// Simple identifier: `let x = ...`
403    Identifier(String),
404    /// Dict destructuring: `let {name, age} = ...`
405    Dict(Vec<DictPatternField>),
406    /// List destructuring: `let [a, b] = ...`
407    List(Vec<ListPatternElement>),
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}