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}
192
193impl fmt::Display for EventKind {
194    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195        match self {
196            EventKind::Start => write!(f, "start"),
197            EventKind::Message {
198                param_name,
199                param_ty,
200            } => {
201                write!(f, "message({param_name}: {param_ty})")
202            }
203            EventKind::Stop => write!(f, "stop"),
204        }
205    }
206}
207
208// =============================================================================
209// Function declarations
210// =============================================================================
211
212/// A function declaration: `fn name(params) -> ReturnType { ... }`
213#[derive(Debug, Clone, PartialEq)]
214pub struct FnDecl {
215    /// Whether this function is public (can be imported by other modules).
216    pub is_pub: bool,
217    /// The function's name.
218    pub name: Ident,
219    /// The function's parameters.
220    pub params: Vec<Param>,
221    /// The return type.
222    pub return_ty: TypeExpr,
223    /// The function body.
224    pub body: Block,
225    /// Span covering the entire declaration.
226    pub span: Span,
227}
228
229/// A function parameter: `name: Type`
230#[derive(Debug, Clone, PartialEq)]
231pub struct Param {
232    /// The parameter name.
233    pub name: Ident,
234    /// The parameter type.
235    pub ty: TypeExpr,
236    /// Span covering the parameter.
237    pub span: Span,
238}
239
240// =============================================================================
241// Blocks and statements
242// =============================================================================
243
244/// A block of statements: `{ stmt* }`
245#[derive(Debug, Clone, PartialEq)]
246pub struct Block {
247    /// The statements in this block.
248    pub stmts: Vec<Stmt>,
249    /// Span covering the entire block (including braces).
250    pub span: Span,
251}
252
253/// A statement.
254#[derive(Debug, Clone, PartialEq)]
255pub enum Stmt {
256    /// Variable binding: `let name: Type = expr` or `let name = expr`
257    Let {
258        /// The variable name.
259        name: Ident,
260        /// Optional type annotation.
261        ty: Option<TypeExpr>,
262        /// The initial value.
263        value: Expr,
264        /// Span covering the statement.
265        span: Span,
266    },
267
268    /// Assignment: `name = expr`
269    Assign {
270        /// The variable being assigned to.
271        name: Ident,
272        /// The new value.
273        value: Expr,
274        /// Span covering the statement.
275        span: Span,
276    },
277
278    /// Return statement: `return expr?`
279    Return {
280        /// The optional return value.
281        value: Option<Expr>,
282        /// Span covering the statement.
283        span: Span,
284    },
285
286    /// If statement: `if cond { ... } else { ... }`
287    If {
288        /// The condition (must be Bool).
289        condition: Expr,
290        /// The then branch.
291        then_block: Block,
292        /// The optional else branch (can be another If for else-if chains).
293        else_block: Option<ElseBranch>,
294        /// Span covering the statement.
295        span: Span,
296    },
297
298    /// For loop: `for x in iter { ... }`
299    For {
300        /// The loop variable.
301        var: Ident,
302        /// The iterable expression (must be List<T>).
303        iter: Expr,
304        /// The loop body.
305        body: Block,
306        /// Span covering the statement.
307        span: Span,
308    },
309
310    /// While loop: `while cond { ... }`
311    While {
312        /// The condition (must be Bool).
313        condition: Expr,
314        /// The loop body.
315        body: Block,
316        /// Span covering the statement.
317        span: Span,
318    },
319
320    /// Infinite loop: `loop { ... }`
321    Loop {
322        /// The loop body.
323        body: Block,
324        /// Span covering the statement.
325        span: Span,
326    },
327
328    /// Break statement: `break`
329    Break {
330        /// Span covering the statement.
331        span: Span,
332    },
333
334    /// Expression statement: `expr`
335    Expr {
336        /// The expression.
337        expr: Expr,
338        /// Span covering the statement.
339        span: Span,
340    },
341}
342
343impl Stmt {
344    /// Get the span of this statement.
345    #[must_use]
346    pub fn span(&self) -> &Span {
347        match self {
348            Stmt::Let { span, .. }
349            | Stmt::Assign { span, .. }
350            | Stmt::Return { span, .. }
351            | Stmt::If { span, .. }
352            | Stmt::For { span, .. }
353            | Stmt::While { span, .. }
354            | Stmt::Loop { span, .. }
355            | Stmt::Break { span, .. }
356            | Stmt::Expr { span, .. } => span,
357        }
358    }
359}
360
361/// The else branch of an if statement.
362#[derive(Debug, Clone, PartialEq)]
363pub enum ElseBranch {
364    /// `else { ... }`
365    Block(Block),
366    /// `else if ...` (chained if)
367    ElseIf(Box<Stmt>),
368}
369
370// =============================================================================
371// Expressions
372// =============================================================================
373
374/// An expression.
375#[derive(Debug, Clone, PartialEq)]
376pub enum Expr {
377    /// LLM inference: `infer("template")` or `infer("template" -> Type)`
378    Infer {
379        /// The prompt template (may contain `{ident}` interpolations).
380        template: StringTemplate,
381        /// Optional result type annotation.
382        result_ty: Option<TypeExpr>,
383        /// Span covering the expression.
384        span: Span,
385    },
386
387    /// Agent spawning: `spawn AgentName { field: value, ... }`
388    Spawn {
389        /// The agent type to spawn.
390        agent: Ident,
391        /// Initial belief values.
392        fields: Vec<FieldInit>,
393        /// Span covering the expression.
394        span: Span,
395    },
396
397    /// Await: `await expr`
398    Await {
399        /// The agent handle to await.
400        handle: Box<Expr>,
401        /// Span covering the expression.
402        span: Span,
403    },
404
405    /// Send message: `send(handle, message)`
406    Send {
407        /// The agent handle to send to.
408        handle: Box<Expr>,
409        /// The message to send.
410        message: Box<Expr>,
411        /// Span covering the expression.
412        span: Span,
413    },
414
415    /// Emit value: `emit(value)`
416    Emit {
417        /// The value to emit to the awaiter.
418        value: Box<Expr>,
419        /// Span covering the expression.
420        span: Span,
421    },
422
423    /// Function call: `name(args)`
424    Call {
425        /// The function name.
426        name: Ident,
427        /// The arguments.
428        args: Vec<Expr>,
429        /// Span covering the expression.
430        span: Span,
431    },
432
433    /// Method call on self: `self.method(args)`
434    SelfMethodCall {
435        /// The method name.
436        method: Ident,
437        /// The arguments.
438        args: Vec<Expr>,
439        /// Span covering the expression.
440        span: Span,
441    },
442
443    /// Self field access: `self.field`
444    SelfField {
445        /// The field (belief) name.
446        field: Ident,
447        /// Span covering the expression.
448        span: Span,
449    },
450
451    /// Binary operation: `left op right`
452    Binary {
453        /// The operator.
454        op: BinOp,
455        /// The left operand.
456        left: Box<Expr>,
457        /// The right operand.
458        right: Box<Expr>,
459        /// Span covering the expression.
460        span: Span,
461    },
462
463    /// Unary operation: `op operand`
464    Unary {
465        /// The operator.
466        op: UnaryOp,
467        /// The operand.
468        operand: Box<Expr>,
469        /// Span covering the expression.
470        span: Span,
471    },
472
473    /// List literal: `[a, b, c]`
474    List {
475        /// The list elements.
476        elements: Vec<Expr>,
477        /// Span covering the expression.
478        span: Span,
479    },
480
481    /// Literal value.
482    Literal {
483        /// The literal value.
484        value: Literal,
485        /// Span covering the expression.
486        span: Span,
487    },
488
489    /// Variable reference.
490    Var {
491        /// The variable name.
492        name: Ident,
493        /// Span covering the expression.
494        span: Span,
495    },
496
497    /// Parenthesized expression: `(expr)`
498    Paren {
499        /// The inner expression.
500        inner: Box<Expr>,
501        /// Span covering the expression (including parens).
502        span: Span,
503    },
504
505    /// Interpolated string: `"Hello, {name}!"`
506    StringInterp {
507        /// The string template with interpolations.
508        template: StringTemplate,
509        /// Span covering the expression.
510        span: Span,
511    },
512
513    /// Match expression: `match expr { Pattern => expr, ... }`
514    Match {
515        /// The scrutinee expression.
516        scrutinee: Box<Expr>,
517        /// The match arms.
518        arms: Vec<MatchArm>,
519        /// Span covering the expression.
520        span: Span,
521    },
522
523    /// Record construction: `Point { x: 1, y: 2 }`
524    RecordConstruct {
525        /// The record type name.
526        name: Ident,
527        /// Field initializations.
528        fields: Vec<FieldInit>,
529        /// Span covering the expression.
530        span: Span,
531    },
532
533    /// Field access: `record.field`
534    FieldAccess {
535        /// The record expression.
536        object: Box<Expr>,
537        /// The field name.
538        field: Ident,
539        /// Span covering the expression.
540        span: Span,
541    },
542
543    /// Receive message from mailbox: `receive()`
544    Receive {
545        /// Span covering the expression.
546        span: Span,
547    },
548}
549
550impl Expr {
551    /// Get the span of this expression.
552    #[must_use]
553    pub fn span(&self) -> &Span {
554        match self {
555            Expr::Infer { span, .. }
556            | Expr::Spawn { span, .. }
557            | Expr::Await { span, .. }
558            | Expr::Send { span, .. }
559            | Expr::Emit { span, .. }
560            | Expr::Call { span, .. }
561            | Expr::SelfMethodCall { span, .. }
562            | Expr::SelfField { span, .. }
563            | Expr::Binary { span, .. }
564            | Expr::Unary { span, .. }
565            | Expr::List { span, .. }
566            | Expr::Literal { span, .. }
567            | Expr::Var { span, .. }
568            | Expr::Paren { span, .. }
569            | Expr::StringInterp { span, .. }
570            | Expr::Match { span, .. }
571            | Expr::RecordConstruct { span, .. }
572            | Expr::FieldAccess { span, .. }
573            | Expr::Receive { span, .. } => span,
574        }
575    }
576}
577
578/// A field initialization in a spawn or record construction expression: `field: value`
579#[derive(Debug, Clone, PartialEq)]
580pub struct FieldInit {
581    /// The field name.
582    pub name: Ident,
583    /// The initial value.
584    pub value: Expr,
585    /// Span covering the field initialization.
586    pub span: Span,
587}
588
589/// A match arm: `Pattern => expr`
590#[derive(Debug, Clone, PartialEq)]
591pub struct MatchArm {
592    /// The pattern to match.
593    pub pattern: Pattern,
594    /// The expression to evaluate if the pattern matches.
595    pub body: Expr,
596    /// Span covering the arm.
597    pub span: Span,
598}
599
600/// A pattern in a match expression.
601#[derive(Debug, Clone, PartialEq)]
602pub enum Pattern {
603    /// Wildcard pattern: `_`
604    Wildcard {
605        /// Span covering the pattern.
606        span: Span,
607    },
608    /// Enum variant pattern: `Status::Active` or just `Active`
609    Variant {
610        /// Optional enum type name (for qualified patterns).
611        enum_name: Option<Ident>,
612        /// The variant name.
613        variant: Ident,
614        /// Span covering the pattern.
615        span: Span,
616    },
617    /// Literal pattern: `42`, `"hello"`, `true`
618    Literal {
619        /// The literal value.
620        value: Literal,
621        /// Span covering the pattern.
622        span: Span,
623    },
624    /// Binding pattern: `x` (binds the matched value to a variable)
625    Binding {
626        /// The variable name.
627        name: Ident,
628        /// Span covering the pattern.
629        span: Span,
630    },
631}
632
633impl Pattern {
634    /// Get the span of this pattern.
635    #[must_use]
636    pub fn span(&self) -> &Span {
637        match self {
638            Pattern::Wildcard { span }
639            | Pattern::Variant { span, .. }
640            | Pattern::Literal { span, .. }
641            | Pattern::Binding { span, .. } => span,
642        }
643    }
644}
645
646// =============================================================================
647// Operators
648// =============================================================================
649
650/// Binary operators.
651#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
652pub enum BinOp {
653    // Arithmetic
654    /// `+`
655    Add,
656    /// `-`
657    Sub,
658    /// `*`
659    Mul,
660    /// `/`
661    Div,
662
663    // Comparison
664    /// `==`
665    Eq,
666    /// `!=`
667    Ne,
668    /// `<`
669    Lt,
670    /// `>`
671    Gt,
672    /// `<=`
673    Le,
674    /// `>=`
675    Ge,
676
677    // Logical
678    /// `&&`
679    And,
680    /// `||`
681    Or,
682
683    // String
684    /// `++` (string concatenation)
685    Concat,
686}
687
688impl BinOp {
689    /// Get the precedence of this operator (higher = binds tighter).
690    #[must_use]
691    pub fn precedence(self) -> u8 {
692        match self {
693            BinOp::Or => 1,
694            BinOp::And => 2,
695            BinOp::Eq | BinOp::Ne => 3,
696            BinOp::Lt | BinOp::Gt | BinOp::Le | BinOp::Ge => 4,
697            BinOp::Concat => 5,
698            BinOp::Add | BinOp::Sub => 6,
699            BinOp::Mul | BinOp::Div => 7,
700        }
701    }
702
703    /// Check if this operator is left-associative.
704    #[must_use]
705    pub fn is_left_assoc(self) -> bool {
706        // All our operators are left-associative
707        true
708    }
709}
710
711impl fmt::Display for BinOp {
712    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
713        match self {
714            BinOp::Add => write!(f, "+"),
715            BinOp::Sub => write!(f, "-"),
716            BinOp::Mul => write!(f, "*"),
717            BinOp::Div => write!(f, "/"),
718            BinOp::Eq => write!(f, "=="),
719            BinOp::Ne => write!(f, "!="),
720            BinOp::Lt => write!(f, "<"),
721            BinOp::Gt => write!(f, ">"),
722            BinOp::Le => write!(f, "<="),
723            BinOp::Ge => write!(f, ">="),
724            BinOp::And => write!(f, "&&"),
725            BinOp::Or => write!(f, "||"),
726            BinOp::Concat => write!(f, "++"),
727        }
728    }
729}
730
731/// Unary operators.
732#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
733pub enum UnaryOp {
734    /// `-` (negation)
735    Neg,
736    /// `!` (logical not)
737    Not,
738}
739
740impl fmt::Display for UnaryOp {
741    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
742        match self {
743            UnaryOp::Neg => write!(f, "-"),
744            UnaryOp::Not => write!(f, "!"),
745        }
746    }
747}
748
749// =============================================================================
750// Literals
751// =============================================================================
752
753/// A literal value.
754#[derive(Debug, Clone, PartialEq)]
755pub enum Literal {
756    /// Integer literal: `42`, `-7`
757    Int(i64),
758    /// Float literal: `3.14`, `-0.5`
759    Float(f64),
760    /// Boolean literal: `true`, `false`
761    Bool(bool),
762    /// String literal: `"hello"`
763    String(String),
764}
765
766impl fmt::Display for Literal {
767    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
768        match self {
769            Literal::Int(n) => write!(f, "{n}"),
770            Literal::Float(n) => write!(f, "{n}"),
771            Literal::Bool(b) => write!(f, "{b}"),
772            Literal::String(s) => write!(f, "\"{s}\""),
773        }
774    }
775}
776
777// =============================================================================
778// String templates (for interpolation)
779// =============================================================================
780
781/// A string template that may contain interpolations.
782///
783/// For example, `"Hello, {name}!"` becomes:
784/// ```text
785/// StringTemplate {
786///     parts: [
787///         StringPart::Literal("Hello, "),
788///         StringPart::Interpolation(Ident("name")),
789///         StringPart::Literal("!"),
790///     ]
791/// }
792/// ```
793#[derive(Debug, Clone, PartialEq)]
794pub struct StringTemplate {
795    /// The parts of the template.
796    pub parts: Vec<StringPart>,
797    /// Span covering the entire template string.
798    pub span: Span,
799}
800
801impl StringTemplate {
802    /// Create a simple template with no interpolations.
803    #[must_use]
804    pub fn literal(s: String, span: Span) -> Self {
805        Self {
806            parts: vec![StringPart::Literal(s)],
807            span,
808        }
809    }
810
811    /// Check if this template has any interpolations.
812    #[must_use]
813    pub fn has_interpolations(&self) -> bool {
814        self.parts
815            .iter()
816            .any(|p| matches!(p, StringPart::Interpolation(_)))
817    }
818
819    /// Get all interpolated identifiers.
820    pub fn interpolations(&self) -> impl Iterator<Item = &Ident> {
821        self.parts.iter().filter_map(|p| match p {
822            StringPart::Interpolation(ident) => Some(ident),
823            StringPart::Literal(_) => None,
824        })
825    }
826}
827
828impl fmt::Display for StringTemplate {
829    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
830        write!(f, "\"")?;
831        for part in &self.parts {
832            match part {
833                StringPart::Literal(s) => write!(f, "{s}")?,
834                StringPart::Interpolation(ident) => write!(f, "{{{ident}}}")?,
835            }
836        }
837        write!(f, "\"")
838    }
839}
840
841/// A part of a string template.
842#[derive(Debug, Clone, PartialEq)]
843pub enum StringPart {
844    /// A literal string segment.
845    Literal(String),
846    /// An interpolated identifier: `{ident}`
847    Interpolation(Ident),
848}
849
850// =============================================================================
851// Tests
852// =============================================================================
853
854#[cfg(test)]
855mod tests {
856    use super::*;
857
858    #[test]
859    fn binop_precedence() {
860        // Mul/Div > Add/Sub > Comparison > And > Or
861        assert!(BinOp::Mul.precedence() > BinOp::Add.precedence());
862        assert!(BinOp::Add.precedence() > BinOp::Lt.precedence());
863        assert!(BinOp::Lt.precedence() > BinOp::And.precedence());
864        assert!(BinOp::And.precedence() > BinOp::Or.precedence());
865    }
866
867    #[test]
868    fn binop_display() {
869        assert_eq!(format!("{}", BinOp::Add), "+");
870        assert_eq!(format!("{}", BinOp::Eq), "==");
871        assert_eq!(format!("{}", BinOp::Concat), "++");
872        assert_eq!(format!("{}", BinOp::And), "&&");
873    }
874
875    #[test]
876    fn unaryop_display() {
877        assert_eq!(format!("{}", UnaryOp::Neg), "-");
878        assert_eq!(format!("{}", UnaryOp::Not), "!");
879    }
880
881    #[test]
882    fn literal_display() {
883        assert_eq!(format!("{}", Literal::Int(42)), "42");
884        assert_eq!(format!("{}", Literal::Float(3.14)), "3.14");
885        assert_eq!(format!("{}", Literal::Bool(true)), "true");
886        assert_eq!(format!("{}", Literal::String("hello".into())), "\"hello\"");
887    }
888
889    #[test]
890    fn event_kind_display() {
891        assert_eq!(format!("{}", EventKind::Start), "start");
892        assert_eq!(format!("{}", EventKind::Stop), "stop");
893
894        let msg = EventKind::Message {
895            param_name: Ident::dummy("msg"),
896            param_ty: TypeExpr::String,
897        };
898        assert_eq!(format!("{msg}"), "message(msg: String)");
899    }
900
901    #[test]
902    fn string_template_literal() {
903        let template = StringTemplate::literal("hello".into(), Span::dummy());
904        assert!(!template.has_interpolations());
905        assert_eq!(format!("{template}"), "\"hello\"");
906    }
907
908    #[test]
909    fn string_template_with_interpolation() {
910        let template = StringTemplate {
911            parts: vec![
912                StringPart::Literal("Hello, ".into()),
913                StringPart::Interpolation(Ident::dummy("name")),
914                StringPart::Literal("!".into()),
915            ],
916            span: Span::dummy(),
917        };
918        assert!(template.has_interpolations());
919        assert_eq!(format!("{template}"), "\"Hello, {name}!\"");
920
921        let interps: Vec<_> = template.interpolations().collect();
922        assert_eq!(interps.len(), 1);
923        assert_eq!(interps[0].name, "name");
924    }
925
926    #[test]
927    fn expr_span() {
928        let span = Span::dummy();
929        let expr = Expr::Literal {
930            value: Literal::Int(42),
931            span: span.clone(),
932        };
933        assert_eq!(expr.span(), &span);
934    }
935
936    #[test]
937    fn stmt_span() {
938        let span = Span::dummy();
939        let stmt = Stmt::Return {
940            value: None,
941            span: span.clone(),
942        };
943        assert_eq!(stmt.span(), &span);
944    }
945}