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/// If `node` is an `AttributedDecl`, returns `(attrs, inner)`; otherwise
32/// returns an empty attribute slice and the node itself. Use at the top
33/// of any consumer that processes top-level statements so attributes
34/// flow through transparently.
35pub fn peel_attributes(node: &SNode) -> (&[Attribute], &SNode) {
36    match &node.node {
37        Node::AttributedDecl { attributes, inner } => (attributes.as_slice(), inner.as_ref()),
38        _ => (&[], node),
39    }
40}
41
42/// A single argument to an attribute. Positional args have `name = None`;
43/// named args use `name: Some("key")`. Values are restricted to literal
44/// expressions by the parser (string/int/float/bool/nil/identifier).
45#[derive(Debug, Clone, PartialEq)]
46pub struct AttributeArg {
47    pub name: Option<String>,
48    pub value: SNode,
49    pub span: Span,
50}
51
52/// An attribute attached to a declaration: `@deprecated(since: "0.8")`.
53#[derive(Debug, Clone, PartialEq)]
54pub struct Attribute {
55    pub name: String,
56    pub args: Vec<AttributeArg>,
57    pub span: Span,
58}
59
60impl Attribute {
61    /// Find a named argument by key.
62    pub fn named_arg(&self, key: &str) -> Option<&SNode> {
63        self.args
64            .iter()
65            .find(|a| a.name.as_deref() == Some(key))
66            .map(|a| &a.value)
67    }
68
69    /// First positional argument, if any.
70    pub fn positional(&self, idx: usize) -> Option<&SNode> {
71        self.args
72            .iter()
73            .filter(|a| a.name.is_none())
74            .nth(idx)
75            .map(|a| &a.value)
76    }
77
78    /// Convenience: extract a string-literal arg by name.
79    pub fn string_arg(&self, key: &str) -> Option<String> {
80        match self.named_arg(key).map(|n| &n.node) {
81            Some(Node::StringLiteral(s)) => Some(s.clone()),
82            Some(Node::RawStringLiteral(s)) => Some(s.clone()),
83            _ => None,
84        }
85    }
86}
87
88/// AST nodes for the Harn language.
89#[derive(Debug, Clone, PartialEq)]
90pub enum Node {
91    /// A declaration carrying one or more attributes (`@attr`). The inner
92    /// node is always one of: FnDecl, ToolDecl, Pipeline, StructDecl,
93    /// EnumDecl, TypeDecl, InterfaceDecl, ImplBlock.
94    AttributedDecl {
95        attributes: Vec<Attribute>,
96        inner: Box<SNode>,
97    },
98    Pipeline {
99        name: String,
100        params: Vec<String>,
101        body: Vec<SNode>,
102        extends: Option<String>,
103        is_pub: bool,
104    },
105    LetBinding {
106        pattern: BindingPattern,
107        type_ann: Option<TypeExpr>,
108        value: Box<SNode>,
109    },
110    VarBinding {
111        pattern: BindingPattern,
112        type_ann: Option<TypeExpr>,
113        value: Box<SNode>,
114    },
115    OverrideDecl {
116        name: String,
117        params: Vec<String>,
118        body: Vec<SNode>,
119    },
120    ImportDecl {
121        path: String,
122    },
123    /// Selective import: import { foo, bar } from "module"
124    SelectiveImport {
125        names: Vec<String>,
126        path: String,
127    },
128    EnumDecl {
129        name: String,
130        type_params: Vec<TypeParam>,
131        variants: Vec<EnumVariant>,
132        is_pub: bool,
133    },
134    StructDecl {
135        name: String,
136        type_params: Vec<TypeParam>,
137        fields: Vec<StructField>,
138        is_pub: bool,
139    },
140    InterfaceDecl {
141        name: String,
142        type_params: Vec<TypeParam>,
143        associated_types: Vec<(String, Option<TypeExpr>)>,
144        methods: Vec<InterfaceMethod>,
145    },
146    /// Impl block: impl TypeName { fn method(self, ...) { ... } ... }
147    ImplBlock {
148        type_name: String,
149        methods: Vec<SNode>,
150    },
151
152    IfElse {
153        condition: Box<SNode>,
154        then_body: Vec<SNode>,
155        else_body: Option<Vec<SNode>>,
156    },
157    ForIn {
158        pattern: BindingPattern,
159        iterable: Box<SNode>,
160        body: Vec<SNode>,
161    },
162    MatchExpr {
163        value: Box<SNode>,
164        arms: Vec<MatchArm>,
165    },
166    WhileLoop {
167        condition: Box<SNode>,
168        body: Vec<SNode>,
169    },
170    Retry {
171        count: Box<SNode>,
172        body: Vec<SNode>,
173    },
174    ReturnStmt {
175        value: Option<Box<SNode>>,
176    },
177    TryCatch {
178        body: Vec<SNode>,
179        error_var: Option<String>,
180        error_type: Option<TypeExpr>,
181        catch_body: Vec<SNode>,
182        finally_body: Option<Vec<SNode>>,
183    },
184    /// Try expression: try { body } — returns Result.Ok(value) or Result.Err(error).
185    TryExpr {
186        body: Vec<SNode>,
187    },
188    FnDecl {
189        name: String,
190        type_params: Vec<TypeParam>,
191        params: Vec<TypedParam>,
192        return_type: Option<TypeExpr>,
193        where_clauses: Vec<WhereClause>,
194        body: Vec<SNode>,
195        is_pub: bool,
196    },
197    ToolDecl {
198        name: String,
199        description: Option<String>,
200        params: Vec<TypedParam>,
201        return_type: Option<TypeExpr>,
202        body: Vec<SNode>,
203        is_pub: bool,
204    },
205    TypeDecl {
206        name: String,
207        type_params: Vec<TypeParam>,
208        type_expr: TypeExpr,
209    },
210    SpawnExpr {
211        body: Vec<SNode>,
212    },
213    /// Duration literal: 500ms, 5s, 30m, 2h, 1d, 1w
214    DurationLiteral(u64),
215    /// Range expression: `start to end` (inclusive) or `start to end exclusive` (half-open)
216    RangeExpr {
217        start: Box<SNode>,
218        end: Box<SNode>,
219        inclusive: bool,
220    },
221    /// Guard clause: guard condition else { body }
222    GuardStmt {
223        condition: Box<SNode>,
224        else_body: Vec<SNode>,
225    },
226    RequireStmt {
227        condition: Box<SNode>,
228        message: Option<Box<SNode>>,
229    },
230    /// Defer statement: defer { body } — runs body at scope exit.
231    DeferStmt {
232        body: Vec<SNode>,
233    },
234    /// Deadline block: deadline DURATION { body }
235    DeadlineBlock {
236        duration: Box<SNode>,
237        body: Vec<SNode>,
238    },
239    /// Yield expression: yields control to host, optionally with a value.
240    YieldExpr {
241        value: Option<Box<SNode>>,
242    },
243    /// Mutex block: mutual exclusion for concurrent access.
244    MutexBlock {
245        body: Vec<SNode>,
246    },
247    /// Break out of a loop.
248    BreakStmt,
249    /// Continue to next loop iteration.
250    ContinueStmt,
251
252    Parallel {
253        mode: ParallelMode,
254        /// For Count mode: the count expression. For Each/Settle: the list expression.
255        expr: Box<SNode>,
256        variable: Option<String>,
257        body: Vec<SNode>,
258        /// Optional trailing `with { max_concurrent: N, ... }` option block.
259        /// A vec (rather than a dict) preserves source order for error
260        /// reporting and keeps parsing cheap. Only `max_concurrent` is
261        /// currently honored; unknown keys are rejected by the parser.
262        options: Vec<(String, SNode)>,
263    },
264
265    SelectExpr {
266        cases: Vec<SelectCase>,
267        timeout: Option<(Box<SNode>, Vec<SNode>)>,
268        default_body: Option<Vec<SNode>>,
269    },
270
271    FunctionCall {
272        name: String,
273        args: Vec<SNode>,
274    },
275    MethodCall {
276        object: Box<SNode>,
277        method: String,
278        args: Vec<SNode>,
279    },
280    /// Optional method call: `obj?.method(args)` — returns nil if obj is nil.
281    OptionalMethodCall {
282        object: Box<SNode>,
283        method: String,
284        args: Vec<SNode>,
285    },
286    PropertyAccess {
287        object: Box<SNode>,
288        property: String,
289    },
290    /// Optional chaining: `obj?.property` — returns nil if obj is nil.
291    OptionalPropertyAccess {
292        object: Box<SNode>,
293        property: String,
294    },
295    SubscriptAccess {
296        object: Box<SNode>,
297        index: Box<SNode>,
298    },
299    SliceAccess {
300        object: Box<SNode>,
301        start: Option<Box<SNode>>,
302        end: Option<Box<SNode>>,
303    },
304    BinaryOp {
305        op: String,
306        left: Box<SNode>,
307        right: Box<SNode>,
308    },
309    UnaryOp {
310        op: String,
311        operand: Box<SNode>,
312    },
313    Ternary {
314        condition: Box<SNode>,
315        true_expr: Box<SNode>,
316        false_expr: Box<SNode>,
317    },
318    Assignment {
319        target: Box<SNode>,
320        value: Box<SNode>,
321        /// None = plain `=`, Some("+") = `+=`, etc.
322        op: Option<String>,
323    },
324    ThrowStmt {
325        value: Box<SNode>,
326    },
327
328    /// Enum variant construction: EnumName.Variant(args)
329    EnumConstruct {
330        enum_name: String,
331        variant: String,
332        args: Vec<SNode>,
333    },
334    /// Struct construction: StructName { field: value, ... }
335    StructConstruct {
336        struct_name: String,
337        fields: Vec<DictEntry>,
338    },
339
340    InterpolatedString(Vec<StringSegment>),
341    StringLiteral(String),
342    /// Raw string literal `r"..."` — no escape processing.
343    RawStringLiteral(String),
344    IntLiteral(i64),
345    FloatLiteral(f64),
346    BoolLiteral(bool),
347    NilLiteral,
348    Identifier(String),
349    ListLiteral(Vec<SNode>),
350    DictLiteral(Vec<DictEntry>),
351    /// Spread expression `...expr` inside list/dict literals.
352    Spread(Box<SNode>),
353    /// Try operator: expr? — unwraps Result.Ok or propagates Result.Err.
354    TryOperator {
355        operand: Box<SNode>,
356    },
357    /// Try-star operator: `try* EXPR` — evaluates EXPR; on throw, runs
358    /// pending finally blocks up to the enclosing catch and rethrows
359    /// the original value. On success, evaluates to EXPR's value.
360    /// Lowered per spec/HARN_SPEC.md as:
361    ///   { let _r = try { EXPR }
362    ///     guard is_ok(_r) else { throw unwrap_err(_r) }
363    ///     unwrap(_r) }
364    TryStar {
365        operand: Box<SNode>,
366    },
367
368    Block(Vec<SNode>),
369    Closure {
370        params: Vec<TypedParam>,
371        body: Vec<SNode>,
372        /// When true, this closure was written as `fn(params) { body }`.
373        /// The formatter preserves this distinction.
374        fn_syntax: bool,
375    },
376}
377
378/// Parallel execution mode.
379#[derive(Debug, Clone, Copy, PartialEq)]
380pub enum ParallelMode {
381    /// `parallel N { i -> ... }` — run N concurrent tasks.
382    Count,
383    /// `parallel each list { item -> ... }` — map over list concurrently.
384    Each,
385    /// `parallel settle list { item -> ... }` — map with error collection.
386    Settle,
387}
388
389#[derive(Debug, Clone, PartialEq)]
390pub struct MatchArm {
391    pub pattern: SNode,
392    /// Optional guard: `pattern if condition -> { body }`.
393    pub guard: Option<Box<SNode>>,
394    pub body: Vec<SNode>,
395}
396
397#[derive(Debug, Clone, PartialEq)]
398pub struct SelectCase {
399    pub variable: String,
400    pub channel: Box<SNode>,
401    pub body: Vec<SNode>,
402}
403
404#[derive(Debug, Clone, PartialEq)]
405pub struct DictEntry {
406    pub key: SNode,
407    pub value: SNode,
408}
409
410/// An enum variant declaration.
411#[derive(Debug, Clone, PartialEq)]
412pub struct EnumVariant {
413    pub name: String,
414    pub fields: Vec<TypedParam>,
415}
416
417/// A struct field declaration.
418#[derive(Debug, Clone, PartialEq)]
419pub struct StructField {
420    pub name: String,
421    pub type_expr: Option<TypeExpr>,
422    pub optional: bool,
423}
424
425/// An interface method signature.
426#[derive(Debug, Clone, PartialEq)]
427pub struct InterfaceMethod {
428    pub name: String,
429    pub type_params: Vec<TypeParam>,
430    pub params: Vec<TypedParam>,
431    pub return_type: Option<TypeExpr>,
432}
433
434/// A type annotation (optional, for runtime checking).
435#[derive(Debug, Clone, PartialEq)]
436pub enum TypeExpr {
437    /// A named type: int, string, float, bool, nil, list, dict, closure,
438    /// or a user-defined type name.
439    Named(String),
440    /// A union type: `string | nil`, `int | float`.
441    Union(Vec<TypeExpr>),
442    /// A dict shape type: `{name: string, age: int, active?: bool}`.
443    Shape(Vec<ShapeField>),
444    /// A list type: `list<int>`.
445    List(Box<TypeExpr>),
446    /// A dict type with key and value types: `dict<string, int>`.
447    DictType(Box<TypeExpr>, Box<TypeExpr>),
448    /// A lazy iterator type: `iter<int>`. Yields values of the inner type
449    /// via the combinator/sink protocol (`VmValue::Iter` at runtime).
450    Iter(Box<TypeExpr>),
451    /// A generic type application: `Option<int>`, `Result<string, int>`.
452    Applied { name: String, args: Vec<TypeExpr> },
453    /// A function type: `fn(int, string) -> bool`.
454    FnType {
455        params: Vec<TypeExpr>,
456        return_type: Box<TypeExpr>,
457    },
458    /// The bottom type: the type of expressions that never produce a value
459    /// (return, throw, break, continue).
460    Never,
461    /// A string-literal type: `"pass"`, `"fail"`. Assignable to `string`.
462    /// Used in unions to represent enum-like discriminated values.
463    LitString(String),
464    /// An int-literal type: `0`, `1`, `-1`. Assignable to `int`.
465    LitInt(i64),
466}
467
468/// A field in a dict shape type.
469#[derive(Debug, Clone, PartialEq)]
470pub struct ShapeField {
471    pub name: String,
472    pub type_expr: TypeExpr,
473    pub optional: bool,
474}
475
476/// A binding pattern for destructuring in let/var/for-in.
477#[derive(Debug, Clone, PartialEq)]
478pub enum BindingPattern {
479    /// Simple identifier: `let x = ...`
480    Identifier(String),
481    /// Dict destructuring: `let {name, age} = ...`
482    Dict(Vec<DictPatternField>),
483    /// List destructuring: `let [a, b] = ...`
484    List(Vec<ListPatternElement>),
485    /// Pair destructuring for `for (a, b) in iter { ... }`. The iter must
486    /// yield `VmValue::Pair` values. Not valid in let/var bindings.
487    Pair(String, String),
488}
489
490/// A field in a dict destructuring pattern.
491#[derive(Debug, Clone, PartialEq)]
492pub struct DictPatternField {
493    /// The dict key to extract.
494    pub key: String,
495    /// Renamed binding (if different from key), e.g. `{name: alias}`.
496    pub alias: Option<String>,
497    /// True for `...rest` (rest pattern).
498    pub is_rest: bool,
499    /// Default value if the key is missing (nil), e.g. `{name = "default"}`.
500    pub default_value: Option<Box<SNode>>,
501}
502
503/// An element in a list destructuring pattern.
504#[derive(Debug, Clone, PartialEq)]
505pub struct ListPatternElement {
506    /// The variable name to bind.
507    pub name: String,
508    /// True for `...rest` (rest pattern).
509    pub is_rest: bool,
510    /// Default value if the index is out of bounds (nil), e.g. `[a = 0]`.
511    pub default_value: Option<Box<SNode>>,
512}
513
514/// Declared variance of a generic type parameter.
515///
516/// - `Invariant` (default, no marker): the parameter appears in both
517///   input and output positions, or mutable state. `T<A>` and `T<B>`
518///   are unrelated unless `A == B`.
519/// - `Covariant` (`out T`): the parameter appears only in output
520///   positions (produced, not consumed). `T<Sub>` flows into
521///   `T<Super>`.
522/// - `Contravariant` (`in T`): the parameter appears only in input
523///   positions (consumed, not produced). `T<Super>` flows into
524///   `T<Sub>`.
525#[derive(Debug, Clone, Copy, PartialEq, Eq)]
526pub enum Variance {
527    Invariant,
528    Covariant,
529    Contravariant,
530}
531
532/// A generic type parameter on a function or pipeline declaration.
533#[derive(Debug, Clone, PartialEq)]
534pub struct TypeParam {
535    pub name: String,
536    pub variance: Variance,
537}
538
539impl TypeParam {
540    /// Construct an invariant type parameter (the default for
541    /// unannotated `<T>`).
542    pub fn invariant(name: impl Into<String>) -> Self {
543        Self {
544            name: name.into(),
545            variance: Variance::Invariant,
546        }
547    }
548}
549
550/// A where-clause constraint on a generic type parameter.
551#[derive(Debug, Clone, PartialEq)]
552pub struct WhereClause {
553    pub type_name: String,
554    pub bound: String,
555}
556
557/// A parameter with an optional type annotation and optional default value.
558#[derive(Debug, Clone, PartialEq)]
559pub struct TypedParam {
560    pub name: String,
561    pub type_expr: Option<TypeExpr>,
562    pub default_value: Option<Box<SNode>>,
563    /// If true, this is a rest parameter (`...name`) that collects remaining arguments.
564    pub rest: bool,
565}
566
567impl TypedParam {
568    /// Create an untyped parameter.
569    pub fn untyped(name: impl Into<String>) -> Self {
570        Self {
571            name: name.into(),
572            type_expr: None,
573            default_value: None,
574            rest: false,
575        }
576    }
577
578    /// Create a typed parameter.
579    pub fn typed(name: impl Into<String>, type_expr: TypeExpr) -> Self {
580        Self {
581            name: name.into(),
582            type_expr: Some(type_expr),
583            default_value: None,
584            rest: false,
585        }
586    }
587
588    /// Extract just the names from a list of typed params.
589    pub fn names(params: &[TypedParam]) -> Vec<String> {
590        params.iter().map(|p| p.name.clone()).collect()
591    }
592
593    /// Return the index of the first parameter with a default value, or None.
594    pub fn default_start(params: &[TypedParam]) -> Option<usize> {
595        params.iter().position(|p| p.default_value.is_some())
596    }
597}