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