busbar_sf_agentscript/ast.rs
1//! Abstract Syntax Tree types for AgentScript.
2//!
3//! This module defines all types representing a parsed AgentScript file.
4//! Every node in the AST is wrapped in [`Spanned`] to track its source location,
5//! enabling precise error reporting and IDE features like go-to-definition.
6//!
7//! For a comprehensive guide to the AST structure, node relationships, and UX development patterns,
8//! see the **[AST Reference](https://github.com/composable-delivery/sf-agentscript/blob/main/AST_REFERENCE.md)**.
9//!
10//! # AST Structure
11//!
12//! The root type is [`AgentFile`], which contains optional blocks:
13//!
14//! ```text
15//! AgentFile
16//! ├── config: ConfigBlock (agent metadata)
17//! ├── system: SystemBlock (global instructions/messages)
18//! ├── variables: VariablesBlock (state management)
19//! ├── connections: ConnectionsBlock (escalation routing)
20//! ├── language: LanguageBlock (locale settings)
21//! ├── start_agent: StartAgentBlock (entry point)
22//! └── topics: Vec<TopicBlock> (conversation topics)
23//! ```
24//!
25//! # Span Tracking
26//!
27//! All nodes use [`Spanned<T>`] to preserve source locations:
28//!
29//! ```rust
30//! use busbar_sf_agentscript::Spanned;
31//!
32//! // A spanned string with byte offsets 10..20
33//! let name = Spanned::new("MyAgent".to_string(), 10..20);
34//! assert_eq!(name.node, "MyAgent");
35//! assert_eq!(name.span, 10..20);
36//! ```
37//!
38//! # Serialization
39//!
40//! All types implement `Serialize` and `Deserialize` for JSON interop:
41//!
42//! ```rust
43//! # use busbar_sf_agentscript::parse;
44//! let source = "config:\n agent_name: \"Test\"\n";
45//! let agent = parse(source).unwrap();
46//! let json = serde_json::to_string(&agent).unwrap();
47//! ```
48
49use indexmap::IndexMap;
50use serde::{Deserialize, Serialize};
51use std::ops::Range;
52
53/// A span in the source code represented as byte offsets.
54///
55/// Used to track the exact location of every AST node in the original source,
56/// enabling precise error messages and IDE features.
57///
58/// # Example
59///
60/// ```rust
61/// use busbar_sf_agentscript::ast::Span;
62///
63/// let span: Span = 0..10; // Bytes 0 through 9
64/// assert_eq!(span.start, 0);
65/// assert_eq!(span.end, 10);
66/// ```
67pub type Span = Range<usize>;
68
69/// A value with an associated source span.
70///
71/// This wrapper type preserves source location information throughout the AST,
72/// enabling features like:
73/// - Precise error messages pointing to exact source locations
74/// - IDE go-to-definition and hover information
75/// - Source maps for debugging
76///
77/// # Example
78///
79/// ```rust
80/// use busbar_sf_agentscript::Spanned;
81///
82/// let spanned = Spanned::new("hello".to_string(), 5..10);
83/// assert_eq!(spanned.node, "hello");
84/// assert_eq!(spanned.span.start, 5);
85///
86/// // Transform the inner value while preserving span
87/// let upper = spanned.map(|s| s.to_uppercase());
88/// assert_eq!(upper.node, "HELLO");
89/// assert_eq!(upper.span.start, 5);
90/// ```
91#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
92pub struct Spanned<T> {
93 /// The wrapped value.
94 pub node: T,
95 /// Source location as byte offsets.
96 pub span: Span,
97}
98
99impl<T> Spanned<T> {
100 /// Create a new spanned value.
101 ///
102 /// # Example
103 ///
104 /// ```rust
105 /// use busbar_sf_agentscript::Spanned;
106 ///
107 /// let s = Spanned::new(42, 0..2);
108 /// assert_eq!(s.node, 42);
109 /// ```
110 pub fn new(node: T, span: Span) -> Self {
111 Self { node, span }
112 }
113
114 /// Transform the inner value while preserving the span.
115 ///
116 /// # Example
117 ///
118 /// ```rust
119 /// use busbar_sf_agentscript::Spanned;
120 ///
121 /// let s = Spanned::new(5, 0..1);
122 /// let doubled = s.map(|n| n * 2);
123 /// assert_eq!(doubled.node, 10);
124 /// assert_eq!(doubled.span, 0..1);
125 /// ```
126 pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Spanned<U> {
127 Spanned {
128 node: f(self.node),
129 span: self.span,
130 }
131 }
132}
133
134// ============================================================================
135// Top-Level File Structure
136// ============================================================================
137
138/// A complete parsed AgentScript file.
139///
140/// This is the root type returned by [`crate::parse()`]. It contains all the
141/// top-level blocks that make up an AgentScript definition.
142///
143/// # Structure
144///
145/// An AgentScript file typically contains:
146/// - `config:` - Required agent metadata
147/// - `system:` - Global instructions and messages
148/// - `variables:` - State variables
149/// - `start_agent:` - Entry point for the agent
150/// - One or more `topic:` blocks - Conversation topics
151///
152/// # Example
153///
154/// ```rust
155/// use busbar_sf_agentscript::parse;
156///
157/// let source = r#"
158/// config:
159/// agent_name: "MyAgent"
160///
161/// topic main:
162/// description: "Main topic"
163/// "#;
164///
165/// let agent = parse(source).unwrap();
166/// assert!(agent.config.is_some());
167/// assert_eq!(agent.topics.len(), 1);
168/// ```
169#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
170pub struct AgentFile {
171 /// Agent configuration block (`config:` section).
172 ///
173 /// Contains agent metadata like name, label, and description.
174 /// Should be the first block in the file.
175 pub config: Option<Spanned<ConfigBlock>>,
176
177 /// Variable declarations (`variables:` section).
178 ///
179 /// Defines state variables that can be `mutable` (read-write)
180 /// or `linked` (read-only from external context).
181 pub variables: Option<Spanned<VariablesBlock>>,
182
183 /// System configuration (`system:` section).
184 ///
185 /// Contains global instructions and system messages like
186 /// welcome and error messages.
187 pub system: Option<Spanned<SystemBlock>>,
188
189 /// Connection configurations (`connection <name>:` blocks).
190 ///
191 /// Each connection block defines escalation routing for a specific channel.
192 /// Multiple connection blocks can exist at the top level.
193 pub connections: Vec<Spanned<ConnectionBlock>>,
194
195 /// Knowledge base configuration (`knowledge:` section).
196 ///
197 /// Configures access to external knowledge sources.
198 pub knowledge: Option<Spanned<KnowledgeBlock>>,
199
200 /// Language/locale settings (`language:` section).
201 ///
202 /// Configures internationalization settings.
203 pub language: Option<Spanned<LanguageBlock>>,
204
205 /// Entry point (`start_agent:` section).
206 ///
207 /// The initial topic selector that routes conversations
208 /// to appropriate topics.
209 pub start_agent: Option<Spanned<StartAgentBlock>>,
210
211 /// Conversation topics (`topic:` sections).
212 ///
213 /// Each topic defines a conversational context with its own
214 /// reasoning instructions and available actions.
215 pub topics: Vec<Spanned<TopicBlock>>,
216}
217
218impl AgentFile {
219 /// Create a new empty AgentFile.
220 ///
221 /// # Example
222 ///
223 /// ```rust
224 /// use busbar_sf_agentscript::AgentFile;
225 ///
226 /// let agent = AgentFile::new();
227 /// assert!(agent.config.is_none());
228 /// assert!(agent.topics.is_empty());
229 /// ```
230 pub fn new() -> Self {
231 Self::default()
232 }
233}
234
235// ============================================================================
236// Config Block
237// ============================================================================
238
239/// Agent configuration block containing metadata.
240///
241/// This block should appear first in the file and defines identifying
242/// information about the agent.
243///
244/// # AgentScript Syntax
245///
246/// ```text
247/// config:
248/// agent_name: "CustomerSupport"
249/// agent_label: "Customer Support Agent"
250/// description: "Handles customer support inquiries"
251/// agent_type: "AgentforceServiceAgent"
252/// ```
253#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
254pub struct ConfigBlock {
255 /// The agent's unique identifier name (required).
256 ///
257 /// This is used internally to reference the agent.
258 pub agent_name: Spanned<String>,
259
260 /// Human-readable display label (optional).
261 ///
262 /// Shown in UIs when the agent_name is too technical.
263 pub agent_label: Option<Spanned<String>>,
264
265 /// Description of the agent's purpose.
266 pub description: Option<Spanned<String>>,
267
268 /// Agent type classification (e.g., "AgentforceServiceAgent").
269 pub agent_type: Option<Spanned<String>>,
270
271 /// Default user email for agent operations.
272 pub default_agent_user: Option<Spanned<String>>,
273}
274
275// ============================================================================
276// Variables Block
277// ============================================================================
278
279/// Variables block containing state variable declarations.
280///
281/// Variables in AgentScript can be either `mutable` (read-write) or `linked`
282/// (read-only from external context).
283///
284/// # AgentScript Syntax
285///
286/// ```text
287/// variables:
288/// customer_name: mutable string = ""
289/// description: "Customer's name"
290/// order_total: linked number
291/// source: @context.order.total
292/// description: "Total order amount"
293/// ```
294#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
295pub struct VariablesBlock {
296 /// List of variable declarations.
297 pub variables: Vec<Spanned<VariableDecl>>,
298}
299
300/// A single variable declaration.
301///
302/// Variables have a name, kind (mutable/linked), type, and optional metadata.
303/// Mutable variables require a default value; linked variables require a source.
304#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
305pub struct VariableDecl {
306 /// Variable name (used in references like `@variables.name`).
307 pub name: Spanned<String>,
308
309 /// Whether the variable is mutable or linked.
310 pub kind: VariableKind,
311
312 /// The variable's data type.
313 pub ty: Spanned<Type>,
314
315 /// Default value (required for mutable variables).
316 ///
317 /// Example: `mutable string = "default"`
318 pub default: Option<Spanned<Expr>>,
319
320 /// Human-readable description.
321 pub description: Option<Spanned<String>>,
322
323 /// Source reference for linked variables.
324 ///
325 /// Example: `source: @context.user.email`
326 pub source: Option<Spanned<Reference>>,
327}
328
329/// Variable mutability kind.
330#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
331pub enum VariableKind {
332 /// Mutable variable - agent can read and write.
333 ///
334 /// Requires a default value in the declaration.
335 Mutable,
336
337 /// Linked variable - read-only from external context.
338 ///
339 /// Requires a source reference pointing to external data.
340 Linked,
341}
342
343/// Data type for variables and action parameters.
344///
345/// AgentScript supports a fixed set of primitive types plus `List` for arrays.
346///
347/// # Example Types
348///
349/// | Type | Description |
350/// |------|-------------|
351/// | `string` | Text data |
352/// | `number` | Floating-point number |
353/// | `boolean` | True/False |
354/// | `integer` | Whole number |
355/// | `id` | Salesforce record ID |
356/// | `list<string>` | Array of strings |
357#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
358pub enum Type {
359 /// Text data (`string`).
360 String,
361 /// Floating-point number (`number`).
362 Number,
363 /// Boolean value (`boolean`).
364 Boolean,
365 /// Generic object (`object`).
366 Object,
367 /// Date without time (`date`).
368 Date,
369 /// Unix timestamp (`timestamp`).
370 Timestamp,
371 /// Currency value (`currency`).
372 Currency,
373 /// Salesforce record ID (`id`).
374 Id,
375 /// Date with time (`datetime`).
376 Datetime,
377 /// Time without date (`time`).
378 Time,
379 /// Whole number (`integer`).
380 Integer,
381 /// Large integer (`long`).
382 Long,
383 /// Array type (`list<T>`).
384 List(Box<Type>),
385}
386
387impl Type {
388 /// Parse a type from its string representation.
389 ///
390 /// # Example
391 ///
392 /// ```rust
393 /// use busbar_sf_agentscript::Type;
394 ///
395 /// assert_eq!(Type::parse_type("string"), Some(Type::String));
396 /// assert_eq!(Type::parse_type("number"), Some(Type::Number));
397 /// assert_eq!(Type::parse_type("unknown"), None);
398 /// ```
399 pub fn parse_type(s: &str) -> Option<Self> {
400 match s {
401 "string" => Some(Type::String),
402 "number" => Some(Type::Number),
403 "boolean" => Some(Type::Boolean),
404 "object" => Some(Type::Object),
405 "date" => Some(Type::Date),
406 "timestamp" => Some(Type::Timestamp),
407 "currency" => Some(Type::Currency),
408 "id" => Some(Type::Id),
409 "datetime" => Some(Type::Datetime),
410 "time" => Some(Type::Time),
411 "integer" => Some(Type::Integer),
412 "long" => Some(Type::Long),
413 _ => None,
414 }
415 }
416}
417
418// ============================================================================
419// System Block
420// ============================================================================
421
422/// The system block defines global agent settings.
423#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
424pub struct SystemBlock {
425 /// System messages.
426 pub messages: Option<Spanned<SystemMessages>>,
427 /// System instructions.
428 pub instructions: Option<Spanned<Instructions>>,
429}
430
431/// System messages (welcome, error).
432#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
433pub struct SystemMessages {
434 pub welcome: Option<Spanned<String>>,
435 pub error: Option<Spanned<String>>,
436}
437
438// ============================================================================
439// Connection Block
440// ============================================================================
441
442/// A connection block defines escalation routing for a specific channel.
443///
444/// # AgentScript Syntax
445///
446/// ```text
447/// connection messaging:
448/// escalation_message: "I'm connecting you with a specialist."
449/// outbound_route_type: "OmniChannelFlow"
450/// outbound_route_name: "SpecialistQueue"
451/// ```
452#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
453pub struct ConnectionBlock {
454 /// Connection name (e.g., "messaging").
455 pub name: Spanned<String>,
456 /// Connection configuration entries.
457 pub entries: Vec<Spanned<ConnectionEntry>>,
458}
459
460/// A key-value entry in a connection block.
461#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
462pub struct ConnectionEntry {
463 pub name: Spanned<String>,
464 pub value: Spanned<String>,
465}
466
467// Legacy type alias for backwards compatibility
468// TODO: Remove in next major version
469/// @deprecated Use ConnectionBlock instead - this type exists only for backwards compatibility
470pub type ConnectionsBlock = ConnectionBlock;
471
472// ============================================================================
473// Knowledge Block
474// ============================================================================
475
476/// The knowledge block configures knowledge base access.
477#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
478pub struct KnowledgeBlock {
479 /// Knowledge configuration entries.
480 pub entries: Vec<Spanned<KnowledgeEntry>>,
481}
482
483#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
484pub struct KnowledgeEntry {
485 pub name: Spanned<String>,
486 pub value: Spanned<Expr>,
487}
488
489// ============================================================================
490// Language Block
491// ============================================================================
492
493/// The language block configures locale settings.
494#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
495pub struct LanguageBlock {
496 /// Language setting entries.
497 pub entries: Vec<Spanned<LanguageEntry>>,
498}
499
500#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
501pub struct LanguageEntry {
502 pub name: Spanned<String>,
503 pub value: Spanned<Expr>,
504}
505
506// ============================================================================
507// Start Agent Block
508// ============================================================================
509
510/// The start_agent block is the entry point for the agent.
511#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
512pub struct StartAgentBlock {
513 /// The name after start_agent (typically "topic_selector").
514 pub name: Spanned<String>,
515 /// Description of the start agent.
516 pub description: Option<Spanned<String>>,
517 /// Optional system override.
518 pub system: Option<Spanned<TopicSystemOverride>>,
519 /// Optional action definitions.
520 pub actions: Option<Spanned<ActionsBlock>>,
521 /// Optional before_reasoning block.
522 pub before_reasoning: Option<Spanned<DirectiveBlock>>,
523 /// The reasoning block (required).
524 pub reasoning: Option<Spanned<ReasoningBlock>>,
525 /// Optional after_reasoning block.
526 pub after_reasoning: Option<Spanned<DirectiveBlock>>,
527}
528
529// ============================================================================
530// Topic Block
531// ============================================================================
532
533/// A topic block defines a conversation topic.
534#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
535pub struct TopicBlock {
536 /// Topic name.
537 pub name: Spanned<String>,
538 /// Description of the topic.
539 pub description: Option<Spanned<String>>,
540 /// Optional system override.
541 pub system: Option<Spanned<TopicSystemOverride>>,
542 /// Optional action definitions.
543 pub actions: Option<Spanned<ActionsBlock>>,
544 /// Optional before_reasoning block.
545 pub before_reasoning: Option<Spanned<DirectiveBlock>>,
546 /// The reasoning block (required).
547 pub reasoning: Option<Spanned<ReasoningBlock>>,
548 /// Optional after_reasoning block.
549 pub after_reasoning: Option<Spanned<DirectiveBlock>>,
550}
551
552/// System instruction override for a topic.
553#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
554pub struct TopicSystemOverride {
555 pub instructions: Option<Spanned<Instructions>>,
556}
557
558// ============================================================================
559// Actions Block
560// ============================================================================
561
562/// A block of action definitions.
563#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
564pub struct ActionsBlock {
565 pub actions: Vec<Spanned<ActionDef>>,
566}
567
568/// An action definition.
569#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
570pub struct ActionDef {
571 /// Action name.
572 pub name: Spanned<String>,
573 /// Description.
574 pub description: Option<Spanned<String>>,
575 /// Display label.
576 pub label: Option<Spanned<String>>,
577 /// Whether user confirmation is required.
578 pub require_user_confirmation: Option<Spanned<bool>>,
579 /// Whether to show progress indicator.
580 pub include_in_progress_indicator: Option<Spanned<bool>>,
581 /// Progress indicator message.
582 pub progress_indicator_message: Option<Spanned<String>>,
583 /// Input parameters.
584 pub inputs: Option<Spanned<Vec<Spanned<ParamDef>>>>,
585 /// Output parameters.
586 pub outputs: Option<Spanned<Vec<Spanned<ParamDef>>>>,
587 /// Target (e.g., "flow://FlowName").
588 pub target: Option<Spanned<String>>,
589}
590
591/// A parameter definition (for action inputs/outputs).
592#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
593pub struct ParamDef {
594 /// Parameter name.
595 pub name: Spanned<String>,
596 /// Parameter type.
597 pub ty: Spanned<Type>,
598 /// Description.
599 pub description: Option<Spanned<String>>,
600 /// Display label.
601 pub label: Option<Spanned<String>>,
602 /// Whether required (for inputs).
603 pub is_required: Option<Spanned<bool>>,
604 /// Whether to filter from agent.
605 pub filter_from_agent: Option<Spanned<bool>>,
606 /// Whether displayable.
607 pub is_displayable: Option<Spanned<bool>>,
608 /// Complex data type name.
609 pub complex_data_type_name: Option<Spanned<String>>,
610}
611
612// ============================================================================
613// Directive Blocks (before_reasoning, after_reasoning)
614// ============================================================================
615
616/// A directive block contains imperative statements.
617#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
618pub struct DirectiveBlock {
619 pub statements: Vec<Spanned<Stmt>>,
620}
621
622/// A statement in a directive block.
623#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
624pub enum Stmt {
625 /// Variable assignment: `set @variables.x = expr`
626 Set {
627 target: Spanned<Reference>,
628 value: Spanned<Expr>,
629 },
630 /// Action invocation: `run @actions.foo`
631 Run {
632 action: Spanned<Reference>,
633 with_clauses: Vec<Spanned<WithClause>>,
634 set_clauses: Vec<Spanned<SetClause>>,
635 },
636 /// Conditional: `if expr:`
637 If {
638 condition: Spanned<Expr>,
639 then_block: Vec<Spanned<Stmt>>,
640 else_block: Option<Vec<Spanned<Stmt>>>,
641 },
642 /// Transition: `transition to @topic.name`
643 Transition { target: Spanned<Reference> },
644}
645
646/// A with clause binds an action input.
647#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
648pub struct WithClause {
649 pub param: Spanned<String>,
650 pub value: Spanned<WithValue>,
651}
652
653/// The value of a with clause.
654#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
655pub enum WithValue {
656 /// A concrete expression.
657 Expr(Expr),
658}
659
660/// A set clause assigns a value to a variable.
661#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
662pub struct SetClause {
663 pub target: Spanned<Reference>,
664 /// The value to assign (can be reference, literal, or expression).
665 pub source: Spanned<Expr>,
666}
667
668/// Reference to an action output.
669#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
670pub struct OutputRef {
671 /// The output name (e.g., "result" from "@outputs.result").
672 pub name: String,
673}
674
675// ============================================================================
676// Reasoning Block
677// ============================================================================
678
679/// The reasoning block configures LLM reasoning.
680#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
681pub struct ReasoningBlock {
682 /// Instructions for the LLM.
683 pub instructions: Option<Spanned<Instructions>>,
684 /// Actions available to the LLM.
685 pub actions: Option<Spanned<Vec<Spanned<ReasoningAction>>>>,
686}
687
688/// An action available during reasoning.
689#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
690pub struct ReasoningAction {
691 /// Action alias name.
692 pub name: Spanned<String>,
693 /// Target reference (e.g., `@actions.foo` or `@utils.transition to @topic.x`).
694 pub target: Spanned<ReasoningActionTarget>,
695 /// Optional description override.
696 pub description: Option<Spanned<String>>,
697 /// Availability condition.
698 pub available_when: Option<Spanned<Expr>>,
699 /// Input bindings.
700 pub with_clauses: Vec<Spanned<WithClause>>,
701 /// Output captures.
702 pub set_clauses: Vec<Spanned<SetClause>>,
703 /// Chained run statements.
704 pub run_clauses: Vec<Spanned<RunClause>>,
705 /// Post-action if conditions.
706 pub if_clauses: Vec<Spanned<IfClause>>,
707 /// Post-action transition.
708 pub transition: Option<Spanned<Reference>>,
709}
710
711/// Target of a reasoning action.
712#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
713pub enum ReasoningActionTarget {
714 /// Reference to an action: `@actions.foo`
715 Action(Reference),
716 /// Transition utility: `@utils.transition to @topic.x`
717 TransitionTo(Reference),
718 /// Escalate utility: `@utils.escalate`
719 Escalate,
720 /// Set variables utility: `@utils.setVariables`
721 SetVariables,
722 /// Topic delegation: `@topic.x`
723 TopicDelegate(Reference),
724}
725
726/// A chained run clause in reasoning actions.
727#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
728pub struct RunClause {
729 pub action: Spanned<Reference>,
730 pub with_clauses: Vec<Spanned<WithClause>>,
731 pub set_clauses: Vec<Spanned<SetClause>>,
732}
733
734/// A conditional clause in reasoning actions.
735#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
736pub struct IfClause {
737 pub condition: Spanned<Expr>,
738 pub transition: Option<Spanned<Reference>>,
739}
740
741// ============================================================================
742// Instructions
743// ============================================================================
744
745/// Instructions can be static or dynamic.
746#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
747pub enum Instructions {
748 /// Simple string instructions.
749 Simple(String),
750 /// Static multiline (`:| ...`).
751 Static(Vec<Spanned<String>>),
752 /// Dynamic with conditionals (`:-> ...`).
753 Dynamic(Vec<Spanned<InstructionPart>>),
754}
755
756/// A part of dynamic instructions.
757#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
758pub enum InstructionPart {
759 /// Literal text line (prefixed with `|`).
760 Text(String),
761 /// Template interpolation `{!expr}`.
762 Interpolation(Expr),
763 /// Conditional section.
764 Conditional {
765 condition: Spanned<Expr>,
766 then_parts: Vec<Spanned<InstructionPart>>,
767 else_parts: Option<Vec<Spanned<InstructionPart>>>,
768 },
769}
770
771// ============================================================================
772// Expressions
773// ============================================================================
774
775/// An expression in AgentScript.
776///
777/// Expressions can appear in conditions, variable assignments, action bindings,
778/// and dynamic instruction interpolation.
779///
780/// # Variants
781///
782/// | Variant | Example | Description |
783/// |---------|---------|-------------|
784/// | `Reference` | `@variables.name` | Reference to a namespaced resource |
785/// | `String` | `"hello"` | String literal |
786/// | `Number` | `42`, `3.14` | Numeric literal |
787/// | `Bool` | `True`, `False` | Boolean literal |
788/// | `None` | `None` | Null value |
789/// | `List` | `[1, 2, 3]` | Array literal |
790/// | `Object` | `{key: value}` | Object literal |
791/// | `BinOp` | `a == b` | Binary operation |
792/// | `UnaryOp` | `not x` | Unary operation |
793/// | `Ternary` | `x if cond else y` | Conditional expression |
794/// | `Property` | `obj.field` | Property access |
795/// | `Index` | `arr[0]` | Index access |
796#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
797pub enum Expr {
798 /// Reference to a namespaced resource: `@namespace.path`.
799 ///
800 /// Common namespaces: `variables`, `actions`, `outputs`, `topic`, `utils`.
801 Reference(Reference),
802
803 /// String literal: `"text"`.
804 String(String),
805
806 /// Numeric literal: `42` or `3.14`.
807 Number(f64),
808
809 /// Boolean literal: `True` or `False`.
810 Bool(bool),
811
812 /// Null value: `None`.
813 None,
814
815 /// Slot-fill token: `...`
816 ///
817 /// Instructs the LLM to infer the value. Used in `with` clauses:
818 /// `with order_id = ...`
819 SlotFill,
820
821 /// Array literal: `[item1, item2, ...]`.
822 List(Vec<Spanned<Expr>>),
823
824 /// Object literal: `{key1: value1, key2: value2}`.
825 Object(IndexMap<String, Spanned<Expr>>),
826
827 /// Binary operation: `left op right`.
828 BinOp {
829 /// Left operand.
830 left: Box<Spanned<Expr>>,
831 /// Operator.
832 op: BinOp,
833 /// Right operand.
834 right: Box<Spanned<Expr>>,
835 },
836
837 /// Unary operation: `op operand`.
838 UnaryOp {
839 /// Operator (e.g., `not`, `-`).
840 op: UnaryOp,
841 /// Operand.
842 operand: Box<Spanned<Expr>>,
843 },
844
845 /// Ternary conditional: `then_expr if condition else else_expr`.
846 ///
847 /// Note: Python-style ordering (value first, then condition).
848 Ternary {
849 /// The condition to evaluate.
850 condition: Box<Spanned<Expr>>,
851 /// Value if condition is true.
852 then_expr: Box<Spanned<Expr>>,
853 /// Value if condition is false.
854 else_expr: Box<Spanned<Expr>>,
855 },
856
857 /// Property access: `object.field`.
858 Property {
859 /// The object to access.
860 object: Box<Spanned<Expr>>,
861 /// The field name.
862 field: Spanned<String>,
863 },
864
865 /// Index access: `object[index]`.
866 Index {
867 /// The array/object to index.
868 object: Box<Spanned<Expr>>,
869 /// The index expression.
870 index: Box<Spanned<Expr>>,
871 },
872}
873
874/// A reference to a namespaced resource.
875///
876/// References use the `@namespace.path` syntax to access variables, actions,
877/// topics, and utilities.
878///
879/// # Common Namespaces
880///
881/// | Namespace | Purpose | Example |
882/// |-----------|---------|---------|
883/// | `variables` | State variables | `@variables.customer_id` |
884/// | `actions` | Action definitions | `@actions.lookup_order` |
885/// | `outputs` | Action outputs | `@outputs.result` |
886/// | `topic` | Topic references | `@topic.support` |
887/// | `utils` | Built-in utilities | `@utils.transition` |
888/// | `context` | External context | `@context.user.email` |
889///
890/// # Example
891///
892/// ```rust
893/// use busbar_sf_agentscript::Reference;
894///
895/// let ref1 = Reference::new("variables", vec!["customer_id".to_string()]);
896/// assert_eq!(ref1.full_path(), "@variables.customer_id");
897///
898/// let ref2 = Reference::new("utils", vec!["transition".to_string()]);
899/// assert_eq!(ref2.full_path(), "@utils.transition");
900/// ```
901#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
902pub struct Reference {
903 /// The namespace (e.g., "variables", "actions", "outputs", "utils", "topic").
904 pub namespace: String,
905
906 /// Path components after the namespace.
907 ///
908 /// For `@variables.user.name`, this would be `["user", "name"]`.
909 pub path: Vec<String>,
910}
911
912impl Reference {
913 /// Create a new reference.
914 ///
915 /// # Example
916 ///
917 /// ```rust
918 /// use busbar_sf_agentscript::Reference;
919 ///
920 /// let r = Reference::new("variables", vec!["name".to_string()]);
921 /// assert_eq!(r.namespace, "variables");
922 /// ```
923 pub fn new(namespace: impl Into<String>, path: Vec<String>) -> Self {
924 Self {
925 namespace: namespace.into(),
926 path,
927 }
928 }
929
930 /// Get the full reference path as a string.
931 ///
932 /// # Example
933 ///
934 /// ```rust
935 /// use busbar_sf_agentscript::Reference;
936 ///
937 /// let r = Reference::new("actions", vec!["send_email".to_string()]);
938 /// assert_eq!(r.full_path(), "@actions.send_email");
939 /// ```
940 pub fn full_path(&self) -> String {
941 if self.path.is_empty() {
942 format!("@{}", self.namespace)
943 } else {
944 format!("@{}.{}", self.namespace, self.path.join("."))
945 }
946 }
947}
948
949/// Binary operators.
950#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
951pub enum BinOp {
952 // Comparison
953 Eq, // ==
954 Ne, // !=
955 Lt, // <
956 Gt, // >
957 Le, // <=
958 Ge, // >=
959 Is, // is
960 IsNot, // is not
961
962 // Logical
963 And, // and
964 Or, // or
965
966 // Arithmetic
967 Add, // +
968 Sub, // -
969}
970
971impl BinOp {
972 /// Get the precedence of this operator (higher binds tighter).
973 pub fn precedence(&self) -> u8 {
974 match self {
975 BinOp::Or => 1,
976 BinOp::And => 2,
977 BinOp::Is | BinOp::IsNot => 3,
978 BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Gt | BinOp::Le | BinOp::Ge => 4,
979 BinOp::Add | BinOp::Sub => 5,
980 }
981 }
982}
983
984/// Unary operators.
985#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
986pub enum UnaryOp {
987 Not, // not
988 Neg, // - (negative)
989}
990
991// ============================================================================
992// Comments
993// ============================================================================
994
995/// A comment in the source.
996#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
997pub struct Comment {
998 pub text: String,
999 pub span: Span,
1000}