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    TypeDecl {
133        name: String,
134        type_expr: TypeExpr,
135    },
136    SpawnExpr {
137        body: Vec<SNode>,
138    },
139    /// Duration literal: 500ms, 5s, 30m, 2h
140    DurationLiteral(u64),
141    /// Range expression: start upto end (exclusive) or start thru end (inclusive)
142    RangeExpr {
143        start: Box<SNode>,
144        end: Box<SNode>,
145        inclusive: bool,
146    },
147    /// Guard clause: guard condition else { body }
148    GuardStmt {
149        condition: Box<SNode>,
150        else_body: Vec<SNode>,
151    },
152    RequireStmt {
153        condition: Box<SNode>,
154        message: Option<Box<SNode>>,
155    },
156    /// Ask expression: ask { system: "...", user: "...", ... }
157    AskExpr {
158        fields: Vec<DictEntry>,
159    },
160    /// Deadline block: deadline DURATION { body }
161    DeadlineBlock {
162        duration: Box<SNode>,
163        body: Vec<SNode>,
164    },
165    /// Yield expression: yields control to host, optionally with a value.
166    YieldExpr {
167        value: Option<Box<SNode>>,
168    },
169    /// Mutex block: mutual exclusion for concurrent access.
170    MutexBlock {
171        body: Vec<SNode>,
172    },
173    /// Break out of a loop.
174    BreakStmt,
175    /// Continue to next loop iteration.
176    ContinueStmt,
177
178    // Concurrency
179    Parallel {
180        count: Box<SNode>,
181        variable: Option<String>,
182        body: Vec<SNode>,
183    },
184    ParallelMap {
185        list: Box<SNode>,
186        variable: String,
187        body: Vec<SNode>,
188    },
189    ParallelSettle {
190        list: Box<SNode>,
191        variable: String,
192        body: Vec<SNode>,
193    },
194
195    SelectExpr {
196        cases: Vec<SelectCase>,
197        timeout: Option<(Box<SNode>, Vec<SNode>)>,
198        default_body: Option<Vec<SNode>>,
199    },
200
201    // Expressions
202    FunctionCall {
203        name: String,
204        args: Vec<SNode>,
205    },
206    MethodCall {
207        object: Box<SNode>,
208        method: String,
209        args: Vec<SNode>,
210    },
211    /// Optional method call: `obj?.method(args)` — returns nil if obj is nil.
212    OptionalMethodCall {
213        object: Box<SNode>,
214        method: String,
215        args: Vec<SNode>,
216    },
217    PropertyAccess {
218        object: Box<SNode>,
219        property: String,
220    },
221    /// Optional chaining: `obj?.property` — returns nil if obj is nil.
222    OptionalPropertyAccess {
223        object: Box<SNode>,
224        property: String,
225    },
226    SubscriptAccess {
227        object: Box<SNode>,
228        index: Box<SNode>,
229    },
230    SliceAccess {
231        object: Box<SNode>,
232        start: Option<Box<SNode>>,
233        end: Option<Box<SNode>>,
234    },
235    BinaryOp {
236        op: String,
237        left: Box<SNode>,
238        right: Box<SNode>,
239    },
240    UnaryOp {
241        op: String,
242        operand: Box<SNode>,
243    },
244    Ternary {
245        condition: Box<SNode>,
246        true_expr: Box<SNode>,
247        false_expr: Box<SNode>,
248    },
249    Assignment {
250        target: Box<SNode>,
251        value: Box<SNode>,
252        /// None = plain `=`, Some("+") = `+=`, etc.
253        op: Option<String>,
254    },
255    ThrowStmt {
256        value: Box<SNode>,
257    },
258
259    /// Enum variant construction: EnumName.Variant(args)
260    EnumConstruct {
261        enum_name: String,
262        variant: String,
263        args: Vec<SNode>,
264    },
265    /// Struct construction: StructName { field: value, ... }
266    StructConstruct {
267        struct_name: String,
268        fields: Vec<DictEntry>,
269    },
270
271    // Literals
272    InterpolatedString(Vec<StringSegment>),
273    StringLiteral(String),
274    IntLiteral(i64),
275    FloatLiteral(f64),
276    BoolLiteral(bool),
277    NilLiteral,
278    Identifier(String),
279    ListLiteral(Vec<SNode>),
280    DictLiteral(Vec<DictEntry>),
281    /// Spread expression `...expr` inside list/dict literals.
282    Spread(Box<SNode>),
283    /// Try operator: expr? — unwraps Result.Ok or propagates Result.Err.
284    TryOperator {
285        operand: Box<SNode>,
286    },
287
288    // Blocks
289    Block(Vec<SNode>),
290    Closure {
291        params: Vec<TypedParam>,
292        body: Vec<SNode>,
293        /// When true, this closure was written as `fn(params) { body }`.
294        /// The formatter preserves this distinction.
295        fn_syntax: bool,
296    },
297}
298
299#[derive(Debug, Clone, PartialEq)]
300pub struct MatchArm {
301    pub pattern: SNode,
302    pub body: Vec<SNode>,
303}
304
305#[derive(Debug, Clone, PartialEq)]
306pub struct SelectCase {
307    pub variable: String,
308    pub channel: Box<SNode>,
309    pub body: Vec<SNode>,
310}
311
312#[derive(Debug, Clone, PartialEq)]
313pub struct DictEntry {
314    pub key: SNode,
315    pub value: SNode,
316}
317
318/// An enum variant declaration.
319#[derive(Debug, Clone, PartialEq)]
320pub struct EnumVariant {
321    pub name: String,
322    pub fields: Vec<TypedParam>,
323}
324
325/// A struct field declaration.
326#[derive(Debug, Clone, PartialEq)]
327pub struct StructField {
328    pub name: String,
329    pub type_expr: Option<TypeExpr>,
330    pub optional: bool,
331}
332
333/// An interface method signature.
334#[derive(Debug, Clone, PartialEq)]
335pub struct InterfaceMethod {
336    pub name: String,
337    pub type_params: Vec<TypeParam>,
338    pub params: Vec<TypedParam>,
339    pub return_type: Option<TypeExpr>,
340}
341
342/// A type annotation (optional, for runtime checking).
343#[derive(Debug, Clone, PartialEq)]
344pub enum TypeExpr {
345    /// A named type: int, string, float, bool, nil, list, dict, closure,
346    /// or a user-defined type name.
347    Named(String),
348    /// A union type: `string | nil`, `int | float`.
349    Union(Vec<TypeExpr>),
350    /// A dict shape type: `{name: string, age: int, active?: bool}`.
351    Shape(Vec<ShapeField>),
352    /// A list type: `list<int>`.
353    List(Box<TypeExpr>),
354    /// A dict type with key and value types: `dict<string, int>`.
355    DictType(Box<TypeExpr>, Box<TypeExpr>),
356    /// A function type: `fn(int, string) -> bool`.
357    FnType {
358        params: Vec<TypeExpr>,
359        return_type: Box<TypeExpr>,
360    },
361}
362
363/// A field in a dict shape type.
364#[derive(Debug, Clone, PartialEq)]
365pub struct ShapeField {
366    pub name: String,
367    pub type_expr: TypeExpr,
368    pub optional: bool,
369}
370
371/// A binding pattern for destructuring in let/var/for-in.
372#[derive(Debug, Clone, PartialEq)]
373pub enum BindingPattern {
374    /// Simple identifier: `let x = ...`
375    Identifier(String),
376    /// Dict destructuring: `let {name, age} = ...`
377    Dict(Vec<DictPatternField>),
378    /// List destructuring: `let [a, b] = ...`
379    List(Vec<ListPatternElement>),
380}
381
382/// A field in a dict destructuring pattern.
383#[derive(Debug, Clone, PartialEq)]
384pub struct DictPatternField {
385    /// The dict key to extract.
386    pub key: String,
387    /// Renamed binding (if different from key), e.g. `{name: alias}`.
388    pub alias: Option<String>,
389    /// True for `...rest` (rest pattern).
390    pub is_rest: bool,
391}
392
393/// An element in a list destructuring pattern.
394#[derive(Debug, Clone, PartialEq)]
395pub struct ListPatternElement {
396    /// The variable name to bind.
397    pub name: String,
398    /// True for `...rest` (rest pattern).
399    pub is_rest: bool,
400}
401
402/// A generic type parameter on a function or pipeline declaration.
403#[derive(Debug, Clone, PartialEq)]
404pub struct TypeParam {
405    pub name: String,
406}
407
408/// A where-clause constraint on a generic type parameter.
409#[derive(Debug, Clone, PartialEq)]
410pub struct WhereClause {
411    pub type_name: String,
412    pub bound: String,
413}
414
415/// A parameter with an optional type annotation and optional default value.
416#[derive(Debug, Clone, PartialEq)]
417pub struct TypedParam {
418    pub name: String,
419    pub type_expr: Option<TypeExpr>,
420    pub default_value: Option<Box<SNode>>,
421}
422
423impl TypedParam {
424    /// Create an untyped parameter.
425    pub fn untyped(name: impl Into<String>) -> Self {
426        Self {
427            name: name.into(),
428            type_expr: None,
429            default_value: None,
430        }
431    }
432
433    /// Create a typed parameter.
434    pub fn typed(name: impl Into<String>, type_expr: TypeExpr) -> Self {
435        Self {
436            name: name.into(),
437            type_expr: Some(type_expr),
438            default_value: None,
439        }
440    }
441
442    /// Extract just the names from a list of typed params.
443    pub fn names(params: &[TypedParam]) -> Vec<String> {
444        params.iter().map(|p| p.name.clone()).collect()
445    }
446
447    /// Return the index of the first parameter with a default value, or None.
448    pub fn default_start(params: &[TypedParam]) -> Option<usize> {
449        params.iter().position(|p| p.default_value.is_some())
450    }
451}