Skip to main content

sage_parser/
ast.rs

1//! Abstract Syntax Tree definitions for the Sage language.
2//!
3//! This module defines all AST node types that the parser produces.
4//! Every node carries a `Span` for error reporting.
5
6use sage_types::{Ident, Span, TypeExpr};
7use std::fmt;
8
9// =============================================================================
10// Program (top-level)
11// =============================================================================
12
13/// A complete Sage program (or module).
14#[derive(Debug, Clone, PartialEq)]
15pub struct Program {
16    /// Module declarations (`mod foo`).
17    pub mod_decls: Vec<ModDecl>,
18    /// Use declarations (`use foo::Bar`).
19    pub use_decls: Vec<UseDecl>,
20    /// Record type declarations.
21    pub records: Vec<RecordDecl>,
22    /// Enum type declarations.
23    pub enums: Vec<EnumDecl>,
24    /// Constant declarations.
25    pub consts: Vec<ConstDecl>,
26    /// Agent declarations.
27    pub agents: Vec<AgentDecl>,
28    /// Function declarations.
29    pub functions: Vec<FnDecl>,
30    /// The entry-point agent (from `run AgentName`).
31    /// None for library modules that don't have an entry point.
32    pub run_agent: Option<Ident>,
33    /// Span covering the entire program.
34    pub span: Span,
35}
36
37// =============================================================================
38// Module declarations
39// =============================================================================
40
41/// A module declaration: `mod name` or `pub mod name`
42#[derive(Debug, Clone, PartialEq)]
43pub struct ModDecl {
44    /// Whether this module is public.
45    pub is_pub: bool,
46    /// The module name.
47    pub name: Ident,
48    /// Span covering the declaration.
49    pub span: Span,
50}
51
52/// A use declaration: `use path::to::Item`
53#[derive(Debug, Clone, PartialEq)]
54pub struct UseDecl {
55    /// Whether this is a public re-export (`pub use`).
56    pub is_pub: bool,
57    /// The path segments (e.g., `["agents", "Researcher"]`).
58    pub path: Vec<Ident>,
59    /// The kind of import.
60    pub kind: UseKind,
61    /// Span covering the declaration.
62    pub span: Span,
63}
64
65/// The kind of use declaration.
66#[derive(Debug, Clone, PartialEq)]
67pub enum UseKind {
68    /// Simple import: `use a::B` or `use a::B as C`
69    /// The Option is the alias (e.g., `C` in `use a::B as C`).
70    Simple(Option<Ident>),
71    /// Glob import: `use a::*`
72    Glob,
73    /// Group import: `use a::{B, C as D}`
74    /// Each tuple is (name, optional alias).
75    Group(Vec<(Ident, Option<Ident>)>),
76}
77
78// =============================================================================
79// Type declarations (records, enums)
80// =============================================================================
81
82/// A record declaration: `record Point { x: Int, y: Int }`
83#[derive(Debug, Clone, PartialEq)]
84pub struct RecordDecl {
85    /// Whether this record is public.
86    pub is_pub: bool,
87    /// The record's name.
88    pub name: Ident,
89    /// The record's fields.
90    pub fields: Vec<RecordField>,
91    /// Span covering the declaration.
92    pub span: Span,
93}
94
95/// A field in a record declaration: `name: Type`
96#[derive(Debug, Clone, PartialEq)]
97pub struct RecordField {
98    /// The field's name.
99    pub name: Ident,
100    /// The field's type.
101    pub ty: TypeExpr,
102    /// Span covering the field.
103    pub span: Span,
104}
105
106/// An enum declaration: `enum Status { Active, Pending, Done }`
107#[derive(Debug, Clone, PartialEq)]
108pub struct EnumDecl {
109    /// Whether this enum is public.
110    pub is_pub: bool,
111    /// The enum's name.
112    pub name: Ident,
113    /// The enum's variants.
114    pub variants: Vec<Ident>,
115    /// Span covering the declaration.
116    pub span: Span,
117}
118
119/// A const declaration: `const MAX_RETRIES: Int = 3`
120#[derive(Debug, Clone, PartialEq)]
121pub struct ConstDecl {
122    /// Whether this const is public.
123    pub is_pub: bool,
124    /// The constant's name.
125    pub name: Ident,
126    /// The constant's type.
127    pub ty: TypeExpr,
128    /// The constant's value.
129    pub value: Expr,
130    /// Span covering the declaration.
131    pub span: Span,
132}
133
134// =============================================================================
135// Agent declarations
136// =============================================================================
137
138/// An agent declaration: `agent Name { ... }` or `pub agent Name receives MsgType { ... }`
139#[derive(Debug, Clone, PartialEq)]
140pub struct AgentDecl {
141    /// Whether this agent is public (can be imported by other modules).
142    pub is_pub: bool,
143    /// The agent's name.
144    pub name: Ident,
145    /// The message type this agent receives (for message passing).
146    pub receives: Option<TypeExpr>,
147    /// Belief declarations (agent state).
148    pub beliefs: Vec<BeliefDecl>,
149    /// Event handlers.
150    pub handlers: Vec<HandlerDecl>,
151    /// Span covering the entire declaration.
152    pub span: Span,
153}
154
155/// A belief declaration: `belief name: Type`
156#[derive(Debug, Clone, PartialEq)]
157pub struct BeliefDecl {
158    /// The belief's name.
159    pub name: Ident,
160    /// The belief's type.
161    pub ty: TypeExpr,
162    /// Span covering the declaration.
163    pub span: Span,
164}
165
166/// An event handler: `on start { ... }`, `on message(x: T) { ... }`, `on stop { ... }`
167#[derive(Debug, Clone, PartialEq)]
168pub struct HandlerDecl {
169    /// The event kind this handler responds to.
170    pub event: EventKind,
171    /// The handler body.
172    pub body: Block,
173    /// Span covering the entire handler.
174    pub span: Span,
175}
176
177/// The kind of event a handler responds to.
178#[derive(Debug, Clone, PartialEq)]
179pub enum EventKind {
180    /// `on start` — runs when the agent is spawned.
181    Start,
182    /// `on message(param: Type)` — runs when a message is received.
183    Message {
184        /// The parameter name for the incoming message.
185        param_name: Ident,
186        /// The type of the message.
187        param_ty: TypeExpr,
188    },
189    /// `on stop` — runs during graceful shutdown.
190    Stop,
191    /// `on error(e)` — runs when an unhandled error occurs in the agent.
192    Error {
193        /// The parameter name for the error.
194        param_name: Ident,
195    },
196}
197
198impl fmt::Display for EventKind {
199    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200        match self {
201            EventKind::Start => write!(f, "start"),
202            EventKind::Message {
203                param_name,
204                param_ty,
205            } => {
206                write!(f, "message({param_name}: {param_ty})")
207            }
208            EventKind::Stop => write!(f, "stop"),
209            EventKind::Error { param_name } => {
210                write!(f, "error({param_name})")
211            }
212        }
213    }
214}
215
216// =============================================================================
217// Function declarations
218// =============================================================================
219
220/// A function declaration: `fn name(params) -> ReturnType { ... }` or `fn name(params) -> ReturnType fails { ... }`
221#[derive(Debug, Clone, PartialEq)]
222pub struct FnDecl {
223    /// Whether this function is public (can be imported by other modules).
224    pub is_pub: bool,
225    /// The function's name.
226    pub name: Ident,
227    /// The function's parameters.
228    pub params: Vec<Param>,
229    /// The return type.
230    pub return_ty: TypeExpr,
231    /// Whether this function can fail (marked with `fails`).
232    pub is_fallible: bool,
233    /// The function body.
234    pub body: Block,
235    /// Span covering the entire declaration.
236    pub span: Span,
237}
238
239/// A function parameter: `name: Type`
240#[derive(Debug, Clone, PartialEq)]
241pub struct Param {
242    /// The parameter name.
243    pub name: Ident,
244    /// The parameter type.
245    pub ty: TypeExpr,
246    /// Span covering the parameter.
247    pub span: Span,
248}
249
250// =============================================================================
251// Blocks and statements
252// =============================================================================
253
254/// A block of statements: `{ stmt* }`
255#[derive(Debug, Clone, PartialEq)]
256pub struct Block {
257    /// The statements in this block.
258    pub stmts: Vec<Stmt>,
259    /// Span covering the entire block (including braces).
260    pub span: Span,
261}
262
263/// A statement.
264#[derive(Debug, Clone, PartialEq)]
265pub enum Stmt {
266    /// Variable binding: `let name: Type = expr` or `let name = expr`
267    Let {
268        /// The variable name.
269        name: Ident,
270        /// Optional type annotation.
271        ty: Option<TypeExpr>,
272        /// The initial value.
273        value: Expr,
274        /// Span covering the statement.
275        span: Span,
276    },
277
278    /// Assignment: `name = expr`
279    Assign {
280        /// The variable being assigned to.
281        name: Ident,
282        /// The new value.
283        value: Expr,
284        /// Span covering the statement.
285        span: Span,
286    },
287
288    /// Return statement: `return expr?`
289    Return {
290        /// The optional return value.
291        value: Option<Expr>,
292        /// Span covering the statement.
293        span: Span,
294    },
295
296    /// If statement: `if cond { ... } else { ... }`
297    If {
298        /// The condition (must be Bool).
299        condition: Expr,
300        /// The then branch.
301        then_block: Block,
302        /// The optional else branch (can be another If for else-if chains).
303        else_block: Option<ElseBranch>,
304        /// Span covering the statement.
305        span: Span,
306    },
307
308    /// For loop: `for x in iter { ... }`
309    For {
310        /// The loop variable.
311        var: Ident,
312        /// The iterable expression (must be List<T>).
313        iter: Expr,
314        /// The loop body.
315        body: Block,
316        /// Span covering the statement.
317        span: Span,
318    },
319
320    /// While loop: `while cond { ... }`
321    While {
322        /// The condition (must be Bool).
323        condition: Expr,
324        /// The loop body.
325        body: Block,
326        /// Span covering the statement.
327        span: Span,
328    },
329
330    /// Infinite loop: `loop { ... }`
331    Loop {
332        /// The loop body.
333        body: Block,
334        /// Span covering the statement.
335        span: Span,
336    },
337
338    /// Break statement: `break`
339    Break {
340        /// Span covering the statement.
341        span: Span,
342    },
343
344    /// Expression statement: `expr`
345    Expr {
346        /// The expression.
347        expr: Expr,
348        /// Span covering the statement.
349        span: Span,
350    },
351}
352
353impl Stmt {
354    /// Get the span of this statement.
355    #[must_use]
356    pub fn span(&self) -> &Span {
357        match self {
358            Stmt::Let { span, .. }
359            | Stmt::Assign { span, .. }
360            | Stmt::Return { span, .. }
361            | Stmt::If { span, .. }
362            | Stmt::For { span, .. }
363            | Stmt::While { span, .. }
364            | Stmt::Loop { span, .. }
365            | Stmt::Break { span, .. }
366            | Stmt::Expr { span, .. } => span,
367        }
368    }
369}
370
371/// The else branch of an if statement.
372#[derive(Debug, Clone, PartialEq)]
373pub enum ElseBranch {
374    /// `else { ... }`
375    Block(Block),
376    /// `else if ...` (chained if)
377    ElseIf(Box<Stmt>),
378}
379
380// =============================================================================
381// Expressions
382// =============================================================================
383
384/// An expression.
385#[derive(Debug, Clone, PartialEq)]
386pub enum Expr {
387    /// LLM inference: `infer("template")` or `infer("template" -> Type)`
388    Infer {
389        /// The prompt template (may contain `{ident}` interpolations).
390        template: StringTemplate,
391        /// Optional result type annotation.
392        result_ty: Option<TypeExpr>,
393        /// Span covering the expression.
394        span: Span,
395    },
396
397    /// Agent spawning: `spawn AgentName { field: value, ... }`
398    Spawn {
399        /// The agent type to spawn.
400        agent: Ident,
401        /// Initial belief values.
402        fields: Vec<FieldInit>,
403        /// Span covering the expression.
404        span: Span,
405    },
406
407    /// Await: `await expr`
408    Await {
409        /// The agent handle to await.
410        handle: Box<Expr>,
411        /// Span covering the expression.
412        span: Span,
413    },
414
415    /// Send message: `send(handle, message)`
416    Send {
417        /// The agent handle to send to.
418        handle: Box<Expr>,
419        /// The message to send.
420        message: Box<Expr>,
421        /// Span covering the expression.
422        span: Span,
423    },
424
425    /// Emit value: `emit(value)`
426    Emit {
427        /// The value to emit to the awaiter.
428        value: Box<Expr>,
429        /// Span covering the expression.
430        span: Span,
431    },
432
433    /// Function call: `name(args)`
434    Call {
435        /// The function name.
436        name: Ident,
437        /// The arguments.
438        args: Vec<Expr>,
439        /// Span covering the expression.
440        span: Span,
441    },
442
443    /// Method call on self: `self.method(args)`
444    SelfMethodCall {
445        /// The method name.
446        method: Ident,
447        /// The arguments.
448        args: Vec<Expr>,
449        /// Span covering the expression.
450        span: Span,
451    },
452
453    /// Self field access: `self.field`
454    SelfField {
455        /// The field (belief) name.
456        field: Ident,
457        /// Span covering the expression.
458        span: Span,
459    },
460
461    /// Binary operation: `left op right`
462    Binary {
463        /// The operator.
464        op: BinOp,
465        /// The left operand.
466        left: Box<Expr>,
467        /// The right operand.
468        right: Box<Expr>,
469        /// Span covering the expression.
470        span: Span,
471    },
472
473    /// Unary operation: `op operand`
474    Unary {
475        /// The operator.
476        op: UnaryOp,
477        /// The operand.
478        operand: Box<Expr>,
479        /// Span covering the expression.
480        span: Span,
481    },
482
483    /// List literal: `[a, b, c]`
484    List {
485        /// The list elements.
486        elements: Vec<Expr>,
487        /// Span covering the expression.
488        span: Span,
489    },
490
491    /// Literal value.
492    Literal {
493        /// The literal value.
494        value: Literal,
495        /// Span covering the expression.
496        span: Span,
497    },
498
499    /// Variable reference.
500    Var {
501        /// The variable name.
502        name: Ident,
503        /// Span covering the expression.
504        span: Span,
505    },
506
507    /// Parenthesized expression: `(expr)`
508    Paren {
509        /// The inner expression.
510        inner: Box<Expr>,
511        /// Span covering the expression (including parens).
512        span: Span,
513    },
514
515    /// Interpolated string: `"Hello, {name}!"`
516    StringInterp {
517        /// The string template with interpolations.
518        template: StringTemplate,
519        /// Span covering the expression.
520        span: Span,
521    },
522
523    /// Match expression: `match expr { Pattern => expr, ... }`
524    Match {
525        /// The scrutinee expression.
526        scrutinee: Box<Expr>,
527        /// The match arms.
528        arms: Vec<MatchArm>,
529        /// Span covering the expression.
530        span: Span,
531    },
532
533    /// Record construction: `Point { x: 1, y: 2 }`
534    RecordConstruct {
535        /// The record type name.
536        name: Ident,
537        /// Field initializations.
538        fields: Vec<FieldInit>,
539        /// Span covering the expression.
540        span: Span,
541    },
542
543    /// Field access: `record.field`
544    FieldAccess {
545        /// The record expression.
546        object: Box<Expr>,
547        /// The field name.
548        field: Ident,
549        /// Span covering the expression.
550        span: Span,
551    },
552
553    /// Receive message from mailbox: `receive()`
554    Receive {
555        /// Span covering the expression.
556        span: Span,
557    },
558
559    /// Try expression: `try expr` — propagates failure upward.
560    Try {
561        /// The expression that may fail.
562        expr: Box<Expr>,
563        /// Span covering the expression.
564        span: Span,
565    },
566
567    /// Catch expression: `expr catch { recovery }` or `expr catch(e) { recovery }`.
568    Catch {
569        /// The expression that may fail.
570        expr: Box<Expr>,
571        /// The optional error binding (e.g., `e` in `catch(e)`).
572        error_bind: Option<Ident>,
573        /// The recovery expression.
574        recovery: Box<Expr>,
575        /// Span covering the expression.
576        span: Span,
577    },
578}
579
580impl Expr {
581    /// Get the span of this expression.
582    #[must_use]
583    pub fn span(&self) -> &Span {
584        match self {
585            Expr::Infer { span, .. }
586            | Expr::Spawn { span, .. }
587            | Expr::Await { span, .. }
588            | Expr::Send { span, .. }
589            | Expr::Emit { span, .. }
590            | Expr::Call { span, .. }
591            | Expr::SelfMethodCall { span, .. }
592            | Expr::SelfField { span, .. }
593            | Expr::Binary { span, .. }
594            | Expr::Unary { span, .. }
595            | Expr::List { span, .. }
596            | Expr::Literal { span, .. }
597            | Expr::Var { span, .. }
598            | Expr::Paren { span, .. }
599            | Expr::StringInterp { span, .. }
600            | Expr::Match { span, .. }
601            | Expr::RecordConstruct { span, .. }
602            | Expr::FieldAccess { span, .. }
603            | Expr::Receive { span, .. }
604            | Expr::Try { span, .. }
605            | Expr::Catch { span, .. } => span,
606        }
607    }
608}
609
610/// A field initialization in a spawn or record construction expression: `field: value`
611#[derive(Debug, Clone, PartialEq)]
612pub struct FieldInit {
613    /// The field name.
614    pub name: Ident,
615    /// The initial value.
616    pub value: Expr,
617    /// Span covering the field initialization.
618    pub span: Span,
619}
620
621/// A match arm: `Pattern => expr`
622#[derive(Debug, Clone, PartialEq)]
623pub struct MatchArm {
624    /// The pattern to match.
625    pub pattern: Pattern,
626    /// The expression to evaluate if the pattern matches.
627    pub body: Expr,
628    /// Span covering the arm.
629    pub span: Span,
630}
631
632/// A pattern in a match expression.
633#[derive(Debug, Clone, PartialEq)]
634pub enum Pattern {
635    /// Wildcard pattern: `_`
636    Wildcard {
637        /// Span covering the pattern.
638        span: Span,
639    },
640    /// Enum variant pattern: `Status::Active` or just `Active`
641    Variant {
642        /// Optional enum type name (for qualified patterns).
643        enum_name: Option<Ident>,
644        /// The variant name.
645        variant: Ident,
646        /// Span covering the pattern.
647        span: Span,
648    },
649    /// Literal pattern: `42`, `"hello"`, `true`
650    Literal {
651        /// The literal value.
652        value: Literal,
653        /// Span covering the pattern.
654        span: Span,
655    },
656    /// Binding pattern: `x` (binds the matched value to a variable)
657    Binding {
658        /// The variable name.
659        name: Ident,
660        /// Span covering the pattern.
661        span: Span,
662    },
663}
664
665impl Pattern {
666    /// Get the span of this pattern.
667    #[must_use]
668    pub fn span(&self) -> &Span {
669        match self {
670            Pattern::Wildcard { span }
671            | Pattern::Variant { span, .. }
672            | Pattern::Literal { span, .. }
673            | Pattern::Binding { span, .. } => span,
674        }
675    }
676}
677
678// =============================================================================
679// Operators
680// =============================================================================
681
682/// Binary operators.
683#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
684pub enum BinOp {
685    // Arithmetic
686    /// `+`
687    Add,
688    /// `-`
689    Sub,
690    /// `*`
691    Mul,
692    /// `/`
693    Div,
694
695    // Comparison
696    /// `==`
697    Eq,
698    /// `!=`
699    Ne,
700    /// `<`
701    Lt,
702    /// `>`
703    Gt,
704    /// `<=`
705    Le,
706    /// `>=`
707    Ge,
708
709    // Logical
710    /// `&&`
711    And,
712    /// `||`
713    Or,
714
715    // String
716    /// `++` (string concatenation)
717    Concat,
718}
719
720impl BinOp {
721    /// Get the precedence of this operator (higher = binds tighter).
722    #[must_use]
723    pub fn precedence(self) -> u8 {
724        match self {
725            BinOp::Or => 1,
726            BinOp::And => 2,
727            BinOp::Eq | BinOp::Ne => 3,
728            BinOp::Lt | BinOp::Gt | BinOp::Le | BinOp::Ge => 4,
729            BinOp::Concat => 5,
730            BinOp::Add | BinOp::Sub => 6,
731            BinOp::Mul | BinOp::Div => 7,
732        }
733    }
734
735    /// Check if this operator is left-associative.
736    #[must_use]
737    pub fn is_left_assoc(self) -> bool {
738        // All our operators are left-associative
739        true
740    }
741}
742
743impl fmt::Display for BinOp {
744    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
745        match self {
746            BinOp::Add => write!(f, "+"),
747            BinOp::Sub => write!(f, "-"),
748            BinOp::Mul => write!(f, "*"),
749            BinOp::Div => write!(f, "/"),
750            BinOp::Eq => write!(f, "=="),
751            BinOp::Ne => write!(f, "!="),
752            BinOp::Lt => write!(f, "<"),
753            BinOp::Gt => write!(f, ">"),
754            BinOp::Le => write!(f, "<="),
755            BinOp::Ge => write!(f, ">="),
756            BinOp::And => write!(f, "&&"),
757            BinOp::Or => write!(f, "||"),
758            BinOp::Concat => write!(f, "++"),
759        }
760    }
761}
762
763/// Unary operators.
764#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
765pub enum UnaryOp {
766    /// `-` (negation)
767    Neg,
768    /// `!` (logical not)
769    Not,
770}
771
772impl fmt::Display for UnaryOp {
773    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
774        match self {
775            UnaryOp::Neg => write!(f, "-"),
776            UnaryOp::Not => write!(f, "!"),
777        }
778    }
779}
780
781// =============================================================================
782// Literals
783// =============================================================================
784
785/// A literal value.
786#[derive(Debug, Clone, PartialEq)]
787pub enum Literal {
788    /// Integer literal: `42`, `-7`
789    Int(i64),
790    /// Float literal: `3.14`, `-0.5`
791    Float(f64),
792    /// Boolean literal: `true`, `false`
793    Bool(bool),
794    /// String literal: `"hello"`
795    String(String),
796}
797
798impl fmt::Display for Literal {
799    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
800        match self {
801            Literal::Int(n) => write!(f, "{n}"),
802            Literal::Float(n) => write!(f, "{n}"),
803            Literal::Bool(b) => write!(f, "{b}"),
804            Literal::String(s) => write!(f, "\"{s}\""),
805        }
806    }
807}
808
809// =============================================================================
810// String templates (for interpolation)
811// =============================================================================
812
813/// A string template that may contain interpolations.
814///
815/// For example, `"Hello, {name}!"` becomes:
816/// ```text
817/// StringTemplate {
818///     parts: [
819///         StringPart::Literal("Hello, "),
820///         StringPart::Interpolation(Ident("name")),
821///         StringPart::Literal("!"),
822///     ]
823/// }
824/// ```
825#[derive(Debug, Clone, PartialEq)]
826pub struct StringTemplate {
827    /// The parts of the template.
828    pub parts: Vec<StringPart>,
829    /// Span covering the entire template string.
830    pub span: Span,
831}
832
833impl StringTemplate {
834    /// Create a simple template with no interpolations.
835    #[must_use]
836    pub fn literal(s: String, span: Span) -> Self {
837        Self {
838            parts: vec![StringPart::Literal(s)],
839            span,
840        }
841    }
842
843    /// Check if this template has any interpolations.
844    #[must_use]
845    pub fn has_interpolations(&self) -> bool {
846        self.parts
847            .iter()
848            .any(|p| matches!(p, StringPart::Interpolation(_)))
849    }
850
851    /// Get all interpolated identifiers.
852    pub fn interpolations(&self) -> impl Iterator<Item = &Ident> {
853        self.parts.iter().filter_map(|p| match p {
854            StringPart::Interpolation(ident) => Some(ident),
855            StringPart::Literal(_) => None,
856        })
857    }
858}
859
860impl fmt::Display for StringTemplate {
861    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
862        write!(f, "\"")?;
863        for part in &self.parts {
864            match part {
865                StringPart::Literal(s) => write!(f, "{s}")?,
866                StringPart::Interpolation(ident) => write!(f, "{{{ident}}}")?,
867            }
868        }
869        write!(f, "\"")
870    }
871}
872
873/// A part of a string template.
874#[derive(Debug, Clone, PartialEq)]
875pub enum StringPart {
876    /// A literal string segment.
877    Literal(String),
878    /// An interpolated identifier: `{ident}`
879    Interpolation(Ident),
880}
881
882// =============================================================================
883// Tests
884// =============================================================================
885
886#[cfg(test)]
887mod tests {
888    use super::*;
889
890    #[test]
891    fn binop_precedence() {
892        // Mul/Div > Add/Sub > Comparison > And > Or
893        assert!(BinOp::Mul.precedence() > BinOp::Add.precedence());
894        assert!(BinOp::Add.precedence() > BinOp::Lt.precedence());
895        assert!(BinOp::Lt.precedence() > BinOp::And.precedence());
896        assert!(BinOp::And.precedence() > BinOp::Or.precedence());
897    }
898
899    #[test]
900    fn binop_display() {
901        assert_eq!(format!("{}", BinOp::Add), "+");
902        assert_eq!(format!("{}", BinOp::Eq), "==");
903        assert_eq!(format!("{}", BinOp::Concat), "++");
904        assert_eq!(format!("{}", BinOp::And), "&&");
905    }
906
907    #[test]
908    fn unaryop_display() {
909        assert_eq!(format!("{}", UnaryOp::Neg), "-");
910        assert_eq!(format!("{}", UnaryOp::Not), "!");
911    }
912
913    #[test]
914    fn literal_display() {
915        assert_eq!(format!("{}", Literal::Int(42)), "42");
916        assert_eq!(format!("{}", Literal::Float(3.14)), "3.14");
917        assert_eq!(format!("{}", Literal::Bool(true)), "true");
918        assert_eq!(format!("{}", Literal::String("hello".into())), "\"hello\"");
919    }
920
921    #[test]
922    fn event_kind_display() {
923        assert_eq!(format!("{}", EventKind::Start), "start");
924        assert_eq!(format!("{}", EventKind::Stop), "stop");
925
926        let msg = EventKind::Message {
927            param_name: Ident::dummy("msg"),
928            param_ty: TypeExpr::String,
929        };
930        assert_eq!(format!("{msg}"), "message(msg: String)");
931    }
932
933    #[test]
934    fn string_template_literal() {
935        let template = StringTemplate::literal("hello".into(), Span::dummy());
936        assert!(!template.has_interpolations());
937        assert_eq!(format!("{template}"), "\"hello\"");
938    }
939
940    #[test]
941    fn string_template_with_interpolation() {
942        let template = StringTemplate {
943            parts: vec![
944                StringPart::Literal("Hello, ".into()),
945                StringPart::Interpolation(Ident::dummy("name")),
946                StringPart::Literal("!".into()),
947            ],
948            span: Span::dummy(),
949        };
950        assert!(template.has_interpolations());
951        assert_eq!(format!("{template}"), "\"Hello, {name}!\"");
952
953        let interps: Vec<_> = template.interpolations().collect();
954        assert_eq!(interps.len(), 1);
955        assert_eq!(interps[0].name, "name");
956    }
957
958    #[test]
959    fn expr_span() {
960        let span = Span::dummy();
961        let expr = Expr::Literal {
962            value: Literal::Int(42),
963            span: span.clone(),
964        };
965        assert_eq!(expr.span(), &span);
966    }
967
968    #[test]
969    fn stmt_span() {
970        let span = Span::dummy();
971        let stmt = Stmt::Return {
972            value: None,
973            span: span.clone(),
974        };
975        assert_eq!(stmt.span(), &span);
976    }
977}