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        variants: Vec<EnumVariant>,
68        is_pub: bool,
69    },
70    StructDecl {
71        name: String,
72        fields: Vec<StructField>,
73        is_pub: bool,
74    },
75    InterfaceDecl {
76        name: String,
77        type_params: Vec<TypeParam>,
78        methods: Vec<InterfaceMethod>,
79    },
80    /// Impl block: impl TypeName { fn method(self, ...) { ... } ... }
81    ImplBlock {
82        type_name: String,
83        methods: Vec<SNode>,
84    },
85
86    // Control flow
87    IfElse {
88        condition: Box<SNode>,
89        then_body: Vec<SNode>,
90        else_body: Option<Vec<SNode>>,
91    },
92    ForIn {
93        pattern: BindingPattern,
94        iterable: Box<SNode>,
95        body: Vec<SNode>,
96    },
97    MatchExpr {
98        value: Box<SNode>,
99        arms: Vec<MatchArm>,
100    },
101    WhileLoop {
102        condition: Box<SNode>,
103        body: Vec<SNode>,
104    },
105    Retry {
106        count: Box<SNode>,
107        body: Vec<SNode>,
108    },
109    ReturnStmt {
110        value: Option<Box<SNode>>,
111    },
112    TryCatch {
113        body: Vec<SNode>,
114        error_var: Option<String>,
115        error_type: Option<TypeExpr>,
116        catch_body: Vec<SNode>,
117        finally_body: Option<Vec<SNode>>,
118    },
119    /// Try expression: try { body } — returns Result.Ok(value) or Result.Err(error).
120    TryExpr {
121        body: Vec<SNode>,
122    },
123    FnDecl {
124        name: String,
125        type_params: Vec<TypeParam>,
126        params: Vec<TypedParam>,
127        return_type: Option<TypeExpr>,
128        where_clauses: Vec<WhereClause>,
129        body: Vec<SNode>,
130        is_pub: bool,
131    },
132    ToolDecl {
133        name: String,
134        description: Option<String>,
135        params: Vec<TypedParam>,
136        return_type: Option<TypeExpr>,
137        body: Vec<SNode>,
138        is_pub: bool,
139    },
140    TypeDecl {
141        name: String,
142        type_expr: TypeExpr,
143    },
144    SpawnExpr {
145        body: Vec<SNode>,
146    },
147    /// Duration literal: 500ms, 5s, 30m, 2h
148    DurationLiteral(u64),
149    /// Range expression: start upto end (exclusive) or start thru end (inclusive)
150    RangeExpr {
151        start: Box<SNode>,
152        end: Box<SNode>,
153        inclusive: bool,
154    },
155    /// Guard clause: guard condition else { body }
156    GuardStmt {
157        condition: Box<SNode>,
158        else_body: Vec<SNode>,
159    },
160    RequireStmt {
161        condition: Box<SNode>,
162        message: Option<Box<SNode>>,
163    },
164    /// Ask expression: ask { system: "...", user: "...", ... }
165    AskExpr {
166        fields: Vec<DictEntry>,
167    },
168    /// Deadline block: deadline DURATION { body }
169    DeadlineBlock {
170        duration: Box<SNode>,
171        body: Vec<SNode>,
172    },
173    /// Yield expression: yields control to host, optionally with a value.
174    YieldExpr {
175        value: Option<Box<SNode>>,
176    },
177    /// Mutex block: mutual exclusion for concurrent access.
178    MutexBlock {
179        body: Vec<SNode>,
180    },
181    /// Break out of a loop.
182    BreakStmt,
183    /// Continue to next loop iteration.
184    ContinueStmt,
185
186    // Concurrency
187    Parallel {
188        count: Box<SNode>,
189        variable: Option<String>,
190        body: Vec<SNode>,
191    },
192    ParallelMap {
193        list: Box<SNode>,
194        variable: String,
195        body: Vec<SNode>,
196    },
197    ParallelSettle {
198        list: Box<SNode>,
199        variable: String,
200        body: Vec<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#[derive(Debug, Clone, PartialEq)]
310pub struct MatchArm {
311    pub pattern: SNode,
312    pub body: Vec<SNode>,
313}
314
315#[derive(Debug, Clone, PartialEq)]
316pub struct SelectCase {
317    pub variable: String,
318    pub channel: Box<SNode>,
319    pub body: Vec<SNode>,
320}
321
322#[derive(Debug, Clone, PartialEq)]
323pub struct DictEntry {
324    pub key: SNode,
325    pub value: SNode,
326}
327
328/// An enum variant declaration.
329#[derive(Debug, Clone, PartialEq)]
330pub struct EnumVariant {
331    pub name: String,
332    pub fields: Vec<TypedParam>,
333}
334
335/// A struct field declaration.
336#[derive(Debug, Clone, PartialEq)]
337pub struct StructField {
338    pub name: String,
339    pub type_expr: Option<TypeExpr>,
340    pub optional: bool,
341}
342
343/// An interface method signature.
344#[derive(Debug, Clone, PartialEq)]
345pub struct InterfaceMethod {
346    pub name: String,
347    pub type_params: Vec<TypeParam>,
348    pub params: Vec<TypedParam>,
349    pub return_type: Option<TypeExpr>,
350}
351
352/// A type annotation (optional, for runtime checking).
353#[derive(Debug, Clone, PartialEq)]
354pub enum TypeExpr {
355    /// A named type: int, string, float, bool, nil, list, dict, closure,
356    /// or a user-defined type name.
357    Named(String),
358    /// A union type: `string | nil`, `int | float`.
359    Union(Vec<TypeExpr>),
360    /// A dict shape type: `{name: string, age: int, active?: bool}`.
361    Shape(Vec<ShapeField>),
362    /// A list type: `list<int>`.
363    List(Box<TypeExpr>),
364    /// A dict type with key and value types: `dict<string, int>`.
365    DictType(Box<TypeExpr>, Box<TypeExpr>),
366    /// A function type: `fn(int, string) -> bool`.
367    FnType {
368        params: Vec<TypeExpr>,
369        return_type: Box<TypeExpr>,
370    },
371    /// The bottom type: the type of expressions that never produce a value
372    /// (return, throw, break, continue).
373    Never,
374}
375
376/// A field in a dict shape type.
377#[derive(Debug, Clone, PartialEq)]
378pub struct ShapeField {
379    pub name: String,
380    pub type_expr: TypeExpr,
381    pub optional: bool,
382}
383
384/// A binding pattern for destructuring in let/var/for-in.
385#[derive(Debug, Clone, PartialEq)]
386pub enum BindingPattern {
387    /// Simple identifier: `let x = ...`
388    Identifier(String),
389    /// Dict destructuring: `let {name, age} = ...`
390    Dict(Vec<DictPatternField>),
391    /// List destructuring: `let [a, b] = ...`
392    List(Vec<ListPatternElement>),
393}
394
395/// A field in a dict destructuring pattern.
396#[derive(Debug, Clone, PartialEq)]
397pub struct DictPatternField {
398    /// The dict key to extract.
399    pub key: String,
400    /// Renamed binding (if different from key), e.g. `{name: alias}`.
401    pub alias: Option<String>,
402    /// True for `...rest` (rest pattern).
403    pub is_rest: bool,
404    /// Default value if the key is missing (nil), e.g. `{name = "default"}`.
405    pub default_value: Option<Box<SNode>>,
406}
407
408/// An element in a list destructuring pattern.
409#[derive(Debug, Clone, PartialEq)]
410pub struct ListPatternElement {
411    /// The variable name to bind.
412    pub name: String,
413    /// True for `...rest` (rest pattern).
414    pub is_rest: bool,
415    /// Default value if the index is out of bounds (nil), e.g. `[a = 0]`.
416    pub default_value: Option<Box<SNode>>,
417}
418
419/// A generic type parameter on a function or pipeline declaration.
420#[derive(Debug, Clone, PartialEq)]
421pub struct TypeParam {
422    pub name: String,
423}
424
425/// A where-clause constraint on a generic type parameter.
426#[derive(Debug, Clone, PartialEq)]
427pub struct WhereClause {
428    pub type_name: String,
429    pub bound: String,
430}
431
432/// A parameter with an optional type annotation and optional default value.
433#[derive(Debug, Clone, PartialEq)]
434pub struct TypedParam {
435    pub name: String,
436    pub type_expr: Option<TypeExpr>,
437    pub default_value: Option<Box<SNode>>,
438    /// If true, this is a rest parameter (`...name`) that collects remaining arguments.
439    pub rest: bool,
440}
441
442impl TypedParam {
443    /// Create an untyped parameter.
444    pub fn untyped(name: impl Into<String>) -> Self {
445        Self {
446            name: name.into(),
447            type_expr: None,
448            default_value: None,
449            rest: false,
450        }
451    }
452
453    /// Create a typed parameter.
454    pub fn typed(name: impl Into<String>, type_expr: TypeExpr) -> Self {
455        Self {
456            name: name.into(),
457            type_expr: Some(type_expr),
458            default_value: None,
459            rest: false,
460        }
461    }
462
463    /// Extract just the names from a list of typed params.
464    pub fn names(params: &[TypedParam]) -> Vec<String> {
465        params.iter().map(|p| p.name.clone()).collect()
466    }
467
468    /// Return the index of the first parameter with a default value, or None.
469    pub fn default_start(params: &[TypedParam]) -> Option<usize> {
470        params.iter().position(|p| p.default_value.is_some())
471    }
472}