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}