Skip to main content

varpulis_core/
ast.rs

1//! Abstract Syntax Tree for VPL
2
3use crate::span::Spanned;
4use crate::types::Type;
5use serde::{Deserialize, Serialize};
6
7/// A complete VPL program
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9pub struct Program {
10    /// The top-level statements that make up the program.
11    pub statements: Vec<Spanned<Stmt>>,
12}
13
14/// Top-level statement
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16pub enum Stmt {
17    /// Connector declaration: `connector MyMqtt = mqtt (host: "localhost", port: 1883)`
18    ConnectorDecl {
19        /// Connector name.
20        name: String,
21        /// Connector protocol type (e.g., `"mqtt"`, `"kafka"`).
22        connector_type: String,
23        /// Connection parameters.
24        params: Vec<ConnectorParam>,
25    },
26    /// Stream declaration: `stream X = Y` or `stream X = Y.where(...)`
27    StreamDecl {
28        /// Stream name.
29        name: String,
30        /// Optional type annotation for the stream.
31        type_annotation: Option<Type>,
32        /// Event source for this stream.
33        source: StreamSource,
34        /// Chained stream operations.
35        ops: Vec<StreamOp>,
36        /// Per-operation source spans (parallel to `ops`). Empty when spans unavailable.
37        #[serde(default)]
38        op_spans: Vec<crate::span::Span>,
39    },
40    /// Event declaration: `event X: ...`
41    EventDecl {
42        /// Event type name.
43        name: String,
44        /// Parent event type if this event extends another.
45        extends: Option<String>,
46        /// Fields declared on this event type.
47        fields: Vec<Field>,
48    },
49    /// Type alias: `type X = Y`
50    TypeDecl {
51        /// Alias name.
52        name: String,
53        /// The aliased type.
54        ty: Type,
55    },
56    /// Variable declaration: `let x = ...` or `var x = ...`
57    VarDecl {
58        /// Whether the variable can be reassigned.
59        mutable: bool,
60        /// Variable name.
61        name: String,
62        /// Optional type annotation.
63        ty: Option<Type>,
64        /// Initial value expression.
65        value: Expr,
66    },
67    /// Constant declaration: `const X = ...`
68    ConstDecl {
69        /// Constant name.
70        name: String,
71        /// Optional type annotation.
72        ty: Option<Type>,
73        /// Constant value expression.
74        value: Expr,
75    },
76    /// Function declaration: `fn x(...) -> T: ...`
77    FnDecl {
78        /// Function name.
79        name: String,
80        /// Function parameters.
81        params: Vec<Param>,
82        /// Optional return type.
83        ret: Option<Type>,
84        /// Function body statements.
85        body: Vec<Spanned<Self>>,
86    },
87    /// Configuration block with name (e.g., `config mqtt { ... }`)
88    ///
89    /// **Deprecated:** Use `connector` declarations instead.
90    /// ```vpl
91    /// connector MyMqtt = mqtt (
92    ///     broker: "localhost",
93    ///     port: 1883,
94    ///     topic: "events/#"
95    /// )
96    /// ```
97    Config {
98        /// Configuration block name.
99        name: String,
100        /// Configuration key-value items.
101        items: Vec<ConfigItem>,
102    },
103    /// Import statement
104    Import {
105        /// Module path to import.
106        path: String,
107        /// Optional import alias.
108        alias: Option<String>,
109    },
110    /// Expression statement
111    Expr(Expr),
112    /// If statement
113    If {
114        /// Condition expression.
115        cond: Expr,
116        /// Statements to execute when the condition is true.
117        then_branch: Vec<Spanned<Self>>,
118        /// Optional else-if branches (condition, body pairs).
119        elif_branches: Vec<(Expr, Vec<Spanned<Self>>)>,
120        /// Optional else branch.
121        else_branch: Option<Vec<Spanned<Self>>>,
122    },
123    /// For loop
124    For {
125        /// Loop variable name.
126        var: String,
127        /// Iterable expression.
128        iter: Expr,
129        /// Loop body statements.
130        body: Vec<Spanned<Self>>,
131    },
132    /// While loop
133    While {
134        /// Loop condition.
135        cond: Expr,
136        /// Loop body statements.
137        body: Vec<Spanned<Self>>,
138    },
139    /// Return statement
140    Return(Option<Expr>),
141    /// Break statement
142    Break,
143    /// Continue statement
144    Continue,
145    /// Emit event statement: `emit EventType(field1: expr1, field2: expr2)`
146    Emit {
147        /// Event type to emit.
148        event_type: String,
149        /// Named field values for the emitted event.
150        fields: Vec<NamedArg>,
151    },
152    /// Variable assignment: `name := value`
153    Assignment {
154        /// Variable name to assign.
155        name: String,
156        /// New value expression.
157        value: Expr,
158    },
159    /// SASE+ Pattern declaration: `pattern Name = SEQ(A, B+) within 1h partition by user_id`
160    PatternDecl {
161        /// Pattern name.
162        name: String,
163        /// SASE+ pattern expression.
164        expr: SasePatternExpr,
165        /// Optional time window constraint.
166        within: Option<Expr>,
167        /// Optional partition key expression.
168        partition_by: Option<Expr>,
169    },
170    /// Context declaration: `context name (cores: [0, 1])`
171    ContextDecl {
172        /// Context name.
173        name: String,
174        /// Optional CPU core affinity list.
175        cores: Option<Vec<usize>>,
176    },
177}
178
179/// Connector parameter: `host: "localhost"` or `port: 1883`
180#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
181pub struct ConnectorParam {
182    /// Parameter name.
183    pub name: String,
184    /// Parameter value.
185    pub value: ConfigValue,
186}
187
188/// SASE+ Pattern Expression for complex event processing
189#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
190pub enum SasePatternExpr {
191    /// Sequence: SEQ(A, B, C)
192    Seq(Vec<SasePatternItem>),
193    /// Conjunction: A AND B
194    And(Box<Self>, Box<Self>),
195    /// Disjunction: A OR B
196    Or(Box<Self>, Box<Self>),
197    /// Negation: NOT A
198    Not(Box<Self>),
199    /// Single event type reference
200    Event(String),
201    /// Grouped expression
202    Group(Box<Self>),
203}
204
205/// Item in a SASE+ sequence with optional Kleene operator
206#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
207pub struct SasePatternItem {
208    /// Event type name
209    pub event_type: String,
210    /// Optional alias for the event
211    pub alias: Option<String>,
212    /// Kleene operator: None, Plus (+), Star (*), Optional (?)
213    pub kleene: Option<KleeneOp>,
214    /// Optional filter condition
215    pub filter: Option<Expr>,
216}
217
218/// Kleene operators for SASE+ patterns
219#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
220pub enum KleeneOp {
221    /// One or more (+)
222    Plus,
223    /// Zero or more (*)
224    Star,
225    /// Zero or one (?)
226    Optional,
227}
228
229/// Stream source
230#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
231pub enum StreamSource {
232    /// Reference to existing stream with optional alias
233    Ident(String),
234    /// Event type with optional alias: `EventType as alias`
235    IdentWithAlias {
236        /// Event type or stream name.
237        name: String,
238        /// Alias for the source.
239        alias: String,
240    },
241    /// Event type with all quantifier and optional alias: `all EventType as alias`
242    AllWithAlias {
243        /// Event type name.
244        name: String,
245        /// Optional alias for the source.
246        alias: Option<String>,
247    },
248    /// From connector: `EventType.from(Connector, topic: "...", qos: 1)`
249    FromConnector {
250        /// Event type to receive from the connector.
251        event_type: String,
252        /// Connector name to read from.
253        connector_name: String,
254        /// Additional connector parameters.
255        params: Vec<ConnectorParam>,
256    },
257    /// Merge multiple streams
258    Merge(Vec<InlineStreamDecl>),
259    /// Join multiple streams
260    Join(Vec<JoinClause>),
261    /// Sequence construct: `sequence(step1: Event1, step2: Event2 where cond, ...)`
262    Sequence(SequenceDecl),
263    /// Periodic timer source: `timer(5s)` or `timer(5s, initial_delay: 1s)`
264    Timer(TimerDecl),
265}
266
267/// Timer declaration for periodic event generation
268#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
269pub struct TimerDecl {
270    /// Interval between timer fires (duration expression)
271    pub interval: Expr,
272    /// Optional initial delay before first fire
273    pub initial_delay: Option<Box<Expr>>,
274}
275
276/// Sequence declaration for temporal event correlation
277#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
278pub struct SequenceDecl {
279    /// Whether to match all first events or just one
280    pub match_all: bool,
281    /// Global timeout for the sequence
282    pub timeout: Option<Box<Expr>>,
283    /// Named steps in the sequence
284    pub steps: Vec<SequenceStepDecl>,
285}
286
287/// A single step in a sequence declaration
288#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
289pub struct SequenceStepDecl {
290    /// Alias for the captured event
291    pub alias: String,
292    /// Event type to match
293    pub event_type: String,
294    /// Optional filter condition
295    pub filter: Option<Expr>,
296    /// Optional timeout for this step
297    pub timeout: Option<Box<Expr>>,
298}
299
300/// Inline stream declaration used in merge/join
301#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
302pub struct InlineStreamDecl {
303    /// Name alias for this inline stream.
304    pub name: String,
305    /// Source event type or stream name.
306    pub source: String,
307    /// Optional filter condition for this source.
308    pub filter: Option<Expr>,
309}
310
311/// Join type for stream correlation
312#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
313pub enum JoinType {
314    /// Inner join (default) — emit only when all sources match
315    #[default]
316    Inner,
317    /// Left join — emit when left source has an event, fill nulls for missing right
318    Left,
319    /// Right join — emit when right source has an event, fill nulls for missing left
320    Right,
321    /// Full outer join — emit for either side, fill nulls for missing sources
322    Full,
323}
324
325/// Join clause
326#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
327pub struct JoinClause {
328    /// Name alias for this join source.
329    pub name: String,
330    /// Source event type or stream name.
331    pub source: String,
332    /// Optional join condition expression.
333    pub on: Option<Expr>,
334    /// Type of join (inner, left, right, full).
335    #[serde(default)]
336    pub join_type: JoinType,
337}
338
339/// Stream operation (method call on stream)
340#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
341pub enum StreamOp {
342    /// Filter: `.where(cond)`
343    Where(Expr),
344    /// Projection: `.select(fields)`
345    Select(Vec<SelectItem>),
346    /// Window: `.window(duration, ...)`
347    Window(WindowArgs),
348    /// Aggregation: `.aggregate(...)`
349    Aggregate(Vec<AggItem>),
350    /// Having: `.having(cond)` - filter after aggregation
351    Having(Expr),
352    /// Partitioning: `.partition_by(key)`
353    PartitionBy(Expr),
354    /// Ordering: `.order_by(...)`
355    OrderBy(Vec<OrderItem>),
356    /// Limit: `.limit(n)`
357    Limit(Expr),
358    /// Distinct: `.distinct(expr?)`
359    Distinct(Option<Expr>),
360    /// Map: `.map(fn)`
361    Map(Expr),
362    /// Filter with lambda: `.filter(fn)`
363    Filter(Expr),
364    /// Tap for observability: `.tap(...)`
365    Tap(Vec<NamedArg>),
366    /// Print to console: `.print(...)` or `.print("message", expr)`
367    Print(Vec<Expr>),
368    /// Log with level: `.log(level: "info", message: "...", data: expr)`
369    Log(Vec<NamedArg>),
370    /// Emit output: `.emit(fields...)` or `.emit as Type (fields...)`
371    Emit {
372        /// Optional type cast: `.emit as Alert (...)`
373        output_type: Option<String>,
374        /// Field mappings
375        fields: Vec<NamedArg>,
376        /// Optional target context for cross-context emit: `.emit(context: ctx_name, ...)`
377        target_context: Option<String>,
378    },
379    /// Send to connector: `.to(Connector, topic: "...", method: "POST")`
380    To {
381        /// Target connector name.
382        connector_name: String,
383        /// Sink parameters (topic, method, etc.).
384        params: Vec<ConnectorParam>,
385    },
386    /// Send to destination (legacy): `.to(target)`
387    ToExpr(Expr),
388    /// Pattern matching: `.pattern(...)`
389    Pattern(PatternDef),
390    /// Concurrent processing: `.concurrent(...)`
391    Concurrent(Vec<NamedArg>),
392    /// Process with function: `.process(fn)`
393    Process(Expr),
394    /// Error handler: `.on_error(fn)`
395    OnError(Expr),
396    /// Collect results: `.collect()`
397    Collect,
398    /// Join condition: `.on(expr)`
399    On(Expr),
400    /// Followed-by sequence: `-> EventType where condition as alias`
401    FollowedBy(FollowedByClause),
402    /// Timeout constraint: `.within(duration)`
403    Within(Expr),
404    /// Negation: `.not(EventType where condition)`
405    Not(FollowedByClause),
406    /// Fork into multiple parallel paths: `.fork(path1: ..., path2: ...)`
407    Fork(Vec<ForkPath>),
408    /// Wait for any path to complete: `.any()` or `.any(n)` for at least n
409    Any(Option<usize>),
410    /// Wait for all paths to complete: `.all()`
411    All,
412    /// Wait for first path to complete: `.first()`
413    First,
414    /// Assign stream to a context: `.context(name)`
415    Context(String),
416    /// Watermark configuration: `.watermark(out_of_order: 10s)`
417    Watermark(Vec<NamedArg>),
418    /// Allowed lateness for late data: `.allowed_lateness(30s)`
419    AllowedLateness(Expr),
420    /// Trend aggregation over Kleene patterns (Hamlet engine):
421    /// `.trend_aggregate(count: count_trends(), events: count_events(rising))`
422    TrendAggregate(Vec<TrendAggItem>),
423    /// ONNX model scoring: `.score(model: "path.onnx", inputs: [...], outputs: [...])`
424    Score(ScoreSpec),
425    /// PST-based pattern forecasting: `.forecast(confidence: 0.7, horizon: 2m, warmup: 500, max_depth: 5)`
426    Forecast(ForecastSpec),
427    /// External connector enrichment: `.enrich(WeatherAPI, key: t.city, fields: [forecast, humidity], cache_ttl: 5m)`
428    Enrich(EnrichSpec),
429}
430
431/// A path in a fork construct
432#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
433pub struct ForkPath {
434    /// Name of the path
435    pub name: String,
436    /// Sequence of operations for this path
437    pub ops: Vec<StreamOp>,
438}
439
440/// Item in a trend_aggregate operation
441#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
442pub struct TrendAggItem {
443    /// Output alias for the aggregation result
444    pub alias: String,
445    /// Aggregation function name: "count_trends", "count_events", "sum_trends", etc.
446    pub func: String,
447    /// Optional argument (field reference for sum/avg/min/max, alias for count_events)
448    pub arg: Option<Expr>,
449}
450
451/// Specification for ONNX model scoring
452#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
453pub struct ScoreSpec {
454    /// Path to the ONNX model file.
455    pub model_path: String,
456    /// Input tensor names to feed from event fields.
457    pub inputs: Vec<String>,
458    /// Output tensor names to extract as event fields.
459    pub outputs: Vec<String>,
460    /// Enable GPU acceleration (default: false)
461    #[serde(default)]
462    pub gpu: bool,
463    /// Batch size for inference (default: 1, >1 enables batch mode)
464    #[serde(default = "default_batch_size")]
465    pub batch_size: usize,
466    /// GPU device ID (default: 0)
467    #[serde(default)]
468    pub device_id: i32,
469}
470
471const fn default_batch_size() -> usize {
472    1
473}
474
475/// Specification for PST-based pattern forecasting
476#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
477pub struct ForecastSpec {
478    /// Minimum probability to emit forecast (default 0.5)
479    pub confidence: Option<Expr>,
480    /// Forecast time horizon (default = within duration)
481    pub horizon: Option<Expr>,
482    /// Events before forecasting starts (default 100)
483    pub warmup: Option<Expr>,
484    /// Maximum PST context depth (default 5)
485    pub max_depth: Option<Expr>,
486    /// Enable Hawkes intensity modulation (default true)
487    pub hawkes: Option<Expr>,
488    /// Enable conformal prediction intervals (default true)
489    pub conformal: Option<Expr>,
490    /// Preset mode: "fast", "accurate", or "balanced" (default "balanced")
491    pub mode: Option<Expr>,
492}
493
494/// Specification for external connector enrichment
495#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
496pub struct EnrichSpec {
497    /// Name of the connector to use for lookups
498    pub connector_name: String,
499    /// Expression to evaluate as the lookup key
500    pub key_expr: Box<Expr>,
501    /// Fields to extract from the enrichment response
502    pub fields: Vec<String>,
503    /// Cache TTL (optional, duration expression)
504    pub cache_ttl: Option<Expr>,
505    /// Timeout for the enrichment request (optional, duration expression)
506    pub timeout: Option<Expr>,
507    /// Fallback value when lookup fails (optional)
508    pub fallback: Option<Expr>,
509}
510
511/// Followed-by clause for temporal sequences
512#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
513pub struct FollowedByClause {
514    /// Event type to match
515    pub event_type: String,
516    /// Optional filter condition
517    pub filter: Option<Expr>,
518    /// Optional alias for captured event
519    pub alias: Option<String>,
520    /// Whether to match all events (true) or just one (false)
521    pub match_all: bool,
522}
523
524/// Select item in projection
525#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
526pub enum SelectItem {
527    /// Simple field reference
528    Field(String),
529    /// Aliased expression: `alias: expr`
530    Alias(String, Expr),
531}
532
533/// Aggregation item
534#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
535pub struct AggItem {
536    /// Output field name for the aggregation result.
537    pub alias: String,
538    /// Aggregation expression (e.g., `avg(temperature)`).
539    pub expr: Expr,
540}
541
542/// Order item
543#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
544pub struct OrderItem {
545    /// Expression to sort by.
546    pub expr: Expr,
547    /// Whether to sort in descending order.
548    pub descending: bool,
549}
550
551/// Window arguments
552#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
553pub struct WindowArgs {
554    /// Window duration (tumbling window size).
555    pub duration: Expr,
556    /// Optional slide interval for sliding windows.
557    pub sliding: Option<Expr>,
558    /// Optional eviction policy expression.
559    pub policy: Option<Expr>,
560    /// Session window gap duration (syntax: `.window(session: 5m)`)
561    pub session_gap: Option<Expr>,
562}
563
564/// Pattern definition
565#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
566pub struct PatternDef {
567    /// Pattern name.
568    pub name: String,
569    /// Matcher expression for the pattern.
570    pub matcher: Expr,
571}
572
573/// Named argument: `name: value`
574#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
575pub struct NamedArg {
576    /// Argument name.
577    pub name: String,
578    /// Argument value expression.
579    pub value: Expr,
580}
581
582/// Field in event declaration
583#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
584pub struct Field {
585    /// Field name.
586    pub name: String,
587    /// Field type.
588    pub ty: Type,
589    /// Whether the field is optional.
590    pub optional: bool,
591}
592
593/// Function parameter
594#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
595pub struct Param {
596    /// Parameter name.
597    pub name: String,
598    /// Parameter type.
599    pub ty: Type,
600}
601
602/// Expression
603#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
604pub enum Expr {
605    /// Null literal.
606    Null,
607    /// Boolean literal.
608    Bool(bool),
609    /// Integer literal.
610    Int(i64),
611    /// Floating-point literal.
612    Float(f64),
613    /// String literal.
614    Str(String),
615    /// Duration literal in nanoseconds.
616    Duration(u64),
617    /// Timestamp literal in nanoseconds since epoch.
618    Timestamp(i64),
619
620    /// Array literal: `[1, 2, 3]`.
621    Array(Vec<Self>),
622    /// Map literal: `{key: value, ...}`.
623    Map(Vec<(String, Self)>),
624
625    /// Identifier reference.
626    Ident(String),
627
628    /// Binary operation: `left op right`.
629    Binary {
630        /// The binary operator.
631        op: BinOp,
632        /// Left operand.
633        left: Box<Self>,
634        /// Right operand.
635        right: Box<Self>,
636    },
637
638    /// Unary operation: `op expr`.
639    Unary {
640        /// The unary operator.
641        op: UnaryOp,
642        /// Operand expression.
643        expr: Box<Self>,
644    },
645
646    /// Member access: `expr.member`.
647    Member {
648        /// Object expression.
649        expr: Box<Self>,
650        /// Member name to access.
651        member: String,
652    },
653
654    /// Optional member access: `expr?.member`.
655    OptionalMember {
656        /// Object expression (may be null).
657        expr: Box<Self>,
658        /// Member name to access.
659        member: String,
660    },
661
662    /// Index access: `expr[index]`.
663    Index {
664        /// Collection expression.
665        expr: Box<Self>,
666        /// Index expression.
667        index: Box<Self>,
668    },
669
670    /// Slice: `expr[start:end]`.
671    Slice {
672        /// Collection expression.
673        expr: Box<Self>,
674        /// Optional start index.
675        start: Option<Box<Self>>,
676        /// Optional end index.
677        end: Option<Box<Self>>,
678    },
679
680    /// Function call: `func(args)`.
681    Call {
682        /// Function expression to call.
683        func: Box<Self>,
684        /// Arguments to the function.
685        args: Vec<Arg>,
686    },
687
688    /// Lambda: `x => expr` or `(x, y) => expr`.
689    Lambda {
690        /// Lambda parameter names.
691        params: Vec<String>,
692        /// Lambda body expression.
693        body: Box<Self>,
694    },
695
696    /// Conditional expression: `if cond then a else b`.
697    If {
698        /// Condition expression.
699        cond: Box<Self>,
700        /// Value when true.
701        then_branch: Box<Self>,
702        /// Value when false.
703        else_branch: Box<Self>,
704    },
705
706    /// Null coalescing: `expr ?? default`.
707    Coalesce {
708        /// Expression that may be null.
709        expr: Box<Self>,
710        /// Fallback value when expr is null.
711        default: Box<Self>,
712    },
713
714    /// Range: `start..end` or `start..=end`.
715    Range {
716        /// Range start.
717        start: Box<Self>,
718        /// Range end.
719        end: Box<Self>,
720        /// Whether the end is inclusive (`..=`).
721        inclusive: bool,
722    },
723
724    /// Block expression: `{ let a = 1; let b = 2; a + b }`.
725    Block {
726        /// Local bindings: (name, optional type, value, is_mutable).
727        stmts: Vec<(String, Option<Type>, Self, bool)>,
728        /// Final expression that produces the block's value.
729        result: Box<Self>,
730    },
731}
732
733/// Function argument
734#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
735pub enum Arg {
736    /// Positional argument.
737    Positional(Expr),
738    /// Named argument: `name: value`.
739    Named(String, Expr),
740}
741
742/// Binary operator
743#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
744pub enum BinOp {
745    /// Addition (`+`).
746    Add,
747    /// Subtraction (`-`).
748    Sub,
749    /// Multiplication (`*`).
750    Mul,
751    /// Division (`/`).
752    Div,
753    /// Modulo (`%`).
754    Mod,
755    /// Exponentiation (`**`).
756    Pow,
757
758    /// Equality (`==`).
759    Eq,
760    /// Inequality (`!=`).
761    NotEq,
762    /// Less than (`<`).
763    Lt,
764    /// Less than or equal (`<=`).
765    Le,
766    /// Greater than (`>`).
767    Gt,
768    /// Greater than or equal (`>=`).
769    Ge,
770    /// Membership test (`in`).
771    In,
772    /// Negated membership test (`not in`).
773    NotIn,
774    /// Type identity test (`is`).
775    Is,
776
777    /// Logical AND (`and`).
778    And,
779    /// Logical OR (`or`).
780    Or,
781    /// Logical XOR (`xor`).
782    Xor,
783
784    /// Temporal followed-by operator (`->`).
785    FollowedBy,
786
787    /// Bitwise AND (`&`).
788    BitAnd,
789    /// Bitwise OR (`|`).
790    BitOr,
791    /// Bitwise XOR (`^`).
792    BitXor,
793    /// Left shift (`<<`).
794    Shl,
795    /// Right shift (`>>`).
796    Shr,
797}
798
799impl BinOp {
800    /// Returns the operator's source-level string representation.
801    pub const fn as_str(&self) -> &'static str {
802        match self {
803            Self::Add => "+",
804            Self::Sub => "-",
805            Self::Mul => "*",
806            Self::Div => "/",
807            Self::Mod => "%",
808            Self::Pow => "**",
809            Self::Eq => "==",
810            Self::NotEq => "!=",
811            Self::Lt => "<",
812            Self::Le => "<=",
813            Self::Gt => ">",
814            Self::Ge => ">=",
815            Self::In => "in",
816            Self::NotIn => "not in",
817            Self::Is => "is",
818            Self::And => "and",
819            Self::Or => "or",
820            Self::Xor => "xor",
821            Self::FollowedBy => "->",
822            Self::BitAnd => "&",
823            Self::BitOr => "|",
824            Self::BitXor => "^",
825            Self::Shl => "<<",
826            Self::Shr => ">>",
827        }
828    }
829}
830
831/// Unary operator
832#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
833pub enum UnaryOp {
834    /// Arithmetic negation (`-`).
835    Neg,
836    /// Logical negation (`not`).
837    Not,
838    /// Bitwise complement (`~`).
839    BitNot,
840}
841
842impl UnaryOp {
843    /// Returns the operator's source-level string representation.
844    pub const fn as_str(&self) -> &'static str {
845        match self {
846            Self::Neg => "-",
847            Self::Not => "not",
848            Self::BitNot => "~",
849        }
850    }
851}
852
853/// Configuration item
854#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
855pub enum ConfigItem {
856    /// Simple key-value pair.
857    Value(String, ConfigValue),
858    /// Nested configuration block.
859    Nested(String, Vec<Self>),
860}
861
862/// Configuration value
863#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
864pub enum ConfigValue {
865    /// Boolean value.
866    Bool(bool),
867    /// Integer value.
868    Int(i64),
869    /// Floating-point value.
870    Float(f64),
871    /// String value.
872    Str(String),
873    /// Duration value in nanoseconds.
874    Duration(u64),
875    /// Identifier reference.
876    Ident(String),
877    /// Array of configuration values.
878    Array(Vec<Self>),
879    /// Map of key-value pairs.
880    Map(Vec<(String, Self)>),
881}
882
883impl ConfigValue {
884    /// Get value as string (works for Str and Ident)
885    pub fn as_string(&self) -> Option<&str> {
886        match self {
887            Self::Str(s) => Some(s),
888            Self::Ident(s) => Some(s),
889            _ => None,
890        }
891    }
892
893    /// Get value as i64
894    pub const fn as_int(&self) -> Option<i64> {
895        match self {
896            Self::Int(i) => Some(*i),
897            Self::Float(f) => Some(*f as i64),
898            _ => None,
899        }
900    }
901
902    /// Get value as f64
903    pub const fn as_float(&self) -> Option<f64> {
904        match self {
905            Self::Float(f) => Some(*f),
906            Self::Int(i) => Some(*i as f64),
907            _ => None,
908        }
909    }
910
911    /// Get value as bool
912    pub const fn as_bool(&self) -> Option<bool> {
913        match self {
914            Self::Bool(b) => Some(*b),
915            _ => None,
916        }
917    }
918}