Skip to main content

pepl_types/
ast.rs

1//! AST node types for the PEPL language.
2//!
3//! Every node carries a [`Span`] for error reporting.
4//! Large recursive types are boxed to keep enum sizes reasonable.
5//! [`BTreeMap`] is NOT used here — AST preserves source order.
6
7use crate::Span;
8#[allow(unused_imports)]
9use serde::{Deserialize, Serialize};
10
11// ══════════════════════════════════════════════════════════════════════════════
12// Top Level
13// ══════════════════════════════════════════════════════════════════════════════
14
15/// A complete PEPL program: one space declaration + zero or more test blocks.
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
17pub struct Program {
18    pub space: SpaceDecl,
19    pub tests: Vec<TestsBlock>,
20    pub span: Span,
21}
22
23/// `space Name { body }`
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25pub struct SpaceDecl {
26    pub name: Ident,
27    pub body: SpaceBody,
28    pub span: Span,
29}
30
31/// The body of a space declaration — blocks in enforced order.
32///
33/// Block ordering: types → state → capabilities → credentials → derived →
34/// invariants → actions → views → update → handleEvent
35#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
36pub struct SpaceBody {
37    pub types: Vec<TypeDecl>,
38    pub state: StateBlock,
39    pub capabilities: Option<CapabilitiesBlock>,
40    pub credentials: Option<CredentialsBlock>,
41    pub derived: Option<DerivedBlock>,
42    pub invariants: Vec<InvariantDecl>,
43    pub actions: Vec<ActionDecl>,
44    pub views: Vec<ViewDecl>,
45    pub update: Option<UpdateDecl>,
46    pub handle_event: Option<HandleEventDecl>,
47    pub span: Span,
48}
49
50// ══════════════════════════════════════════════════════════════════════════════
51// Identifiers
52// ══════════════════════════════════════════════════════════════════════════════
53
54/// A spanned identifier.
55#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
56pub struct Ident {
57    pub name: String,
58    pub span: Span,
59}
60
61impl Ident {
62    pub fn new(name: impl Into<String>, span: Span) -> Self {
63        Self {
64            name: name.into(),
65            span,
66        }
67    }
68}
69
70// ══════════════════════════════════════════════════════════════════════════════
71// Type Declarations
72// ══════════════════════════════════════════════════════════════════════════════
73
74/// `type Name = ...`
75#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
76pub struct TypeDecl {
77    pub name: Ident,
78    pub body: TypeDeclBody,
79    pub span: Span,
80}
81
82/// The body of a type declaration — either a sum type or a type alias.
83#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
84pub enum TypeDeclBody {
85    /// `type Shape = | Circle(radius: number) | Rectangle(width: number, height: number)`
86    SumType(Vec<VariantDef>),
87    /// `type Meters = number`
88    Alias(TypeAnnotation),
89}
90
91/// A sum type variant: `Circle(radius: number)` or `Active` (unit variant).
92#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
93pub struct VariantDef {
94    pub name: Ident,
95    pub params: Vec<Param>,
96    pub span: Span,
97}
98
99// ══════════════════════════════════════════════════════════════════════════════
100// State & Related Blocks
101// ══════════════════════════════════════════════════════════════════════════════
102
103/// `state { field: type = default, ... }`
104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105pub struct StateBlock {
106    pub fields: Vec<StateField>,
107    pub span: Span,
108}
109
110/// A single state field: `count: number = 0`
111#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
112pub struct StateField {
113    pub name: Ident,
114    pub type_ann: TypeAnnotation,
115    pub default: Expr,
116    pub span: Span,
117}
118
119/// `capabilities { required: [...], optional: [...] }`
120#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
121pub struct CapabilitiesBlock {
122    pub required: Vec<Ident>,
123    pub optional: Vec<Ident>,
124    pub span: Span,
125}
126
127/// `credentials { api_key: string, ... }`
128#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
129pub struct CredentialsBlock {
130    pub fields: Vec<CredentialField>,
131    pub span: Span,
132}
133
134/// A credential field: `api_key: string`
135#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
136pub struct CredentialField {
137    pub name: Ident,
138    pub type_ann: TypeAnnotation,
139    pub span: Span,
140}
141
142/// `derived { total: number = list.length(items), ... }`
143#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
144pub struct DerivedBlock {
145    pub fields: Vec<DerivedField>,
146    pub span: Span,
147}
148
149/// A derived field: `total: number = list.length(items)`
150#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
151pub struct DerivedField {
152    pub name: Ident,
153    pub type_ann: TypeAnnotation,
154    pub value: Expr,
155    pub span: Span,
156}
157
158// ══════════════════════════════════════════════════════════════════════════════
159// Invariants
160// ══════════════════════════════════════════════════════════════════════════════
161
162/// `invariant name { expr }`
163#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
164pub struct InvariantDecl {
165    pub name: Ident,
166    pub condition: Expr,
167    pub span: Span,
168}
169
170// ══════════════════════════════════════════════════════════════════════════════
171// Actions
172// ══════════════════════════════════════════════════════════════════════════════
173
174/// `action name(params) { body }`
175#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
176pub struct ActionDecl {
177    pub name: Ident,
178    pub params: Vec<Param>,
179    pub body: Block,
180    pub span: Span,
181}
182
183/// A parameter: `name: type`
184#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
185pub struct Param {
186    pub name: Ident,
187    pub type_ann: TypeAnnotation,
188    pub span: Span,
189}
190
191/// `{ statements... }`
192#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
193pub struct Block {
194    pub stmts: Vec<Stmt>,
195    pub span: Span,
196}
197
198// ══════════════════════════════════════════════════════════════════════════════
199// Views
200// ══════════════════════════════════════════════════════════════════════════════
201
202/// `view name(params) -> Surface { ui_elements... }`
203#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
204pub struct ViewDecl {
205    pub name: Ident,
206    pub params: Vec<Param>,
207    pub body: UIBlock,
208    pub span: Span,
209}
210
211/// A UI block: `{ ui_elements... }`
212#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
213pub struct UIBlock {
214    pub elements: Vec<UIElement>,
215    pub span: Span,
216}
217
218/// An element inside a UI block.
219#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
220pub enum UIElement {
221    /// `Component { props } [{ children }]`
222    Component(ComponentExpr),
223    /// `let name = expr`
224    Let(LetBinding),
225    /// `if cond { ui... } [else { ui... }]`
226    If(UIIf),
227    /// `for item [, index] in expr { ui... }`
228    For(UIFor),
229}
230
231/// A component expression: `Text { value: "hello" }` or `Modal { visible: v } { children }`
232#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
233pub struct ComponentExpr {
234    pub name: Ident,
235    pub props: Vec<PropAssign>,
236    pub children: Option<UIBlock>,
237    pub span: Span,
238}
239
240/// A prop assignment: `label: "Click me"`
241#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
242pub struct PropAssign {
243    pub name: Ident,
244    pub value: Expr,
245    pub span: Span,
246}
247
248/// `if` in a UI context — bodies contain UIElements, not Statements.
249#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
250pub struct UIIf {
251    pub condition: Expr,
252    pub then_block: UIBlock,
253    pub else_block: Option<UIElse>,
254    pub span: Span,
255}
256
257/// The else branch of a UI if — either another if or a UI block.
258#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
259pub enum UIElse {
260    ElseIf(Box<UIIf>),
261    Block(UIBlock),
262}
263
264/// `for item [, index] in expr { ui... }` in a UI context.
265#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
266pub struct UIFor {
267    pub item: Ident,
268    pub index: Option<Ident>,
269    pub iterable: Expr,
270    pub body: UIBlock,
271    pub span: Span,
272}
273
274// ══════════════════════════════════════════════════════════════════════════════
275// Game Loop
276// ══════════════════════════════════════════════════════════════════════════════
277
278/// `update(dt: number) { body }`
279#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
280pub struct UpdateDecl {
281    pub param: Param,
282    pub body: Block,
283    pub span: Span,
284}
285
286/// `handleEvent(event: InputEvent) { body }`
287#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
288pub struct HandleEventDecl {
289    pub param: Param,
290    pub body: Block,
291    pub span: Span,
292}
293
294// ══════════════════════════════════════════════════════════════════════════════
295// Tests
296// ══════════════════════════════════════════════════════════════════════════════
297
298/// `tests { test_cases... }`
299#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
300pub struct TestsBlock {
301    pub cases: Vec<TestCase>,
302    pub span: Span,
303}
304
305/// `test "description" [with_responses { ... }] { body }`
306#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
307pub struct TestCase {
308    pub description: String,
309    pub with_responses: Option<WithResponses>,
310    pub body: Block,
311    pub span: Span,
312}
313
314/// `with_responses { module.function(args) -> value, ... }`
315#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
316pub struct WithResponses {
317    pub mappings: Vec<ResponseMapping>,
318    pub span: Span,
319}
320
321/// `module.function(args) -> value`
322#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
323pub struct ResponseMapping {
324    pub module: Ident,
325    pub function: Ident,
326    pub args: Vec<Expr>,
327    pub response: Expr,
328    pub span: Span,
329}
330
331// ══════════════════════════════════════════════════════════════════════════════
332// Statements
333// ══════════════════════════════════════════════════════════════════════════════
334
335/// A statement in a code block.
336#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
337pub enum Stmt {
338    /// `set field = expr` or `set record.field.nested = expr`
339    Set(SetStmt),
340    /// `let name: Type = expr` or `let _ = expr`
341    Let(LetBinding),
342    /// `if cond { ... } [else { ... }]`
343    If(IfExpr),
344    /// `for item [, index] in expr { ... }`
345    For(ForExpr),
346    /// `match expr { arms... }`
347    Match(MatchExpr),
348    /// `return`
349    Return(ReturnStmt),
350    /// `assert expr [, "message"]`
351    Assert(AssertStmt),
352    /// A bare expression (value is discarded unless last in block).
353    Expr(ExprStmt),
354}
355
356/// `set target = value`
357#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
358pub struct SetStmt {
359    /// Path segments: `["record", "field", "nested"]`
360    pub target: Vec<Ident>,
361    pub value: Expr,
362    pub span: Span,
363}
364
365/// `let name: Type = expr` or `let _ = expr`
366#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
367pub struct LetBinding {
368    /// `None` for `let _ = expr` (discard binding)
369    pub name: Option<Ident>,
370    pub type_ann: Option<TypeAnnotation>,
371    pub value: Expr,
372    pub span: Span,
373}
374
375/// `return`
376#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
377pub struct ReturnStmt {
378    pub span: Span,
379}
380
381/// `assert expr [, "message"]`
382#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
383pub struct AssertStmt {
384    pub condition: Expr,
385    pub message: Option<String>,
386    pub span: Span,
387}
388
389/// A bare expression statement.
390#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
391pub struct ExprStmt {
392    pub expr: Expr,
393    pub span: Span,
394}
395
396// ══════════════════════════════════════════════════════════════════════════════
397// Expressions
398// ══════════════════════════════════════════════════════════════════════════════
399
400/// An expression node. Uses `Box` for recursive variants.
401#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
402pub struct Expr {
403    pub kind: ExprKind,
404    pub span: Span,
405}
406
407impl Expr {
408    pub fn new(kind: ExprKind, span: Span) -> Self {
409        Self { kind, span }
410    }
411}
412
413/// The kind of expression.
414#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
415pub enum ExprKind {
416    // ── Literals ──
417    /// `42`, `3.14`
418    NumberLit(f64),
419    /// `"hello"` (no interpolation)
420    StringLit(String),
421    /// `"hello ${name}"` — parts alternate: string, expr, string, expr, string
422    StringInterpolation(Vec<StringPart>),
423    /// `true` / `false`
424    BoolLit(bool),
425    /// `nil`
426    NilLit,
427    /// `[expr, ...]`
428    ListLit(Vec<Expr>),
429    /// `{ field: expr, ...spread, ... }`
430    RecordLit(Vec<RecordEntry>),
431
432    // ── Identifiers & Calls ──
433    /// `my_var`, `count`
434    Identifier(String),
435    /// `func(args...)` — unqualified call
436    Call { name: Ident, args: Vec<Expr> },
437    /// `module.function(args...)` — qualified (stdlib) call
438    QualifiedCall {
439        module: Ident,
440        function: Ident,
441        args: Vec<Expr>,
442    },
443    /// `expr.field`
444    FieldAccess { object: Box<Expr>, field: Ident },
445    /// `expr.method(args...)`
446    MethodCall {
447        object: Box<Expr>,
448        method: Ident,
449        args: Vec<Expr>,
450    },
451
452    // ── Operators ──
453    /// `a + b`, `a == b`, `a and b`, etc.
454    Binary {
455        left: Box<Expr>,
456        op: BinOp,
457        right: Box<Expr>,
458    },
459    /// `-x`, `not x`
460    Unary { op: UnaryOp, operand: Box<Expr> },
461    /// `expr?` — Result unwrap
462    ResultUnwrap(Box<Expr>),
463    /// `a ?? b` — nil-coalescing
464    NilCoalesce { left: Box<Expr>, right: Box<Expr> },
465
466    // ── Control Flow ──
467    /// `if cond { ... } [else { ... }]`
468    If(Box<IfExpr>),
469    /// `for item [, index] in expr { ... }`
470    For(Box<ForExpr>),
471    /// `match expr { arms... }`
472    Match(Box<MatchExpr>),
473
474    // ── Lambda ──
475    /// `fn(params) { body }`
476    Lambda(Box<LambdaExpr>),
477
478    // ── Grouping ──
479    /// `(expr)`
480    Paren(Box<Expr>),
481}
482
483/// A part of an interpolated string.
484#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
485pub enum StringPart {
486    /// Literal text segment.
487    Literal(String),
488    /// An interpolated expression `${expr}`.
489    Expr(Expr),
490}
491
492/// An entry in a record literal.
493#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
494pub enum RecordEntry {
495    /// `field: expr`
496    Field { name: Ident, value: Expr },
497    /// `...expr`
498    Spread(Expr),
499}
500
501// ── Binary Operators ──────────────────────────────────────────────────────────
502
503/// Binary operators (in precedence order, lowest first).
504#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
505pub enum BinOp {
506    // Logical
507    Or,
508    And,
509    // Comparison
510    Eq,
511    NotEq,
512    Less,
513    Greater,
514    LessEq,
515    GreaterEq,
516    // Arithmetic
517    Add,
518    Sub,
519    Mul,
520    Div,
521    Mod,
522}
523
524impl BinOp {
525    /// Returns the operator symbol for error messages.
526    pub fn as_str(&self) -> &'static str {
527        match self {
528            BinOp::Or => "or",
529            BinOp::And => "and",
530            BinOp::Eq => "==",
531            BinOp::NotEq => "!=",
532            BinOp::Less => "<",
533            BinOp::Greater => ">",
534            BinOp::LessEq => "<=",
535            BinOp::GreaterEq => ">=",
536            BinOp::Add => "+",
537            BinOp::Sub => "-",
538            BinOp::Mul => "*",
539            BinOp::Div => "/",
540            BinOp::Mod => "%",
541        }
542    }
543}
544
545/// Unary operators.
546#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
547pub enum UnaryOp {
548    /// `-x`
549    Neg,
550    /// `not x`
551    Not,
552}
553
554// ── Control Flow Expressions ──────────────────────────────────────────────────
555
556/// `if cond { stmts... } [else { stmts... } | else if ...]`
557#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
558pub struct IfExpr {
559    pub condition: Expr,
560    pub then_block: Block,
561    pub else_branch: Option<ElseBranch>,
562    pub span: Span,
563}
564
565/// The else branch of an if expression.
566#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
567pub enum ElseBranch {
568    /// `else if cond { ... }`
569    ElseIf(Box<IfExpr>),
570    /// `else { ... }`
571    Block(Block),
572}
573
574/// `for item [, index] in iterable { stmts... }`
575#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
576pub struct ForExpr {
577    pub item: Ident,
578    pub index: Option<Ident>,
579    pub iterable: Expr,
580    pub body: Block,
581    pub span: Span,
582}
583
584/// `match expr { arms... }`
585#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
586pub struct MatchExpr {
587    pub subject: Expr,
588    pub arms: Vec<MatchArm>,
589    pub span: Span,
590}
591
592/// `Pattern -> expr | { stmts... }`
593#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
594pub struct MatchArm {
595    pub pattern: Pattern,
596    pub body: MatchArmBody,
597    pub span: Span,
598}
599
600/// The body of a match arm — either a single expression or a block.
601#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
602pub enum MatchArmBody {
603    Expr(Expr),
604    Block(Block),
605}
606
607/// A pattern in a match arm.
608#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
609pub enum Pattern {
610    /// `VariantName` or `VariantName(a, b, c)`
611    Variant { name: Ident, bindings: Vec<Ident> },
612    /// `_` wildcard
613    Wildcard(Span),
614}
615
616// ── Lambda ────────────────────────────────────────────────────────────────────
617
618/// `fn(params) { body }` — block-body only.
619#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
620pub struct LambdaExpr {
621    pub params: Vec<Param>,
622    pub body: Block,
623    pub span: Span,
624}
625
626// ══════════════════════════════════════════════════════════════════════════════
627// Type Annotations
628// ══════════════════════════════════════════════════════════════════════════════
629
630/// A type annotation in PEPL source code.
631#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
632pub struct TypeAnnotation {
633    pub kind: TypeKind,
634    pub span: Span,
635}
636
637impl TypeAnnotation {
638    pub fn new(kind: TypeKind, span: Span) -> Self {
639        Self { kind, span }
640    }
641}
642
643impl std::fmt::Display for TypeAnnotation {
644    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
645        write!(f, "{}", self.kind)
646    }
647}
648
649impl std::fmt::Display for TypeKind {
650    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
651        match self {
652            TypeKind::Number => write!(f, "number"),
653            TypeKind::String => write!(f, "string"),
654            TypeKind::Bool => write!(f, "bool"),
655            TypeKind::Nil => write!(f, "nil"),
656            TypeKind::Any => write!(f, "any"),
657            TypeKind::Color => write!(f, "color"),
658            TypeKind::Surface => write!(f, "Surface"),
659            TypeKind::InputEvent => write!(f, "InputEvent"),
660            TypeKind::List(inner) => write!(f, "list<{}>", inner),
661            TypeKind::Record(fields) => {
662                write!(f, "{{ ")?;
663                for (i, field) in fields.iter().enumerate() {
664                    if i > 0 {
665                        write!(f, ", ")?;
666                    }
667                    if field.optional {
668                        write!(f, "{}?: {}", field.name.name, field.type_ann)?;
669                    } else {
670                        write!(f, "{}: {}", field.name.name, field.type_ann)?;
671                    }
672                }
673                write!(f, " }}")
674            }
675            TypeKind::Result(ok, err) => write!(f, "Result<{}, {}>", ok, err),
676            TypeKind::Function { params, ret } => {
677                write!(f, "(")?;
678                for (i, p) in params.iter().enumerate() {
679                    if i > 0 {
680                        write!(f, ", ")?;
681                    }
682                    write!(f, "{}", p)?;
683                }
684                write!(f, ") -> {}", ret)
685            }
686            TypeKind::Named(name) => write!(f, "{}", name),
687        }
688    }
689}
690
691/// The kind of type.
692#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
693pub enum TypeKind {
694    /// `number`
695    Number,
696    /// `string`
697    String,
698    /// `bool`
699    Bool,
700    /// `nil`
701    Nil,
702    /// `any` — stdlib-only, rejected in user code by the type checker
703    Any,
704    /// `color`
705    Color,
706    /// `Surface`
707    Surface,
708    /// `InputEvent`
709    InputEvent,
710    /// `list<T>`
711    List(Box<TypeAnnotation>),
712    /// `{ name: string, age?: number }` — anonymous record type
713    Record(Vec<RecordTypeField>),
714    /// `Result<T, E>`
715    Result(Box<TypeAnnotation>, Box<TypeAnnotation>),
716    /// `(T1, T2) -> R` — function type
717    Function {
718        params: Vec<TypeAnnotation>,
719        ret: Box<TypeAnnotation>,
720    },
721    /// User-defined type name (sum type or alias): `Shape`, `Priority`
722    Named(String),
723}
724
725/// A field in an anonymous record type: `name?: Type`
726#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
727pub struct RecordTypeField {
728    pub name: Ident,
729    pub optional: bool,
730    pub type_ann: TypeAnnotation,
731    pub span: Span,
732}