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