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