Skip to main content

icl_core/parser/
ast.rs

1//! ICL AST Types — Abstract Syntax Tree node definitions
2//!
3//! These types represent the parsed structure of an ICL contract.
4//! They map directly to the BNF grammar in CORE-SPECIFICATION.md.
5//!
6//! The AST is the raw parse tree with source positions (spans).
7//! A separate lowering step converts AST → semantic `Contract` in lib.rs.
8//!
9//! All AST types derive: Debug, Clone, PartialEq
10
11use super::tokenizer::Span;
12
13// ── Top-Level ──────────────────────────────────────────────
14
15/// Root AST node for an ICL contract definition
16#[derive(Debug, Clone, PartialEq)]
17pub struct ContractNode {
18    pub identity: IdentityNode,
19    pub purpose_statement: PurposeStatementNode,
20    pub data_semantics: DataSemanticsNode,
21    pub behavioral_semantics: BehavioralSemanticsNode,
22    pub execution_constraints: ExecutionConstraintsNode,
23    pub human_machine_contract: HumanMachineContractNode,
24    pub extensions: Option<ExtensionsNode>,
25    pub span: Span,
26}
27
28// ── Identity (§1.2) ───────────────────────────────────────
29
30#[derive(Debug, Clone, PartialEq)]
31pub struct IdentityNode {
32    pub stable_id: SpannedValue<String>,
33    pub version: SpannedValue<i64>,
34    pub created_timestamp: SpannedValue<String>,
35    pub owner: SpannedValue<String>,
36    pub semantic_hash: SpannedValue<String>,
37    pub span: Span,
38}
39
40// ── Purpose Statement (§1.3) ──────────────────────────────
41
42#[derive(Debug, Clone, PartialEq)]
43pub struct PurposeStatementNode {
44    pub narrative: SpannedValue<String>,
45    pub intent_source: SpannedValue<String>,
46    pub confidence_level: SpannedValue<f64>,
47    pub span: Span,
48}
49
50// ── Data Semantics (§1.4) ─────────────────────────────────
51
52#[derive(Debug, Clone, PartialEq)]
53pub struct DataSemanticsNode {
54    pub state: Vec<StateFieldNode>,
55    pub invariants: Vec<SpannedValue<String>>,
56    pub span: Span,
57}
58
59/// A field in a state definition or parameter list
60#[derive(Debug, Clone, PartialEq)]
61pub struct StateFieldNode {
62    pub name: SpannedValue<String>,
63    pub type_expr: TypeExpression,
64    pub default_value: Option<LiteralValue>,
65    pub span: Span,
66}
67
68// ── Type Expressions ──────────────────────────────────────
69
70/// Type expression matching BNF grammar
71#[derive(Debug, Clone, PartialEq)]
72pub enum TypeExpression {
73    /// Primitive: Integer, Float, String, Boolean, ISO8601, UUID
74    Primitive(PrimitiveType, Span),
75    /// Array<T>
76    Array(Box<TypeExpression>, Span),
77    /// Map<K, V>
78    Map(Box<TypeExpression>, Box<TypeExpression>, Span),
79    /// Object { fields... }
80    Object(Vec<StateFieldNode>, Span),
81    /// Enum ["a", "b", "c"]
82    Enum(Vec<SpannedValue<String>>, Span),
83}
84
85/// ICL primitive types
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum PrimitiveType {
88    Integer,
89    Float,
90    String,
91    Boolean,
92    Iso8601,
93    Uuid,
94}
95
96/// Literal values for defaults and inline data
97#[derive(Debug, Clone, PartialEq)]
98pub enum LiteralValue {
99    String(String, Span),
100    Integer(i64, Span),
101    Float(f64, Span),
102    Boolean(bool, Span),
103    Array(Vec<LiteralValue>, Span),
104}
105
106// ── Behavioral Semantics (§1.5) ───────────────────────────
107
108#[derive(Debug, Clone, PartialEq)]
109pub struct BehavioralSemanticsNode {
110    pub operations: Vec<OperationNode>,
111    pub span: Span,
112}
113
114#[derive(Debug, Clone, PartialEq)]
115pub struct OperationNode {
116    pub name: SpannedValue<String>,
117    pub precondition: SpannedValue<String>,
118    pub parameters: Vec<StateFieldNode>,
119    pub postcondition: SpannedValue<String>,
120    pub side_effects: Vec<SpannedValue<String>>,
121    pub idempotence: SpannedValue<String>,
122    pub span: Span,
123}
124
125// ── Execution Constraints (§1.6) ──────────────────────────
126
127#[derive(Debug, Clone, PartialEq)]
128pub struct ExecutionConstraintsNode {
129    pub trigger_types: Vec<SpannedValue<String>>,
130    pub resource_limits: ResourceLimitsNode,
131    pub external_permissions: Vec<SpannedValue<String>>,
132    pub sandbox_mode: SpannedValue<String>,
133    pub span: Span,
134}
135
136#[derive(Debug, Clone, PartialEq)]
137pub struct ResourceLimitsNode {
138    pub max_memory_bytes: SpannedValue<i64>,
139    pub computation_timeout_ms: SpannedValue<i64>,
140    pub max_state_size_bytes: SpannedValue<i64>,
141    pub span: Span,
142}
143
144// ── Human-Machine Contract (§1.7) ─────────────────────────
145
146#[derive(Debug, Clone, PartialEq)]
147pub struct HumanMachineContractNode {
148    pub system_commitments: Vec<SpannedValue<String>>,
149    pub system_refusals: Vec<SpannedValue<String>>,
150    pub user_obligations: Vec<SpannedValue<String>>,
151    pub span: Span,
152}
153
154// ── Extensions (§5) ───────────────────────────────────────
155
156#[derive(Debug, Clone, PartialEq)]
157pub struct ExtensionsNode {
158    pub systems: Vec<SystemExtensionNode>,
159    pub span: Span,
160}
161
162#[derive(Debug, Clone, PartialEq)]
163pub struct SystemExtensionNode {
164    pub name: SpannedValue<String>,
165    pub fields: Vec<CustomFieldNode>,
166    pub span: Span,
167}
168
169#[derive(Debug, Clone, PartialEq)]
170pub struct CustomFieldNode {
171    pub name: SpannedValue<String>,
172    pub value: LiteralValue,
173    pub span: Span,
174}
175
176// ── Spanned Value (generic wrapper) ───────────────────────
177
178/// A value annotated with its source span
179#[derive(Debug, Clone, PartialEq)]
180pub struct SpannedValue<T> {
181    pub value: T,
182    pub span: Span,
183}
184
185impl<T> SpannedValue<T> {
186    pub fn new(value: T, span: Span) -> Self {
187        SpannedValue { value, span }
188    }
189}
190
191// ── Display implementations ───────────────────────────────
192
193impl std::fmt::Display for PrimitiveType {
194    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
195        match self {
196            PrimitiveType::Integer => write!(f, "Integer"),
197            PrimitiveType::Float => write!(f, "Float"),
198            PrimitiveType::String => write!(f, "String"),
199            PrimitiveType::Boolean => write!(f, "Boolean"),
200            PrimitiveType::Iso8601 => write!(f, "ISO8601"),
201            PrimitiveType::Uuid => write!(f, "UUID"),
202        }
203    }
204}
205
206impl std::fmt::Display for TypeExpression {
207    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
208        match self {
209            TypeExpression::Primitive(p, _) => write!(f, "{}", p),
210            TypeExpression::Array(inner, _) => write!(f, "Array<{}>", inner),
211            TypeExpression::Map(k, v, _) => write!(f, "Map<{}, {}>", k, v),
212            TypeExpression::Object(fields, _) => {
213                write!(f, "Object {{ ")?;
214                for (i, field) in fields.iter().enumerate() {
215                    if i > 0 {
216                        write!(f, ", ")?;
217                    }
218                    write!(f, "{}: {}", field.name.value, field.type_expr)?;
219                }
220                write!(f, " }}")
221            }
222            TypeExpression::Enum(variants, _) => {
223                write!(f, "Enum [")?;
224                for (i, v) in variants.iter().enumerate() {
225                    if i > 0 {
226                        write!(f, ", ")?;
227                    }
228                    write!(f, "\"{}\"", v.value)?;
229                }
230                write!(f, "]")
231            }
232        }
233    }
234}
235
236impl std::fmt::Display for LiteralValue {
237    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
238        match self {
239            LiteralValue::String(s, _) => write!(f, "\"{}\"", s),
240            LiteralValue::Integer(n, _) => write!(f, "{}", n),
241            LiteralValue::Float(n, _) => write!(f, "{}", n),
242            LiteralValue::Boolean(b, _) => write!(f, "{}", b),
243            LiteralValue::Array(items, _) => {
244                write!(f, "[")?;
245                for (i, item) in items.iter().enumerate() {
246                    if i > 0 {
247                        write!(f, ", ")?;
248                    }
249                    write!(f, "{}", item)?;
250                }
251                write!(f, "]")
252            }
253        }
254    }
255}
256
257impl std::fmt::Display for ContractNode {
258    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
259        write!(
260            f,
261            "Contract(id={}, v={})",
262            self.identity.stable_id.value, self.identity.version.value
263        )
264    }
265}
266
267// ── Span accessors (convenience) ──────────────────────────
268
269impl TypeExpression {
270    pub fn span(&self) -> &Span {
271        match self {
272            TypeExpression::Primitive(_, s) => s,
273            TypeExpression::Array(_, s) => s,
274            TypeExpression::Map(_, _, s) => s,
275            TypeExpression::Object(_, s) => s,
276            TypeExpression::Enum(_, s) => s,
277        }
278    }
279}
280
281impl LiteralValue {
282    pub fn span(&self) -> &Span {
283        match self {
284            LiteralValue::String(_, s) => s,
285            LiteralValue::Integer(_, s) => s,
286            LiteralValue::Float(_, s) => s,
287            LiteralValue::Boolean(_, s) => s,
288            LiteralValue::Array(_, s) => s,
289        }
290    }
291}
292
293#[cfg(test)]
294mod tests {
295    use super::*;
296
297    #[test]
298    fn test_primitive_type_display() {
299        assert_eq!(PrimitiveType::Integer.to_string(), "Integer");
300        assert_eq!(PrimitiveType::Float.to_string(), "Float");
301        assert_eq!(PrimitiveType::String.to_string(), "String");
302        assert_eq!(PrimitiveType::Boolean.to_string(), "Boolean");
303        assert_eq!(PrimitiveType::Iso8601.to_string(), "ISO8601");
304        assert_eq!(PrimitiveType::Uuid.to_string(), "UUID");
305    }
306
307    #[test]
308    fn test_type_expression_display() {
309        let span = Span {
310            line: 1,
311            column: 1,
312            offset: 0,
313        };
314
315        let int_ty = TypeExpression::Primitive(PrimitiveType::Integer, span.clone());
316        assert_eq!(int_ty.to_string(), "Integer");
317
318        let arr_ty = TypeExpression::Array(
319            Box::new(TypeExpression::Primitive(
320                PrimitiveType::String,
321                span.clone(),
322            )),
323            span.clone(),
324        );
325        assert_eq!(arr_ty.to_string(), "Array<String>");
326
327        let map_ty = TypeExpression::Map(
328            Box::new(TypeExpression::Primitive(
329                PrimitiveType::String,
330                span.clone(),
331            )),
332            Box::new(TypeExpression::Primitive(
333                PrimitiveType::Integer,
334                span.clone(),
335            )),
336            span.clone(),
337        );
338        assert_eq!(map_ty.to_string(), "Map<String, Integer>");
339    }
340
341    #[test]
342    fn test_enum_display() {
343        let span = Span {
344            line: 1,
345            column: 1,
346            offset: 0,
347        };
348        let enum_ty = TypeExpression::Enum(
349            vec![
350                SpannedValue::new("active".to_string(), span.clone()),
351                SpannedValue::new("inactive".to_string(), span.clone()),
352            ],
353            span.clone(),
354        );
355        assert_eq!(enum_ty.to_string(), r#"Enum ["active", "inactive"]"#);
356    }
357
358    #[test]
359    fn test_literal_display() {
360        let span = Span {
361            line: 1,
362            column: 1,
363            offset: 0,
364        };
365        assert_eq!(
366            LiteralValue::String("hello".to_string(), span.clone()).to_string(),
367            "\"hello\""
368        );
369        assert_eq!(LiteralValue::Integer(42, span.clone()).to_string(), "42");
370        #[allow(clippy::approx_constant)]
371        let float_val = LiteralValue::Float(3.14, span.clone());
372        assert_eq!(float_val.to_string(), "3.14");
373        assert_eq!(
374            LiteralValue::Boolean(true, span.clone()).to_string(),
375            "true"
376        );
377    }
378
379    #[test]
380    fn test_spanned_value() {
381        let span = Span {
382            line: 5,
383            column: 10,
384            offset: 50,
385        };
386        let sv = SpannedValue::new("test".to_string(), span.clone());
387        assert_eq!(sv.value, "test");
388        assert_eq!(sv.span, span);
389    }
390
391    #[test]
392    fn test_type_expression_span() {
393        let span = Span {
394            line: 3,
395            column: 7,
396            offset: 30,
397        };
398        let ty = TypeExpression::Primitive(PrimitiveType::Boolean, span.clone());
399        assert_eq!(ty.span(), &span);
400    }
401
402    #[test]
403    fn test_object_display() {
404        let span = Span {
405            line: 1,
406            column: 1,
407            offset: 0,
408        };
409        let obj = TypeExpression::Object(
410            vec![
411                StateFieldNode {
412                    name: SpannedValue::new("x".to_string(), span.clone()),
413                    type_expr: TypeExpression::Primitive(PrimitiveType::Integer, span.clone()),
414                    default_value: None,
415                    span: span.clone(),
416                },
417                StateFieldNode {
418                    name: SpannedValue::new("y".to_string(), span.clone()),
419                    type_expr: TypeExpression::Primitive(PrimitiveType::Float, span.clone()),
420                    default_value: None,
421                    span: span.clone(),
422                },
423            ],
424            span.clone(),
425        );
426        assert_eq!(obj.to_string(), "Object { x: Integer, y: Float }");
427    }
428
429    #[test]
430    fn test_nested_type_display() {
431        let span = Span {
432            line: 1,
433            column: 1,
434            offset: 0,
435        };
436        // Array<Map<String, Integer>>
437        let inner = TypeExpression::Map(
438            Box::new(TypeExpression::Primitive(
439                PrimitiveType::String,
440                span.clone(),
441            )),
442            Box::new(TypeExpression::Primitive(
443                PrimitiveType::Integer,
444                span.clone(),
445            )),
446            span.clone(),
447        );
448        let outer = TypeExpression::Array(Box::new(inner), span.clone());
449        assert_eq!(outer.to_string(), "Array<Map<String, Integer>>");
450    }
451}