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}