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