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