Skip to main content

eure_document/
eure_macro.rs

1/// A declarative macro for building Eure documents, inspired by serde_json's `json!` macro.
2///
3/// # Syntax
4///
5/// The macro uses a TT muncher pattern to support arbitrary path combinations:
6/// - Idents: `a.b.c`
7/// - Extensions: `a.%ext` (use `%` instead of `$` since `$` is reserved in macros)
8/// - Tuple index: `a.#0`, `a.#1`
9/// - Array markers: `a[]` (push), `a[0]` (index)
10/// - Tuple keys: `a.(1, "key")` (composite map keys)
11/// - Mixed paths: `a.%ext[].b`, `a[].%ext.#0`, `a.(1, 2).name`
12///
13/// # Special Values
14///
15/// - `null`: Creates a null value
16/// - `!`: Creates an unbound hole (explicit placeholder)
17/// - `@code("content")`: Creates inline code with implicit language
18/// - `@code("lang", "content")`: Creates inline code with explicit language
19/// - `@block("content")`: Creates block code with implicit language
20/// - `@block("lang", "content")`: Creates block code with explicit language
21///
22/// # Examples
23///
24/// ```
25/// use eure_document::eure;
26///
27/// // Simple assignment (commas are optional)
28/// let doc = eure!({
29///     name = "Alice"
30///     age = 30
31/// });
32///
33/// // Null and hole values
34/// let doc = eure!({
35///     optional = null
36///     placeholder = !
37/// });
38///
39/// // Code values
40/// let doc = eure!({
41///     snippet = @code("let x = 1")
42///     sql = @code("sql", "SELECT * FROM users")
43///     script = @block("fn main() {}")
44///     rust_code = @block("rust", "fn main() {\n    println!(\"Hello\");\n}")
45/// });
46///
47/// // Nested paths
48/// let doc = eure!({
49///     user.name = "Bob"
50///     user.active = true
51/// });
52///
53/// // Blocks (for grouping)
54/// let doc = eure!({
55///     user {
56///         name = "Charlie"
57///         role = "admin"
58///     }
59/// });
60///
61/// // Extensions
62/// let doc = eure!({
63///     field.%variant = @code("text")
64/// });
65///
66/// // Tuple index
67/// let doc = eure!({
68///     point.#0 = 1.0f64
69///     point.#1 = 2.0f64
70/// });
71///
72/// // Array markers
73/// let doc = eure!({
74///     items[] = 1
75///     items[] = 2
76/// });
77///
78/// // Tuple keys (composite map keys)
79/// let doc = eure!({
80///     map.(1, "key") = "value"
81///     map.(true, 2) = "another"
82/// });
83///
84/// // Arrays (literal)
85/// let doc = eure!({
86///     tags = ["a", "b", "c"]
87/// });
88///
89/// // Tuples (literal)
90/// let doc = eure!({
91///     point = (1.0f64, 2.0f64)
92/// });
93///
94/// // Sections (like TOML)
95/// let doc = eure!({
96///     @user
97///     name = "Alice"
98///     age = 30
99///
100///     @settings
101///     theme = "dark"
102/// });
103/// ```
104#[macro_export]
105macro_rules! eure {
106    // ========================================================================
107    // Entry points
108    //
109    // The macro entry points handle the top-level document structure.
110    // ========================================================================
111
112    // Empty document: `eure!({})` creates an empty map document
113    ({}) => {{
114        $crate::document::EureDocument::new_empty()
115    }};
116
117    // Document with body: `eure!({ ... })` creates a document and processes the body
118    ({ $($body:tt)* }) => {{
119        #[allow(unused_mut)]
120        let mut c = $crate::document::constructor::DocumentConstructor::new();
121        $crate::eure!(@stmt c; $($body)*);
122        c.finish()
123    }};
124
125    // Generic entry point with explicit constructor: `eure!($constructor; { ... })`
126    // Works with any InterpreterSink implementation (DocumentConstructor, SourceConstructor, etc.)
127    ($c:ident; { $($body:tt)* }) => {{
128        $crate::eure!(@stmt $c; $($body)*);
129    }};
130
131    // ========================================================================
132    // Value conversion helper (@value_tt)
133    //
134    // Converts a single token tree to a primitive value. This allows comma-free
135    // syntax by matching exactly one tt at a time.
136    //
137    // Note: Arrays, tuples, and object literals are NOT handled here.
138    // They require explicit patterns in @terminal/@stmt because they need
139    // the DocumentConstructor's navigation system.
140    // ========================================================================
141
142    // null literal
143    (@value_tt null) => { $crate::value::PrimitiveValue::Null };
144
145    // Boolean identifiers
146    (@value_tt true) => { true };
147    (@value_tt false) => { false };
148
149    // General literal fallback (string, int, float)
150    (@value_tt $v:literal) => { $v };
151
152    // General expression fallback (variables, expressions)
153    (@value_tt $v:expr) => { $v };
154
155    // ========================================================================
156    // Array items helper (@array_items)
157    //
158    // Processes array items using the DocumentConstructor. Each item is added
159    // by navigating to ArrayIndex($crate::path::ArrayIndexKind::Push) which appends to the array.
160    // ========================================================================
161
162    // Array items: empty (terminal)
163    (@array_items $c:ident;) => {};
164
165    // Array items: skip comma
166    (@array_items $c:ident; , $($rest:tt)*) => {{
167        $crate::eure!(@array_items $c; $($rest)*);
168    }};
169
170    // Array items: @code literal
171    (@array_items $c:ident; @ code ($content:literal) $($rest:tt)*) => {{
172        let scope = $c.begin_scope();
173        $c.navigate($crate::path::PathSegment::ArrayIndex($crate::path::ArrayIndexKind::Push)).unwrap();
174        $c.bind_from($crate::text::Text::inline_implicit($content)).unwrap();
175        $c.end_scope(scope).unwrap();
176        $crate::eure!(@array_items $c; $($rest)*);
177    }};
178
179    // Array items: @code with language
180    (@array_items $c:ident; @ code ($lang:literal, $content:literal) $($rest:tt)*) => {{
181        let scope = $c.begin_scope();
182        $c.navigate($crate::path::PathSegment::ArrayIndex($crate::path::ArrayIndexKind::Push)).unwrap();
183        $c.bind_from($crate::text::Text::inline($content, $lang)).unwrap();
184        $c.end_scope(scope).unwrap();
185        $crate::eure!(@array_items $c; $($rest)*);
186    }};
187
188    // Array items: nested array
189    (@array_items $c:ident; [$($inner:tt)*] $($rest:tt)*) => {{
190        let scope = $c.begin_scope();
191        $c.navigate($crate::path::PathSegment::ArrayIndex($crate::path::ArrayIndexKind::Push)).unwrap();
192        $c.bind_empty_array().unwrap();
193        $crate::eure!(@array_items $c; $($inner)*);
194        $c.end_scope(scope).unwrap();
195        $crate::eure!(@array_items $c; $($rest)*);
196    }};
197
198    // Array items: nested tuple
199    (@array_items $c:ident; ($($inner:tt)*) $($rest:tt)*) => {{
200        let scope = $c.begin_scope();
201        $c.navigate($crate::path::PathSegment::ArrayIndex($crate::path::ArrayIndexKind::Push)).unwrap();
202        $c.bind_empty_tuple().unwrap();
203        $crate::eure!(@tuple_items $c 0; $($inner)*);
204        $c.end_scope(scope).unwrap();
205        $crate::eure!(@array_items $c; $($rest)*);
206    }};
207
208    // Array items: single item (primitive)
209    (@array_items $c:ident; $item:tt $($rest:tt)*) => {{
210        let scope = $c.begin_scope();
211        $c.navigate($crate::path::PathSegment::ArrayIndex($crate::path::ArrayIndexKind::Push)).unwrap();
212        $c.bind_from($crate::eure!(@value_tt $item)).unwrap();
213        $c.end_scope(scope).unwrap();
214        $crate::eure!(@array_items $c; $($rest)*);
215    }};
216
217    // ========================================================================
218    // Tuple items helper (@tuple_items)
219    //
220    // Processes tuple items using the DocumentConstructor. Each item is added
221    // by navigating to TupleIndex(idx).
222    // ========================================================================
223
224    // Tuple items: empty (terminal)
225    (@tuple_items $c:ident $idx:expr;) => {};
226
227    // Tuple items: skip comma
228    (@tuple_items $c:ident $idx:expr; , $($rest:tt)*) => {{
229        $crate::eure!(@tuple_items $c $idx; $($rest)*);
230    }};
231
232    // Tuple items: @code literal
233    (@tuple_items $c:ident $idx:expr; @ code ($content:literal) $($rest:tt)*) => {{
234        let scope = $c.begin_scope();
235        $c.navigate($crate::path::PathSegment::TupleIndex($idx)).unwrap();
236        $c.bind_from($crate::text::Text::inline_implicit($content)).unwrap();
237        $c.end_scope(scope).unwrap();
238        $crate::eure!(@tuple_items $c ($idx + 1); $($rest)*);
239    }};
240
241    // Tuple items: @code with language
242    (@tuple_items $c:ident $idx:expr; @ code ($lang:literal, $content:literal) $($rest:tt)*) => {{
243        let scope = $c.begin_scope();
244        $c.navigate($crate::path::PathSegment::TupleIndex($idx)).unwrap();
245        $c.bind_from($crate::text::Text::inline($content, $lang)).unwrap();
246        $c.end_scope(scope).unwrap();
247        $crate::eure!(@tuple_items $c ($idx + 1); $($rest)*);
248    }};
249
250    // Tuple items: nested array
251    (@tuple_items $c:ident $idx:expr; [$($inner:tt)*] $($rest:tt)*) => {{
252        let scope = $c.begin_scope();
253        $c.navigate($crate::path::PathSegment::TupleIndex($idx)).unwrap();
254        $c.bind_empty_array().unwrap();
255        $crate::eure!(@array_items $c; $($inner)*);
256        $c.end_scope(scope).unwrap();
257        $crate::eure!(@tuple_items $c ($idx + 1); $($rest)*);
258    }};
259
260    // Tuple items: nested tuple
261    (@tuple_items $c:ident $idx:expr; ($($inner:tt)*) $($rest:tt)*) => {{
262        let scope = $c.begin_scope();
263        $c.navigate($crate::path::PathSegment::TupleIndex($idx)).unwrap();
264        $c.bind_empty_tuple().unwrap();
265        $crate::eure!(@tuple_items $c 0; $($inner)*);
266        $c.end_scope(scope).unwrap();
267        $crate::eure!(@tuple_items $c ($idx + 1); $($rest)*);
268    }};
269
270    // Tuple items: single item (primitive)
271    (@tuple_items $c:ident $idx:expr; $item:tt $($rest:tt)*) => {{
272        let scope = $c.begin_scope();
273        $c.navigate($crate::path::PathSegment::TupleIndex($idx)).unwrap();
274        $c.bind_from($crate::eure!(@value_tt $item)).unwrap();
275        $c.end_scope(scope).unwrap();
276        $crate::eure!(@tuple_items $c ($idx + 1); $($rest)*);
277    }};
278
279    // ========================================================================
280    // Object key helper (@object_key)
281    //
282    // Converts a key token to a value suitable for ObjectKey::from().
283    // Identifiers are stringified, literals are used as-is.
284    // ========================================================================
285
286    // Object key: identifier -> stringify to string
287    (@object_key $key:ident) => { stringify!($key) };
288
289    // Object key: literal or other token -> use as-is
290    (@object_key $key:tt) => { $key };
291
292    // ========================================================================
293    // Object items helper (@object_items)
294    //
295    // Processes object literal items (k => v syntax) using the DocumentConstructor.
296    // ========================================================================
297
298    // Object items: empty (terminal)
299    (@object_items $c:ident;) => {};
300
301    // Object items: skip comma
302    (@object_items $c:ident; , $($rest:tt)*) => {{
303        $crate::eure!(@object_items $c; $($rest)*);
304    }};
305
306    // Object items: k => @code(content)
307    (@object_items $c:ident; $key:tt => @ code ($content:literal) $($rest:tt)*) => {{
308        let scope = $c.begin_scope();
309        $c.navigate($crate::path::PathSegment::Value($crate::eure!(@object_key $key).into())).unwrap();
310        $c.bind_from($crate::text::Text::inline_implicit($content)).unwrap();
311        $c.end_scope(scope).unwrap();
312        $crate::eure!(@object_items $c; $($rest)*);
313    }};
314
315    // Object items: k => @code(lang, content)
316    (@object_items $c:ident; $key:tt => @ code ($lang:literal, $content:literal) $($rest:tt)*) => {{
317        let scope = $c.begin_scope();
318        $c.navigate($crate::path::PathSegment::Value($crate::eure!(@object_key $key).into())).unwrap();
319        $c.bind_from($crate::text::Text::inline($content, $lang)).unwrap();
320        $c.end_scope(scope).unwrap();
321        $crate::eure!(@object_items $c; $($rest)*);
322    }};
323
324    // Object items: k => [array]
325    (@object_items $c:ident; $key:tt => [$($inner:tt)*] $($rest:tt)*) => {{
326        let scope = $c.begin_scope();
327        $c.navigate($crate::path::PathSegment::Value($crate::eure!(@object_key $key).into())).unwrap();
328        $c.bind_empty_array().unwrap();
329        $crate::eure!(@array_items $c; $($inner)*);
330        $c.end_scope(scope).unwrap();
331        $crate::eure!(@object_items $c; $($rest)*);
332    }};
333
334    // Object items: k => (tuple)
335    (@object_items $c:ident; $key:tt => ($($inner:tt)*) $($rest:tt)*) => {{
336        let scope = $c.begin_scope();
337        $c.navigate($crate::path::PathSegment::Value($crate::eure!(@object_key $key).into())).unwrap();
338        $c.bind_empty_tuple().unwrap();
339        $crate::eure!(@tuple_items $c 0; $($inner)*);
340        $c.end_scope(scope).unwrap();
341        $crate::eure!(@object_items $c; $($rest)*);
342    }};
343
344    // Object items: k => primitive
345    (@object_items $c:ident; $key:tt => $val:tt $($rest:tt)*) => {{
346        let scope = $c.begin_scope();
347        $c.navigate($crate::path::PathSegment::Value($crate::eure!(@object_key $key).into())).unwrap();
348        $c.bind_from($crate::eure!(@value_tt $val)).unwrap();
349        $c.end_scope(scope).unwrap();
350        $crate::eure!(@object_items $c; $($rest)*);
351    }};
352
353    // ========================================================================
354    // Statement handlers (@stmt)
355    //
356    // Process statements within a document or block. Each statement is either:
357    // - A root value binding: `= value`
358    // - A root special value: `= null`, `= !`, `= @code(...)`
359    // - A path-based statement: `path = value` or `path { block }`
360    // - A section: `@path` followed by bindings
361    //
362    // The statement handler delegates path parsing to @path.
363    // ========================================================================
364
365    // Empty body - nothing to process
366    (@stmt $c:ident;) => {};
367
368    // Skip optional comma at statement start (commas are optional separators)
369    (@stmt $c:ident; , $($rest:tt)*) => {{
370        $crate::eure!(@stmt $c; $($rest)*);
371    }};
372
373    // Root binding: hole (!) - explicit unbound placeholder
374    // Note: This must come before general `= $v:tt` to match `!` specifically
375    (@stmt $c:ident; = ! $($rest:tt)*) => {{
376        $c.bind_hole(None).unwrap();
377        $crate::eure!(@stmt $c; $($rest)*);
378    }};
379
380    // Root binding: negative literal (e.g., = -42)
381    // Negative numbers are two tokens: `-` and the number, so they need special handling
382    (@stmt $c:ident; = - $v:literal $($rest:tt)*) => {{
383        $c.bind_from(-$v).unwrap();
384        $crate::eure!(@stmt $c; $($rest)*);
385    }};
386
387    // Root binding: inline code with implicit language - @code("content")
388    (@stmt $c:ident; = @ code ($content:literal) $($rest:tt)*) => {{
389        $c.bind_from($crate::text::Text::inline_implicit($content)).unwrap();
390        $crate::eure!(@stmt $c; $($rest)*);
391    }};
392
393    // Root binding: inline code with explicit language - @code("lang", "content")
394    (@stmt $c:ident; = @ code ($lang:literal, $content:literal) $($rest:tt)*) => {{
395        $c.bind_from($crate::text::Text::inline($content, $lang)).unwrap();
396        $crate::eure!(@stmt $c; $($rest)*);
397    }};
398
399    // Root binding: block code with implicit language - @block("content")
400    (@stmt $c:ident; = @ block ($content:literal) $($rest:tt)*) => {{
401        $c.bind_from($crate::text::Text::block_implicit($content)).unwrap();
402        $crate::eure!(@stmt $c; $($rest)*);
403    }};
404
405    // Root binding: block code with explicit language - @block("lang", "content")
406    (@stmt $c:ident; = @ block ($lang:literal, $content:literal) $($rest:tt)*) => {{
407        $c.bind_from($crate::text::Text::block($content, $lang)).unwrap();
408        $crate::eure!(@stmt $c; $($rest)*);
409    }};
410
411    // Root binding: empty array
412    (@stmt $c:ident; = [] $($rest:tt)*) => {{
413        $c.bind_empty_array().unwrap();
414        $crate::eure!(@stmt $c; $($rest)*);
415    }};
416
417    // Root binding: array with items
418    (@stmt $c:ident; = [$($items:tt)+] $($rest:tt)*) => {{
419        $c.bind_empty_array().unwrap();
420        $crate::eure!(@array_items $c; $($items)+);
421        $crate::eure!(@stmt $c; $($rest)*);
422    }};
423
424    // Root binding: empty tuple
425    (@stmt $c:ident; = () $($rest:tt)*) => {{
426        $c.bind_empty_tuple().unwrap();
427        $crate::eure!(@stmt $c; $($rest)*);
428    }};
429
430    // Root binding: tuple with items
431    (@stmt $c:ident; = ($($items:tt)+) $($rest:tt)*) => {{
432        $c.bind_empty_tuple().unwrap();
433        $crate::eure!(@tuple_items $c 0; $($items)+);
434        $crate::eure!(@stmt $c; $($rest)*);
435    }};
436
437    // Root binding: object literal with map syntax { k => v, ... }
438    (@stmt $c:ident; = { $key:tt => $($inner:tt)+ } $($rest:tt)*) => {{
439        $c.bind_empty_map().unwrap();
440        $crate::eure!(@object_items $c; $key => $($inner)+);
441        $crate::eure!(@stmt $c; $($rest)*);
442    }};
443
444    // Root binding: general value (single token tree for primitives)
445    (@stmt $c:ident; = $v:tt $($rest:tt)*) => {{
446        $c.bind_from($crate::eure!(@value_tt $v)).unwrap();
447        $crate::eure!(@stmt $c; $($rest)*);
448    }};
449
450    // Section: @[] / @[N] followed by bindings
451    // In section context, raw []/[N] is only allowed immediately after `@`.
452    (@stmt $c:ident; @ [] $($rest:tt)*) => {{
453        $c.begin_section();
454        let scope = $c.begin_scope();
455        $c.navigate($crate::path::PathSegment::ArrayIndex($crate::path::ArrayIndexKind::Push)).unwrap();
456        $crate::eure!(@section_after_seg $c scope; $($rest)*);
457    }};
458    (@stmt $c:ident; @ [$idx:literal] $($rest:tt)*) => {{
459        $c.begin_section();
460        let scope = $c.begin_scope();
461        $c.navigate($crate::path::PathSegment::ArrayIndex($crate::path::ArrayIndexKind::Specific($idx))).unwrap();
462        $crate::eure!(@section_after_seg $c scope; $($rest)*);
463    }};
464
465    // Section: @path followed by bindings
466    (@stmt $c:ident; @ $seg:ident $($rest:tt)*) => {{
467        $c.begin_section();
468        let scope = $c.begin_scope();
469        $c.navigate($crate::path::PathSegment::Ident(
470            $crate::identifier::Identifier::new_unchecked(stringify!($seg))
471        )).unwrap();
472        $crate::eure!(@section_after_seg $c scope; $($rest)*);
473    }};
474
475    // Start parsing a path-based statement - delegate to path parser
476    // Creates a scope that will be closed when the statement ends
477    (@stmt $c:ident; $($tokens:tt)+) => {{
478        $c.begin_binding();
479        let scope = $c.begin_scope();
480        $crate::eure!(@path $c scope; $($tokens)+);
481    }};
482
483    // ========================================================================
484    // Section handlers (@section_*)
485    //
486    // Parse section syntax: @path followed by bindings until next section or end.
487    // ========================================================================
488
489    // After parsing a segment, check for more path or bindings
490    (@section_after_seg $c:ident $scope:ident; . $seg:ident $($rest:tt)*) => {{
491        $c.navigate($crate::path::PathSegment::Ident(
492            $crate::identifier::Identifier::new_unchecked(stringify!($seg))
493        )).unwrap();
494        $crate::eure!(@section_after_seg $c $scope; $($rest)*);
495    }};
496    (@section_after_seg $c:ident $scope:ident; # [] $($rest:tt)*) => {{
497        $c.navigate($crate::path::PathSegment::ArrayIndex($crate::path::ArrayIndexKind::Push)).unwrap();
498        $crate::eure!(@section_after_seg $c $scope; $($rest)*);
499    }};
500    (@section_after_seg $c:ident $scope:ident; # [$idx:literal] $($rest:tt)*) => {{
501        $c.navigate($crate::path::PathSegment::ArrayIndex($crate::path::ArrayIndexKind::Specific($idx))).unwrap();
502        $crate::eure!(@section_after_seg $c $scope; $($rest)*);
503    }};
504    (@section_after_seg $c:ident $scope:ident; [$($arr:tt)*] $($rest:tt)*) => {{
505        compile_error!(
506            "in section headers, raw []/[N] is only allowed immediately after `@`; use #[] or #[N] for continuation"
507        );
508    }};
509
510    // Section with value binding: @path = value (pattern #4 with value)
511    (@section_after_seg $c:ident $scope:ident; = $v:tt $($rest:tt)*) => {{
512        $c.bind_from($crate::eure!(@value_tt $v)).unwrap();
513        $c.begin_section_items();
514        // Continue parsing bindings within this section
515        $crate::eure!(@section_bindings $c $scope; $($rest)*);
516    }};
517
518    // Section with empty block: @path {} (pattern #5)
519    (@section_after_seg $c:ident $scope:ident; {} $($rest:tt)*) => {{
520        $c.begin_eure_block();
521        $c.bind_empty_map().unwrap();
522        $c.end_eure_block().unwrap();
523        $c.end_scope($scope).unwrap();
524        $c.end_section_block().unwrap();
525        $crate::eure!(@stmt $c; $($rest)*);
526    }};
527
528    // Section with non-empty block: @path { ... } (pattern #5/#6)
529    (@section_after_seg $c:ident $scope:ident; { $($inner:tt)+ } $($rest:tt)*) => {{
530        $c.begin_eure_block();
531        $crate::eure!(@stmt $c; $($inner)+);
532        $c.end_eure_block().unwrap();
533        $c.end_scope($scope).unwrap();
534        $c.end_section_block().unwrap();
535        $crate::eure!(@stmt $c; $($rest)*);
536    }};
537
538    // Section body starts (no value binding, no block) (pattern #4)
539    (@section_after_seg $c:ident $scope:ident; $($rest:tt)*) => {{
540        $c.begin_section_items();
541        $crate::eure!(@section_bindings $c $scope; $($rest)*);
542    }};
543
544    // Section bindings: empty - close scope
545    (@section_bindings $c:ident $scope:ident;) => {{
546        $c.end_section_items().unwrap();
547        $c.end_scope($scope).unwrap();
548    }};
549
550    // Section bindings: skip optional comma
551    (@section_bindings $c:ident $scope:ident; , $($rest:tt)*) => {{
552        $crate::eure!(@section_bindings $c $scope; $($rest)*);
553    }};
554
555    // Section bindings: new section starts - close current and start new
556    (@section_bindings $c:ident $scope:ident; @ [] $($rest:tt)*) => {{
557        $c.end_section_items().unwrap();
558        $c.end_scope($scope).unwrap();
559        $crate::eure!(@stmt $c; @ [] $($rest)*);
560    }};
561    (@section_bindings $c:ident $scope:ident; @ [$idx:literal] $($rest:tt)*) => {{
562        $c.end_section_items().unwrap();
563        $c.end_scope($scope).unwrap();
564        $crate::eure!(@stmt $c; @ [$idx] $($rest)*);
565    }};
566    (@section_bindings $c:ident $scope:ident; @ $seg:ident $($rest:tt)*) => {{
567        $c.end_section_items().unwrap();
568        $c.end_scope($scope).unwrap();
569        $crate::eure!(@stmt $c; @ $seg $($rest)*);
570    }};
571
572    // Section bindings: regular binding (path-based)
573    (@section_bindings $c:ident $scope:ident; $($tokens:tt)+) => {{
574        $c.begin_binding();
575        let inner_scope = $c.begin_scope();
576        $crate::eure!(@section_path $c $scope inner_scope; $($tokens)+);
577    }};
578
579    // Section path parsing - similar to @path but returns to @section_bindings
580    (@section_path $c:ident $section_scope:ident $scope:ident; $seg:ident $($rest:tt)*) => {{
581        $c.navigate($crate::path::PathSegment::Ident(
582            $crate::identifier::Identifier::new_unchecked(stringify!($seg))
583        )).unwrap();
584        $crate::eure!(@section_after_path $c $section_scope $scope; $($rest)*);
585    }};
586
587    // Section path: extension segment
588    (@section_path $c:ident $section_scope:ident $scope:ident; % $ext:ident $($rest:tt)*) => {{
589        $c.navigate($crate::path::PathSegment::Extension(
590            $crate::identifier::Identifier::new_unchecked(stringify!($ext))
591        )).unwrap();
592        $crate::eure!(@section_after_path $c $section_scope $scope; $($rest)*);
593    }};
594
595    // Section path: extension with string literal
596    (@section_path $c:ident $section_scope:ident $scope:ident; % $ext:literal $($rest:tt)*) => {{
597        $c.navigate($crate::path::PathSegment::Extension(
598            $ext.parse().unwrap()
599        )).unwrap();
600        $crate::eure!(@section_after_path $c $section_scope $scope; $($rest)*);
601    }};
602
603    // Section path: explicit array marker
604    (@section_path $c:ident $section_scope:ident $scope:ident; # [] $($rest:tt)*) => {{
605        $c.navigate($crate::path::PathSegment::ArrayIndex($crate::path::ArrayIndexKind::Push)).unwrap();
606        $crate::eure!(@section_terminal $c $section_scope $scope; $($rest)*);
607    }};
608    (@section_path $c:ident $section_scope:ident $scope:ident; # [$idx:literal] $($rest:tt)*) => {{
609        $c.navigate($crate::path::PathSegment::ArrayIndex($crate::path::ArrayIndexKind::Specific($idx))).unwrap();
610        $crate::eure!(@section_terminal $c $section_scope $scope; $($rest)*);
611    }};
612
613    // Section path: tuple index
614    (@section_path $c:ident $section_scope:ident $scope:ident; # $idx:literal $($rest:tt)*) => {{
615        $c.navigate($crate::path::PathSegment::TupleIndex($idx)).unwrap();
616        $crate::eure!(@section_after_path $c $section_scope $scope; $($rest)*);
617    }};
618
619    // Section path: tuple key
620    (@section_path $c:ident $section_scope:ident $scope:ident; ($($tuple:tt)*) $($rest:tt)*) => {{
621        let key = $crate::eure!(@build_tuple_key; $($tuple)*);
622        $c.navigate($crate::path::PathSegment::Value(key)).unwrap();
623        $crate::eure!(@section_after_path $c $section_scope $scope; $($rest)*);
624    }};
625
626    // Section path: string literal key
627    (@section_path $c:ident $section_scope:ident $scope:ident; $key:literal $($rest:tt)*) => {{
628        $c.navigate($crate::path::PathSegment::Value($key.into())).unwrap();
629        $crate::eure!(@section_after_path $c $section_scope $scope; $($rest)*);
630    }};
631    (@section_path $c:ident $section_scope:ident $scope:ident; [$($arr:tt)*] $($rest:tt)*) => {{
632        compile_error!(
633            "in section bindings, raw []/[N] is not allowed; use #[] or #[N] for array segments"
634        );
635    }};
636
637    // After section path segment: raw []/[N] is disallowed in section bindings
638    (@section_after_path $c:ident $section_scope:ident $scope:ident; [$($arr:tt)*] $($rest:tt)*) => {{
639        compile_error!(
640            "in section bindings, raw []/[N] is not allowed; use #[] or #[N] for array segments"
641        );
642    }};
643
644    // After section path segment: continue to terminal
645    (@section_after_path $c:ident $section_scope:ident $scope:ident; $($rest:tt)*) => {{
646        $crate::eure!(@section_terminal $c $section_scope $scope; $($rest)*);
647    }};
648
649    // Section terminal: more path
650    (@section_terminal $c:ident $section_scope:ident $scope:ident; . $($rest:tt)+) => {{
651        $crate::eure!(@section_path $c $section_scope $scope; $($rest)+);
652    }};
653    (@section_terminal $c:ident $section_scope:ident $scope:ident; # [] $($rest:tt)*) => {{
654        $c.navigate($crate::path::PathSegment::ArrayIndex($crate::path::ArrayIndexKind::Push)).unwrap();
655        $crate::eure!(@section_terminal $c $section_scope $scope; $($rest)*);
656    }};
657    (@section_terminal $c:ident $section_scope:ident $scope:ident; # [$idx:literal] $($rest:tt)*) => {{
658        $c.navigate($crate::path::PathSegment::ArrayIndex($crate::path::ArrayIndexKind::Specific($idx))).unwrap();
659        $crate::eure!(@section_terminal $c $section_scope $scope; $($rest)*);
660    }};
661    (@section_terminal $c:ident $section_scope:ident $scope:ident; [$($arr:tt)*] $($rest:tt)*) => {{
662        compile_error!(
663            "in section bindings, raw []/[N] is not allowed; use #[] or #[N] for array segments"
664        );
665    }};
666
667    // Section terminal: hole
668    (@section_terminal $c:ident $section_scope:ident $scope:ident; = ! $($rest:tt)*) => {{
669        $c.bind_hole(None).unwrap();
670        $c.end_scope($scope).unwrap();
671        $c.end_binding_value().unwrap();
672        $crate::eure!(@section_bindings $c $section_scope; $($rest)*);
673    }};
674
675    // Section terminal: inline code with implicit language
676    (@section_terminal $c:ident $section_scope:ident $scope:ident; = @ code ($content:literal) $($rest:tt)*) => {{
677        $c.bind_from($crate::text::Text::inline_implicit($content)).unwrap();
678        $c.end_scope($scope).unwrap();
679        $c.end_binding_value().unwrap();
680        $crate::eure!(@section_bindings $c $section_scope; $($rest)*);
681    }};
682
683    // Section terminal: inline code with explicit language
684    (@section_terminal $c:ident $section_scope:ident $scope:ident; = @ code ($lang:literal, $content:literal) $($rest:tt)*) => {{
685        $c.bind_from($crate::text::Text::inline($content, $lang)).unwrap();
686        $c.end_scope($scope).unwrap();
687        $c.end_binding_value().unwrap();
688        $crate::eure!(@section_bindings $c $section_scope; $($rest)*);
689    }};
690
691    // Section terminal: block code with implicit language
692    (@section_terminal $c:ident $section_scope:ident $scope:ident; = @ block ($content:literal) $($rest:tt)*) => {{
693        $c.bind_from($crate::text::Text::block_implicit($content)).unwrap();
694        $c.end_scope($scope).unwrap();
695        $c.end_binding_value().unwrap();
696        $crate::eure!(@section_bindings $c $section_scope; $($rest)*);
697    }};
698
699    // Section terminal: block code with explicit language
700    (@section_terminal $c:ident $section_scope:ident $scope:ident; = @ block ($lang:literal, $content:literal) $($rest:tt)*) => {{
701        $c.bind_from($crate::text::Text::block($content, $lang)).unwrap();
702        $c.end_scope($scope).unwrap();
703        $c.end_binding_value().unwrap();
704        $crate::eure!(@section_bindings $c $section_scope; $($rest)*);
705    }};
706
707    // Section terminal: value assignment
708    (@section_terminal $c:ident $section_scope:ident $scope:ident; = $v:tt $($rest:tt)*) => {{
709        $c.bind_from($crate::eure!(@value_tt $v)).unwrap();
710        $c.end_scope($scope).unwrap();
711        $c.end_binding_value().unwrap();
712        $crate::eure!(@section_bindings $c $section_scope; $($rest)*);
713    }};
714
715    // Section terminal: empty block
716    (@section_terminal $c:ident $section_scope:ident $scope:ident; {} $($rest:tt)*) => {{
717        $c.begin_eure_block();
718        $c.bind_empty_map().unwrap();
719        $c.end_eure_block().unwrap();
720        $c.end_scope($scope).unwrap();
721        $c.end_binding_block().unwrap();
722        $crate::eure!(@section_bindings $c $section_scope; $($rest)*);
723    }};
724
725    // Section terminal: non-empty block
726    (@section_terminal $c:ident $section_scope:ident $scope:ident; { $($inner:tt)+ } $($rest:tt)*) => {{
727        $c.begin_eure_block();
728        $crate::eure!(@stmt $c; $($inner)+);
729        $c.end_eure_block().unwrap();
730        $c.end_scope($scope).unwrap();
731        $c.end_binding_block().unwrap();
732        $crate::eure!(@section_bindings $c $section_scope; $($rest)*);
733    }};
734
735    // ========================================================================
736    // Path segment parsing (@path)
737    //
738    // Parse one path segment at a time using TT muncher pattern.
739    // Each segment type navigates to a child node and then delegates to @after_path.
740    //
741    // Supported segment types:
742    // - `ident`: Regular identifier (a, user, field_name)
743    // - `%ext`: Extension namespace ($variant becomes %variant in macro)
744    // - `#N`: Tuple index (#0, #1, #2)
745    // - `(a, b)`: Tuple key for composite map keys
746    // - `"key"`: String literal for non-identifier keys (e.g., "min-length")
747    // ========================================================================
748
749    // Segment: identifier (e.g., `field`, `user`, `name`)
750    (@path $c:ident $scope:ident; $seg:ident $($rest:tt)*) => {{
751        $c.navigate($crate::path::PathSegment::Ident(
752            $crate::identifier::Identifier::new_unchecked(stringify!($seg))
753        )).unwrap();
754        $crate::eure!(@after_path $c $scope; $($rest)*);
755    }};
756
757    // Segment: extension with identifier (e.g., `%variant`, `%schema`)
758    // Note: Uses % instead of $ because $ is reserved in macros
759    (@path $c:ident $scope:ident; % $ext:ident $($rest:tt)*) => {{
760        $c.navigate($crate::path::PathSegment::Extension(
761            $crate::identifier::Identifier::new_unchecked(stringify!($ext))
762        )).unwrap();
763        $crate::eure!(@after_path $c $scope; $($rest)*);
764    }};
765
766    // Segment: extension with string literal (e.g., `%"variant-repr"`)
767    // Used for hyphenated extension names that aren't valid Rust identifiers
768    (@path $c:ident $scope:ident; % $ext:literal $($rest:tt)*) => {{
769        $c.navigate($crate::path::PathSegment::Extension(
770            $ext.parse().unwrap()
771        )).unwrap();
772        $crate::eure!(@after_path $c $scope; $($rest)*);
773    }};
774
775    // Segment: explicit array marker (e.g., `#[]`, `#[0]`)
776    (@path $c:ident $scope:ident; # [] $($rest:tt)*) => {{
777        $c.navigate($crate::path::PathSegment::ArrayIndex($crate::path::ArrayIndexKind::Push)).unwrap();
778        $crate::eure!(@terminal $c $scope; $($rest)*);
779    }};
780    (@path $c:ident $scope:ident; # [$idx:literal] $($rest:tt)*) => {{
781        $c.navigate($crate::path::PathSegment::ArrayIndex($crate::path::ArrayIndexKind::Specific($idx))).unwrap();
782        $crate::eure!(@terminal $c $scope; $($rest)*);
783    }};
784
785    // Segment: tuple index (e.g., `#0`, `#1`, `#255`)
786    (@path $c:ident $scope:ident; # $idx:literal $($rest:tt)*) => {{
787        $c.navigate($crate::path::PathSegment::TupleIndex($idx)).unwrap();
788        $crate::eure!(@after_path $c $scope; $($rest)*);
789    }};
790
791    // Segment: tuple key (e.g., `(1, "key")`, `(true, 2)`)
792    // Used as composite map keys
793    (@path $c:ident $scope:ident; ($($tuple:tt)*) $($rest:tt)*) => {{
794        let key = $crate::eure!(@build_tuple_key; $($tuple)*);
795        $c.navigate($crate::path::PathSegment::Value(key)).unwrap();
796        $crate::eure!(@after_path $c $scope; $($rest)*);
797    }};
798
799    // Segment: string literal key (e.g., `"min-length"`, `"Content-Type"`)
800    // Used for keys that aren't valid identifiers
801    (@path $c:ident $scope:ident; $key:literal $($rest:tt)*) => {{
802        $c.navigate($crate::path::PathSegment::Value($key.into())).unwrap();
803        $crate::eure!(@after_path $c $scope; $($rest)*);
804    }};
805
806    // ========================================================================
807    // Build tuple key (@build_tuple_key)
808    //
809    // Constructs an ObjectKey::Tuple from comma-separated values.
810    // Used for composite map keys like (1, "key").
811    // ========================================================================
812
813    // Empty tuple key: ()
814    (@build_tuple_key;) => {{
815        $crate::value::ObjectKey::Tuple($crate::value::Tuple(Default::default()))
816    }};
817
818    // Non-empty tuple key: (a, b, c) - each item converted via Into<ObjectKey>
819    (@build_tuple_key; $($item:expr),+ $(,)?) => {{
820        $crate::value::ObjectKey::Tuple($crate::value::Tuple::from_iter(
821            [$(<_ as Into<$crate::value::ObjectKey>>::into($item)),+]
822        ))
823    }};
824
825    // ========================================================================
826    // After path segment (@after_path)
827    //
828    // After parsing a segment, check if there's an optional array marker [].
829    // If found, handle it; otherwise proceed to terminal handling.
830    // ========================================================================
831
832    // Has array marker - delegate to @array_marker
833    (@after_path $c:ident $scope:ident; [$($arr:tt)*] $($rest:tt)*) => {{
834        $crate::eure!(@array_marker $c $scope [$($arr)*]; $($rest)*);
835    }};
836
837    // No array marker - proceed to terminal handling
838    (@after_path $c:ident $scope:ident; $($rest:tt)*) => {{
839        $crate::eure!(@terminal $c $scope; $($rest)*);
840    }};
841
842    // ========================================================================
843    // Array marker handling (@array_marker)
844    //
845    // Process the content of array markers:
846    // - `[]`: Push to array (creates new element)
847    // - `[N]`: Access array at index N
848    // ========================================================================
849
850    // Empty array marker: push operation (creates new element at end)
851    (@array_marker $c:ident $scope:ident []; $($rest:tt)*) => {{
852        $c.navigate($crate::path::PathSegment::ArrayIndex($crate::path::ArrayIndexKind::Push)).unwrap();
853        $crate::eure!(@terminal $c $scope; $($rest)*);
854    }};
855
856    // Array marker with index: access at specific position
857    (@array_marker $c:ident $scope:ident [$idx:literal]; $($rest:tt)*) => {{
858        $c.navigate($crate::path::PathSegment::ArrayIndex($crate::path::ArrayIndexKind::Specific($idx))).unwrap();
859        $crate::eure!(@terminal $c $scope; $($rest)*);
860    }};
861
862    // ========================================================================
863    // Terminal handling (@terminal)
864    //
865    // Handle what comes after the path:
866    // - `.more.path`: Continue parsing more segments
867    // - `= value`: Bind a value (single token tree)
868    // - `= !`: Leave as hole (explicit placeholder)
869    // - `= @code(...)`: Bind inline code
870    // - `= @block(...)`: Bind block code
871    // - `{ ... }`: Block syntax (grouped bindings)
872    // - `{}`: Empty block (creates empty map)
873    //
874    // Uses $v:tt for values to enable comma-free syntax.
875    // Note: @code and @block must be handled specially as they span multiple tokens.
876    // ========================================================================
877
878    // Continuation: more path segments after dot
879    (@terminal $c:ident $scope:ident; . $($rest:tt)+) => {{
880        $crate::eure!(@path $c $scope; $($rest)+);
881    }};
882    (@terminal $c:ident $scope:ident; # [] $($rest:tt)*) => {{
883        $c.navigate($crate::path::PathSegment::ArrayIndex($crate::path::ArrayIndexKind::Push)).unwrap();
884        $crate::eure!(@terminal $c $scope; $($rest)*);
885    }};
886    (@terminal $c:ident $scope:ident; # [$idx:literal] $($rest:tt)*) => {{
887        $c.navigate($crate::path::PathSegment::ArrayIndex($crate::path::ArrayIndexKind::Specific($idx))).unwrap();
888        $crate::eure!(@terminal $c $scope; $($rest)*);
889    }};
890
891    // Terminal: hole (!) - explicit unbound placeholder
892    (@terminal $c:ident $scope:ident; = ! $($rest:tt)*) => {{
893        $c.bind_hole(None).unwrap();
894        $c.end_scope($scope).unwrap();
895        $c.end_binding_value().unwrap();
896        $crate::eure!(@stmt $c; $($rest)*);
897    }};
898
899    // Terminal: negative literal (e.g., path = -42)
900    (@terminal $c:ident $scope:ident; = - $v:literal $($rest:tt)*) => {{
901        $c.bind_from(-$v).unwrap();
902        $c.end_scope($scope).unwrap();
903        $c.end_binding_value().unwrap();
904        $crate::eure!(@stmt $c; $($rest)*);
905    }};
906
907    // Terminal: inline code with implicit language - @code("content")
908    (@terminal $c:ident $scope:ident; = @ code ($content:literal) $($rest:tt)*) => {{
909        $c.bind_from($crate::text::Text::inline_implicit($content)).unwrap();
910        $c.end_scope($scope).unwrap();
911        $c.end_binding_value().unwrap();
912        $crate::eure!(@stmt $c; $($rest)*);
913    }};
914
915    // Terminal: inline code with explicit language - @code("lang", "content")
916    (@terminal $c:ident $scope:ident; = @ code ($lang:literal, $content:literal) $($rest:tt)*) => {{
917        $c.bind_from($crate::text::Text::inline($content, $lang)).unwrap();
918        $c.end_scope($scope).unwrap();
919        $c.end_binding_value().unwrap();
920        $crate::eure!(@stmt $c; $($rest)*);
921    }};
922
923    // Terminal: block code with implicit language - @block("content")
924    (@terminal $c:ident $scope:ident; = @ block ($content:literal) $($rest:tt)*) => {{
925        $c.bind_from($crate::text::Text::block_implicit($content)).unwrap();
926        $c.end_scope($scope).unwrap();
927        $c.end_binding_value().unwrap();
928        $crate::eure!(@stmt $c; $($rest)*);
929    }};
930
931    // Terminal: block code with explicit language - @block("lang", "content")
932    (@terminal $c:ident $scope:ident; = @ block ($lang:literal, $content:literal) $($rest:tt)*) => {{
933        $c.bind_from($crate::text::Text::block($content, $lang)).unwrap();
934        $c.end_scope($scope).unwrap();
935        $c.end_binding_value().unwrap();
936        $crate::eure!(@stmt $c; $($rest)*);
937    }};
938
939    // Terminal: empty array
940    (@terminal $c:ident $scope:ident; = [] $($rest:tt)*) => {{
941        $c.bind_empty_array().unwrap();
942        $c.end_scope($scope).unwrap();
943        $c.end_binding_value().unwrap();
944        $crate::eure!(@stmt $c; $($rest)*);
945    }};
946
947    // Terminal: array with items
948    (@terminal $c:ident $scope:ident; = [$($items:tt)+] $($rest:tt)*) => {{
949        $c.bind_empty_array().unwrap();
950        $crate::eure!(@array_items $c; $($items)+);
951        $c.end_scope($scope).unwrap();
952        $c.end_binding_value().unwrap();
953        $crate::eure!(@stmt $c; $($rest)*);
954    }};
955
956    // Terminal: empty tuple
957    (@terminal $c:ident $scope:ident; = () $($rest:tt)*) => {{
958        $c.bind_empty_tuple().unwrap();
959        $c.end_scope($scope).unwrap();
960        $c.end_binding_value().unwrap();
961        $crate::eure!(@stmt $c; $($rest)*);
962    }};
963
964    // Terminal: tuple with items
965    (@terminal $c:ident $scope:ident; = ($($items:tt)+) $($rest:tt)*) => {{
966        $c.bind_empty_tuple().unwrap();
967        $crate::eure!(@tuple_items $c 0; $($items)+);
968        $c.end_scope($scope).unwrap();
969        $c.end_binding_value().unwrap();
970        $crate::eure!(@stmt $c; $($rest)*);
971    }};
972
973    // Terminal: object literal with map syntax { k => v, ... }
974    (@terminal $c:ident $scope:ident; = { $key:tt => $($inner:tt)+ } $($rest:tt)*) => {{
975        $c.bind_empty_map().unwrap();
976        $crate::eure!(@object_items $c; $key => $($inner)+);
977        $c.end_scope($scope).unwrap();
978        $c.end_binding_value().unwrap();
979        $crate::eure!(@stmt $c; $($rest)*);
980    }};
981
982    // Terminal: value assignment (single token tree for primitives)
983    (@terminal $c:ident $scope:ident; = $v:tt $($rest:tt)*) => {{
984        $c.bind_from($crate::eure!(@value_tt $v)).unwrap();
985        $c.end_scope($scope).unwrap();
986        $c.end_binding_value().unwrap();
987        $crate::eure!(@stmt $c; $($rest)*);
988    }};
989
990    // Terminal: empty block -> empty map
991    (@terminal $c:ident $scope:ident; {} $($rest:tt)*) => {{
992        $c.begin_eure_block();
993        $c.bind_empty_map().unwrap();
994        $c.end_eure_block().unwrap();
995        $c.end_scope($scope).unwrap();
996        $c.end_binding_block().unwrap();
997        $crate::eure!(@stmt $c; $($rest)*);
998    }};
999
1000    // Terminal: non-empty block
1001    (@terminal $c:ident $scope:ident; { $($inner:tt)+ } $($rest:tt)*) => {{
1002        $c.begin_eure_block();
1003        $crate::eure!(@stmt $c; $($inner)+);
1004        $c.end_eure_block().unwrap();
1005        $c.end_scope($scope).unwrap();
1006        $c.end_binding_block().unwrap();
1007        $crate::eure!(@stmt $c; $($rest)*);
1008    }};
1009}
1010
1011/// A macro for building [`SourceDocument`]s with source layout tracking.
1012///
1013/// This macro is similar to [`eure!`] but creates a [`SourceDocument`] which
1014/// preserves source structure information for round-trip formatting.
1015///
1016/// # Example
1017///
1018/// ```
1019/// use eure_document::eure_source;
1020///
1021/// let source_doc = eure_source!({
1022///     name = "Alice"
1023///     age = 30
1024/// });
1025///
1026/// // The SourceDocument contains both the document and source structure
1027/// let doc = source_doc.document();
1028/// let root_source = source_doc.root_source();
1029/// ```
1030#[macro_export]
1031macro_rules! eure_source {
1032    // Empty source document
1033    ({}) => {{
1034        $crate::source::SourceDocument::empty()
1035    }};
1036
1037    // Source document with body
1038    ({ $($body:tt)* }) => {{
1039        #[allow(unused_mut)]
1040        let mut c = $crate::document::source_constructor::SourceConstructor::new();
1041        $crate::eure!(c; { $($body)* });
1042        c.finish()
1043    }};
1044}
1045
1046#[cfg(test)]
1047mod tests {
1048    use crate::document::EureDocument;
1049    use alloc::vec;
1050
1051    #[test]
1052    fn test_eure_empty() {
1053        let doc = eure!({});
1054        assert_eq!(doc, EureDocument::new_empty());
1055    }
1056
1057    #[test]
1058    fn test_eure_simple_assignment() {
1059        let doc = eure!({ name = "Alice" });
1060
1061        // Verify the structure
1062        let root_id = doc.get_root_id();
1063        let root = doc.node(root_id);
1064        let name_node_id = root.as_map().unwrap().get_node_id(&"name".into()).unwrap();
1065        let name_node = doc.node(name_node_id);
1066        let prim = name_node.as_primitive().unwrap();
1067        assert_eq!(prim.as_str(), Some("Alice"));
1068    }
1069
1070    #[test]
1071    fn test_eure_nested_path() {
1072        let doc = eure!({
1073            user.name = "Bob"
1074            user.age = 30
1075        });
1076
1077        // Verify structure: root.user.name = "Bob", root.user.age = 30
1078        let root_id = doc.get_root_id();
1079        let root = doc.node(root_id);
1080        let user_id = root.as_map().unwrap().get_node_id(&"user".into()).unwrap();
1081        let user = doc.node(user_id);
1082        let name_id = user.as_map().unwrap().get_node_id(&"name".into()).unwrap();
1083        let name = doc.node(name_id);
1084        assert_eq!(name.as_primitive().unwrap().as_str(), Some("Bob"));
1085
1086        let age_id = user.as_map().unwrap().get_node_id(&"age".into()).unwrap();
1087        let age = doc.node(age_id);
1088        assert!(matches!(
1089            age.as_primitive(),
1090            Some(crate::value::PrimitiveValue::Integer(_))
1091        ));
1092    }
1093
1094    #[test]
1095    fn test_eure_block() {
1096        let doc = eure!({
1097            user {
1098                name = "Charlie"
1099                active = true
1100            }
1101        });
1102
1103        let root_id = doc.get_root_id();
1104        let root = doc.node(root_id);
1105        let user_id = root.as_map().unwrap().get_node_id(&"user".into()).unwrap();
1106        let user = doc.node(user_id);
1107        let name_id = user.as_map().unwrap().get_node_id(&"name".into()).unwrap();
1108        let name = doc.node(name_id);
1109        assert_eq!(name.as_primitive().unwrap().as_str(), Some("Charlie"));
1110    }
1111
1112    #[test]
1113    fn test_eure_extension() {
1114        let doc = eure!({
1115            field.%variant = @code("text")
1116        });
1117
1118        let root_id = doc.get_root_id();
1119        let root = doc.node(root_id);
1120        let field_id = root.as_map().unwrap().get_node_id(&"field".into()).unwrap();
1121        let field = doc.node(field_id);
1122
1123        // Check extension
1124        let variant_id = field.get_extension(&"variant".parse().unwrap()).unwrap();
1125        let variant = doc.node(variant_id);
1126        let text = variant.as_primitive().unwrap().as_text().unwrap();
1127        assert_eq!(text.as_str(), "text");
1128    }
1129
1130    #[test]
1131    fn test_eure_extension_with_child() {
1132        // Test pattern: a.%ext.b = value
1133        let doc = eure!({
1134            field.%variant.name = @code("text")
1135            field.%variant.min_length = 3
1136        });
1137
1138        let root_id = doc.get_root_id();
1139        let root = doc.node(root_id);
1140        let field_id = root.as_map().unwrap().get_node_id(&"field".into()).unwrap();
1141        let field = doc.node(field_id);
1142
1143        // Check extension
1144        let variant_id = field.get_extension(&"variant".parse().unwrap()).unwrap();
1145        let variant = doc.node(variant_id);
1146
1147        // Check child of extension
1148        let name_id = variant
1149            .as_map()
1150            .unwrap()
1151            .get_node_id(&"name".into())
1152            .unwrap();
1153        let name = doc.node(name_id);
1154        let text = name.as_primitive().unwrap().as_text().unwrap();
1155        assert_eq!(text.as_str(), "text");
1156
1157        let min_length_id = variant
1158            .as_map()
1159            .unwrap()
1160            .get_node_id(&"min_length".into())
1161            .unwrap();
1162        let min_length = doc.node(min_length_id);
1163        assert!(matches!(
1164            min_length.as_primitive(),
1165            Some(crate::value::PrimitiveValue::Integer(_))
1166        ));
1167    }
1168
1169    #[test]
1170    fn test_eure_array() {
1171        let doc = eure!({ tags = ["a", "b", "c"] });
1172
1173        let root_id = doc.get_root_id();
1174        let root = doc.node(root_id);
1175        let tags_id = root.as_map().unwrap().get_node_id(&"tags".into()).unwrap();
1176        let tags = doc.node(tags_id);
1177        let array = tags.as_array().unwrap();
1178        assert_eq!(array.len(), 3);
1179    }
1180
1181    #[test]
1182    fn test_eure_tuple() {
1183        let doc = eure!({ point = (1.5, 2.5) });
1184
1185        let root_id = doc.get_root_id();
1186        let root = doc.node(root_id);
1187        let point_id = root.as_map().unwrap().get_node_id(&"point".into()).unwrap();
1188        let point = doc.node(point_id);
1189        let tuple = point.as_tuple().unwrap();
1190        assert_eq!(tuple.len(), 2);
1191    }
1192
1193    #[test]
1194    fn test_eure_multiple_assignments() {
1195        let doc = eure!({
1196            a = 1
1197            b = 2
1198            c = 3
1199        });
1200
1201        let root_id = doc.get_root_id();
1202        let root = doc.node(root_id);
1203        let map = root.as_map().unwrap();
1204        assert_eq!(map.len(), 3);
1205    }
1206
1207    #[test]
1208    fn test_eure_complex() {
1209        // A more complex example combining features
1210        let doc = eure!({
1211            schema {
1212                field.%variant = @code("text")
1213                field.min_length = 3
1214                field.max_length = 20
1215            }
1216            tags = ["required"]
1217        });
1218
1219        let root_id = doc.get_root_id();
1220        let root = doc.node(root_id);
1221
1222        // Check schema exists
1223        let schema_id = root
1224            .as_map()
1225            .unwrap()
1226            .get_node_id(&"schema".into())
1227            .unwrap();
1228        let schema = doc.node(schema_id);
1229
1230        // Check field exists with extension
1231        let field_id = schema
1232            .as_map()
1233            .unwrap()
1234            .get_node_id(&"field".into())
1235            .unwrap();
1236        let field = doc.node(field_id);
1237        assert!(field.get_extension(&"variant".parse().unwrap()).is_some());
1238
1239        // Check tags array
1240        let tags_id = root.as_map().unwrap().get_node_id(&"tags".into()).unwrap();
1241        let tags = doc.node(tags_id);
1242        assert_eq!(tags.as_array().unwrap().len(), 1);
1243    }
1244
1245    #[test]
1246    fn test_eure_array_push() {
1247        // Test array push syntax: items[] = value
1248        let doc = eure!({
1249            items[] = 1
1250            items[] = 2
1251            items[] = 3
1252        });
1253
1254        let root_id = doc.get_root_id();
1255        let root = doc.node(root_id);
1256        let items_id = root.as_map().unwrap().get_node_id(&"items".into()).unwrap();
1257        let items = doc.node(items_id);
1258        let array = items.as_array().unwrap();
1259        assert_eq!(array.len(), 3);
1260    }
1261
1262    #[test]
1263    fn test_eure_array_push_with_child() {
1264        // Test: items[].name = value (array push then navigate to child)
1265        let doc = eure!({
1266            items[].name = "first"
1267            items[].name = "second"
1268        });
1269
1270        let root_id = doc.get_root_id();
1271        let root = doc.node(root_id);
1272        let items_id = root.as_map().unwrap().get_node_id(&"items".into()).unwrap();
1273        let items = doc.node(items_id);
1274        let array = items.as_array().unwrap();
1275        assert_eq!(array.len(), 2);
1276
1277        // Check first element has name = "first"
1278        let first_id = array.get(0).unwrap();
1279        let first = doc.node(first_id);
1280        let name_id = first.as_map().unwrap().get_node_id(&"name".into()).unwrap();
1281        let name = doc.node(name_id);
1282        assert_eq!(name.as_primitive().unwrap().as_str(), Some("first"));
1283    }
1284
1285    #[test]
1286    fn test_eure_root_extension_with_explicit_array_entries() {
1287        let doc = eure!({
1288            %meta { speaker = "Alice" }
1289            #[] = "Hello"
1290            #[] = "World"
1291        });
1292
1293        let root_id = doc.get_root_id();
1294        let root = doc.node(root_id);
1295
1296        let meta_id = root.get_extension(&"meta".parse().unwrap()).unwrap();
1297        let meta = doc.node(meta_id);
1298        let speaker_id = meta
1299            .as_map()
1300            .unwrap()
1301            .get_node_id(&"speaker".into())
1302            .unwrap();
1303        let speaker = doc.node(speaker_id);
1304        assert_eq!(speaker.as_primitive().unwrap().as_str(), Some("Alice"));
1305
1306        let array = root.as_array().unwrap();
1307        assert_eq!(array.len(), 2);
1308
1309        let first = doc.node(array.get(0).unwrap());
1310        assert_eq!(first.as_primitive().unwrap().as_str(), Some("Hello"));
1311
1312        let second = doc.node(array.get(1).unwrap());
1313        assert_eq!(second.as_primitive().unwrap().as_str(), Some("World"));
1314    }
1315
1316    #[test]
1317    fn test_eure_tuple_index() {
1318        // Test tuple index syntax: point.#0, point.#1
1319        let doc = eure!({
1320            point.#0 = 1.5
1321            point.#1 = 2.5
1322        });
1323
1324        let root_id = doc.get_root_id();
1325        let root = doc.node(root_id);
1326        let point_id = root.as_map().unwrap().get_node_id(&"point".into()).unwrap();
1327        let point = doc.node(point_id);
1328        let tuple = point.as_tuple().unwrap();
1329        assert_eq!(tuple.len(), 2);
1330    }
1331
1332    #[test]
1333    fn test_eure_mixed_path_extension_array() {
1334        // Test: a.%ext[].b = value
1335        let doc = eure!({
1336            field.%items[].name = "item1"
1337            field.%items[].name = "item2"
1338        });
1339
1340        let root_id = doc.get_root_id();
1341        let root = doc.node(root_id);
1342        let field_id = root.as_map().unwrap().get_node_id(&"field".into()).unwrap();
1343        let field = doc.node(field_id);
1344
1345        // Get extension
1346        let items_id = field.get_extension(&"items".parse().unwrap()).unwrap();
1347        let items = doc.node(items_id);
1348        let array = items.as_array().unwrap();
1349        assert_eq!(array.len(), 2);
1350    }
1351
1352    #[test]
1353    fn test_eure_mixed_path_array_extension() {
1354        // Test: items[].%variant = value
1355        let doc = eure!({
1356            items[].%variant = @code("text")
1357            items[].%variant = @code("number")
1358        });
1359
1360        let root_id = doc.get_root_id();
1361        let root = doc.node(root_id);
1362        let items_id = root.as_map().unwrap().get_node_id(&"items".into()).unwrap();
1363        let items = doc.node(items_id);
1364        let array = items.as_array().unwrap();
1365        assert_eq!(array.len(), 2);
1366
1367        // Check first element has extension
1368        let first_id = array.get(0).unwrap();
1369        let first = doc.node(first_id);
1370        let variant_id = first.get_extension(&"variant".parse().unwrap()).unwrap();
1371        let variant = doc.node(variant_id);
1372        assert_eq!(
1373            variant.as_primitive().unwrap().as_text().unwrap().as_str(),
1374            "text"
1375        );
1376    }
1377
1378    #[test]
1379    fn test_eure_tuple_key() {
1380        use crate::value::{ObjectKey, Tuple};
1381
1382        // Test tuple key: map.(1, "a") = value
1383        let doc = eure!({
1384            map.(1, "key") = "value1"
1385            map.(2, "key") = "value2"
1386        });
1387
1388        let root_id = doc.get_root_id();
1389        let root = doc.node(root_id);
1390        let map_id = root.as_map().unwrap().get_node_id(&"map".into()).unwrap();
1391        let map_node = doc.node(map_id);
1392        let map = map_node.as_map().unwrap();
1393        assert_eq!(map.len(), 2);
1394
1395        // Check key (1, "key") exists
1396        let tuple_key = ObjectKey::Tuple(Tuple(alloc::vec![1.into(), "key".into()]));
1397        let value_id = map.get_node_id(&tuple_key).unwrap();
1398        let value = doc.node(value_id);
1399        assert_eq!(value.as_primitive().unwrap().as_str(), Some("value1"));
1400    }
1401
1402    #[test]
1403    fn test_eure_tuple_key_with_bool() {
1404        use crate::value::{ObjectKey, Tuple};
1405
1406        // Test tuple key with bool: map.(true, 1) = value
1407        let doc = eure!({
1408            map.(true, 1) = "yes"
1409            map.(false, 1) = "no"
1410        });
1411
1412        let root_id = doc.get_root_id();
1413        let root = doc.node(root_id);
1414        let map_id = root.as_map().unwrap().get_node_id(&"map".into()).unwrap();
1415        let map_node = doc.node(map_id);
1416        let map = map_node.as_map().unwrap();
1417        assert_eq!(map.len(), 2);
1418
1419        // Check key (true, 1) exists
1420        let tuple_key = ObjectKey::Tuple(Tuple(alloc::vec![true.into(), 1.into()]));
1421        let value_id = map.get_node_id(&tuple_key).unwrap();
1422        let value = doc.node(value_id);
1423        assert_eq!(value.as_primitive().unwrap().as_str(), Some("yes"));
1424    }
1425
1426    #[test]
1427    fn test_eure_tuple_key_with_child() {
1428        use crate::value::{ObjectKey, Tuple};
1429
1430        // Test tuple key with child path: map.(1, 2).name = value
1431        let doc = eure!({
1432            map.(1, 2).name = "point_a"
1433            map.(1, 2).value = 42
1434        });
1435
1436        let root_id = doc.get_root_id();
1437        let root = doc.node(root_id);
1438        let map_id = root.as_map().unwrap().get_node_id(&"map".into()).unwrap();
1439        let map_node = doc.node(map_id);
1440        let map = map_node.as_map().unwrap();
1441
1442        // Check key (1, 2) has children
1443        let tuple_key = ObjectKey::Tuple(Tuple(alloc::vec![1.into(), 2.into()]));
1444        let entry_id = map.get_node_id(&tuple_key).unwrap();
1445        let entry = doc.node(entry_id);
1446        let entry_map = entry.as_map().unwrap();
1447
1448        let name_id = entry_map.get_node_id(&"name".into()).unwrap();
1449        let name = doc.node(name_id);
1450        assert_eq!(name.as_primitive().unwrap().as_str(), Some("point_a"));
1451    }
1452
1453    #[test]
1454    fn test_eure_string_key() {
1455        // Test string literal key for hyphenated identifiers: "min-length" = 3
1456        let doc = eure!({
1457            field."min-length" = 3
1458            field."max-length" = 20
1459        });
1460
1461        let root_id = doc.get_root_id();
1462        let root = doc.node(root_id);
1463        let field_id = root.as_map().unwrap().get_node_id(&"field".into()).unwrap();
1464        let field = doc.node(field_id);
1465        let field_map = field.as_map().unwrap();
1466
1467        // Check "min-length" key exists
1468        let min_id = field_map.get_node_id(&"min-length".into()).unwrap();
1469        let min_node = doc.node(min_id);
1470        assert!(matches!(
1471            min_node.as_primitive(),
1472            Some(crate::value::PrimitiveValue::Integer(_))
1473        ));
1474    }
1475
1476    #[test]
1477    fn test_eure_object_literal() {
1478        // Test object literal with => syntax
1479        let doc = eure!({
1480            variants.click = { "x" => 1.0, "y" => 2.0 }
1481        });
1482
1483        let root_id = doc.get_root_id();
1484        let root = doc.node(root_id);
1485        let variants_id = root
1486            .as_map()
1487            .unwrap()
1488            .get_node_id(&"variants".into())
1489            .unwrap();
1490        let variants = doc.node(variants_id);
1491        let click_id = variants
1492            .as_map()
1493            .unwrap()
1494            .get_node_id(&"click".into())
1495            .unwrap();
1496        let click = doc.node(click_id);
1497        let click_map = click.as_map().unwrap();
1498
1499        assert_eq!(click_map.len(), 2);
1500        assert!(click_map.get(&"x".into()).is_some());
1501        assert!(click_map.get(&"y".into()).is_some());
1502    }
1503
1504    #[test]
1505    fn test_eure_object_literal_with_string() {
1506        // Test object literal for schema-like patterns
1507        let doc = eure!({
1508            schema.variants.success = { "data" => "any" }
1509        });
1510
1511        let root_id = doc.get_root_id();
1512        let root = doc.node(root_id);
1513        let schema_id = root
1514            .as_map()
1515            .unwrap()
1516            .get_node_id(&"schema".into())
1517            .unwrap();
1518        let schema = doc.node(schema_id);
1519        let variants_id = schema
1520            .as_map()
1521            .unwrap()
1522            .get_node_id(&"variants".into())
1523            .unwrap();
1524        let variants = doc.node(variants_id);
1525        let success_id = variants
1526            .as_map()
1527            .unwrap()
1528            .get_node_id(&"success".into())
1529            .unwrap();
1530        let success = doc.node(success_id);
1531        let success_map = success.as_map().unwrap();
1532
1533        let data_id = success_map.get_node_id(&"data".into()).unwrap();
1534        let data = doc.node(data_id);
1535        assert_eq!(data.as_primitive().unwrap().as_str(), Some("any"));
1536    }
1537
1538    #[test]
1539    fn test_eure_value_binding() {
1540        // Test value binding at root: = value
1541        let doc = eure!({
1542            = @code("hello")
1543        });
1544
1545        let root_id = doc.get_root_id();
1546        let root = doc.node(root_id);
1547        let text = root.as_primitive().unwrap().as_text().unwrap();
1548        assert_eq!(text.as_str(), "hello");
1549    }
1550
1551    #[test]
1552    fn test_eure_value_binding_with_extension() {
1553        // Test value binding with extension: = value, %ext = value
1554        let doc = eure!({
1555            = @code("any")
1556            %variant = "literal"
1557        });
1558
1559        let root_id = doc.get_root_id();
1560        let root = doc.node(root_id);
1561
1562        // Check value
1563        let text = root.as_primitive().unwrap().as_text().unwrap();
1564        assert_eq!(text.as_str(), "any");
1565
1566        // Check extension
1567        let variant_id = root.get_extension(&"variant".parse().unwrap()).unwrap();
1568        let variant = doc.node(variant_id);
1569        assert_eq!(variant.as_primitive().unwrap().as_str(), Some("literal"));
1570    }
1571
1572    #[test]
1573    fn test_eure_empty_block() {
1574        // Empty block should create an empty map, not a Hole
1575        let doc = eure!({ config {} });
1576
1577        let root_id = doc.get_root_id();
1578        let root = doc.node(root_id);
1579        let config_id = root
1580            .as_map()
1581            .unwrap()
1582            .get_node_id(&"config".into())
1583            .unwrap();
1584        let config = doc.node(config_id);
1585
1586        // Should be an empty map, not Hole
1587        let map = config
1588            .as_map()
1589            .expect("Empty block should create an empty map");
1590        assert!(map.is_empty());
1591    }
1592
1593    // ========================================================================
1594    // Tests for new features: null, !, @code, @block
1595    // ========================================================================
1596
1597    #[test]
1598    fn test_eure_null_literal() {
1599        // Test null literal at field level
1600        let doc = eure!({ optional = null });
1601
1602        let root_id = doc.get_root_id();
1603        let root = doc.node(root_id);
1604        let opt_id = root
1605            .as_map()
1606            .unwrap()
1607            .get_node_id(&"optional".into())
1608            .unwrap();
1609        let opt = doc.node(opt_id);
1610        assert!(matches!(
1611            opt.as_primitive(),
1612            Some(crate::value::PrimitiveValue::Null)
1613        ));
1614    }
1615
1616    #[test]
1617    fn test_eure_null_root() {
1618        // Test null literal at root level
1619        let doc = eure!({
1620            = null
1621        });
1622
1623        let root_id = doc.get_root_id();
1624        let root = doc.node(root_id);
1625        assert!(matches!(
1626            root.as_primitive(),
1627            Some(crate::value::PrimitiveValue::Null)
1628        ));
1629    }
1630
1631    #[test]
1632    fn test_eure_hole_literal() {
1633        use crate::document::node::NodeValue;
1634
1635        // Test hole (!) literal at field level
1636        let doc = eure!({
1637            placeholder = !
1638        });
1639
1640        let root_id = doc.get_root_id();
1641        let root = doc.node(root_id);
1642        let placeholder_id = root
1643            .as_map()
1644            .unwrap()
1645            .get_node_id(&"placeholder".into())
1646            .unwrap();
1647        let placeholder = doc.node(placeholder_id);
1648        assert_eq!(placeholder.content, NodeValue::Hole(None));
1649    }
1650
1651    #[test]
1652    fn test_eure_hole_root() {
1653        use crate::document::node::NodeValue;
1654
1655        // Test hole at root level - explicit `= !` should preserve the Hole
1656        let doc = eure!({
1657            = !
1658        });
1659
1660        let root_id = doc.get_root_id();
1661        let root = doc.node(root_id);
1662        assert_eq!(root.content, NodeValue::Hole(None));
1663    }
1664
1665    #[test]
1666    fn test_eure_code_inline_implicit() {
1667        // Test @code("content") - inline code with implicit language
1668        let doc = eure!({
1669            snippet = @code("let x = 1")
1670        });
1671
1672        let root_id = doc.get_root_id();
1673        let root = doc.node(root_id);
1674        let snippet_id = root
1675            .as_map()
1676            .unwrap()
1677            .get_node_id(&"snippet".into())
1678            .unwrap();
1679        let snippet = doc.node(snippet_id);
1680        let text = snippet.as_primitive().unwrap().as_text().unwrap();
1681        assert_eq!(text.as_str(), "let x = 1");
1682        assert!(text.language.is_implicit());
1683    }
1684
1685    #[test]
1686    fn test_eure_code_inline_with_language() {
1687        // Test @code("lang", "content") - inline code with explicit language
1688        let doc = eure!({
1689            query = @code("sql", "SELECT * FROM users")
1690        });
1691
1692        let root_id = doc.get_root_id();
1693        let root = doc.node(root_id);
1694        let query_id = root.as_map().unwrap().get_node_id(&"query".into()).unwrap();
1695        let query = doc.node(query_id);
1696        let text = query.as_primitive().unwrap().as_text().unwrap();
1697        assert_eq!(text.as_str(), "SELECT * FROM users");
1698        assert_eq!(text.language.as_str(), Some("sql"));
1699    }
1700
1701    #[test]
1702    fn test_eure_block_implicit() {
1703        // Test @block("content") - block code with implicit language
1704        let doc = eure!({
1705            script = @block("fn main() {}")
1706        });
1707
1708        let root_id = doc.get_root_id();
1709        let root = doc.node(root_id);
1710        let script_id = root
1711            .as_map()
1712            .unwrap()
1713            .get_node_id(&"script".into())
1714            .unwrap();
1715        let script = doc.node(script_id);
1716        let text = script.as_primitive().unwrap().as_text().unwrap();
1717        // block adds trailing newline
1718        assert_eq!(text.as_str(), "fn main() {}\n");
1719        assert!(text.language.is_implicit());
1720    }
1721
1722    #[test]
1723    fn test_eure_block_with_language() {
1724        // Test @block("lang", "content") - block code with explicit language
1725        let doc = eure!({
1726            code = @block("rust", "fn main() {\n    println!(\"Hello\");\n}")
1727        });
1728
1729        let root_id = doc.get_root_id();
1730        let root = doc.node(root_id);
1731        let code_id = root.as_map().unwrap().get_node_id(&"code".into()).unwrap();
1732        let code = doc.node(code_id);
1733        let text = code.as_primitive().unwrap().as_text().unwrap();
1734        assert_eq!(text.language.as_str(), Some("rust"));
1735        assert!(text.as_str().contains("println!"));
1736    }
1737
1738    #[test]
1739    fn test_eure_code_at_root() {
1740        // Test @code at root level
1741        let doc = eure!({
1742            = @code("hello")
1743        });
1744
1745        let root_id = doc.get_root_id();
1746        let root = doc.node(root_id);
1747        let text = root.as_primitive().unwrap().as_text().unwrap();
1748        assert_eq!(text.as_str(), "hello");
1749    }
1750
1751    #[test]
1752    fn test_eure_code_with_language_at_root() {
1753        // Test @code("lang", "content") at root level
1754        let doc = eure!({
1755            = @code("sql", "SELECT 1")
1756        });
1757
1758        let root_id = doc.get_root_id();
1759        let root = doc.node(root_id);
1760        let text = root.as_primitive().unwrap().as_text().unwrap();
1761        assert_eq!(text.as_str(), "SELECT 1");
1762        assert_eq!(text.language.as_str(), Some("sql"));
1763    }
1764
1765    #[test]
1766    fn test_eure_block_at_root() {
1767        // Test @block("content") at root level
1768        let doc = eure!({
1769            = @block("fn main() {}")
1770        });
1771
1772        let root_id = doc.get_root_id();
1773        let root = doc.node(root_id);
1774        let text = root.as_primitive().unwrap().as_text().unwrap();
1775        assert_eq!(text.as_str(), "fn main() {}\n");
1776        assert!(text.language.is_implicit());
1777    }
1778
1779    #[test]
1780    fn test_eure_block_with_language_at_root() {
1781        // Test @block("lang", "content") at root level
1782        let doc = eure!({
1783            = @block("rust", "fn main() {}")
1784        });
1785
1786        let root_id = doc.get_root_id();
1787        let root = doc.node(root_id);
1788        let text = root.as_primitive().unwrap().as_text().unwrap();
1789        assert_eq!(text.as_str(), "fn main() {}\n");
1790        assert_eq!(text.language.as_str(), Some("rust"));
1791    }
1792
1793    // ========================================================================
1794    // Tests for edge cases and missing coverage
1795    // ========================================================================
1796
1797    #[test]
1798    fn test_eure_array_specific_index() {
1799        // Test array with specific index: items[0] = value, items[1] = value
1800        let doc = eure!({
1801            items[0] = "first"
1802            items[1] = "second"
1803        });
1804
1805        let root_id = doc.get_root_id();
1806        let root = doc.node(root_id);
1807        let items_id = root.as_map().unwrap().get_node_id(&"items".into()).unwrap();
1808        let items = doc.node(items_id);
1809        let array = items.as_array().unwrap();
1810        assert_eq!(array.len(), 2);
1811
1812        // Check values
1813        let first_id = array.get(0).unwrap();
1814        let first = doc.node(first_id);
1815        assert_eq!(first.as_primitive().unwrap().as_str(), Some("first"));
1816
1817        let second_id = array.get(1).unwrap();
1818        let second = doc.node(second_id);
1819        assert_eq!(second.as_primitive().unwrap().as_str(), Some("second"));
1820    }
1821
1822    #[test]
1823    fn test_eure_array_index_with_child() {
1824        // Test array index with child path: items[0].name = value
1825        let doc = eure!({
1826            items[0].name = "first"
1827            items[0].value = 1
1828            items[1].name = "second"
1829        });
1830
1831        let root_id = doc.get_root_id();
1832        let root = doc.node(root_id);
1833        let items_id = root.as_map().unwrap().get_node_id(&"items".into()).unwrap();
1834        let items = doc.node(items_id);
1835        let array = items.as_array().unwrap();
1836        assert_eq!(array.len(), 2);
1837
1838        // Check first element
1839        let first_id = array.get(0).unwrap();
1840        let first = doc.node(first_id);
1841        let name_id = first.as_map().unwrap().get_node_id(&"name".into()).unwrap();
1842        let name = doc.node(name_id);
1843        assert_eq!(name.as_primitive().unwrap().as_str(), Some("first"));
1844    }
1845
1846    #[test]
1847    fn test_eure_nested_empty_blocks() {
1848        // Test nested empty blocks: a { b { c {} } }
1849        let doc = eure!({
1850            a {
1851                b {
1852                    c {}
1853                }
1854            }
1855        });
1856
1857        let root_id = doc.get_root_id();
1858        let root = doc.node(root_id);
1859
1860        let a_id = root.as_map().unwrap().get_node_id(&"a".into()).unwrap();
1861        let a = doc.node(a_id);
1862
1863        let b_id = a.as_map().unwrap().get_node_id(&"b".into()).unwrap();
1864        let b = doc.node(b_id);
1865
1866        let c_id = b.as_map().unwrap().get_node_id(&"c".into()).unwrap();
1867        let c = doc.node(c_id);
1868
1869        // c should be an empty map
1870        let map = c.as_map().expect("c should be an empty map");
1871        assert!(map.is_empty());
1872    }
1873
1874    #[test]
1875    fn test_eure_multiple_extensions() {
1876        // Test multiple extensions on same node
1877        let doc = eure!({
1878            field.%variant = @code("text")
1879            field.%"variant-repr" = "internal"
1880            field.%schema = "custom"
1881        });
1882
1883        let root_id = doc.get_root_id();
1884        let root = doc.node(root_id);
1885        let field_id = root.as_map().unwrap().get_node_id(&"field".into()).unwrap();
1886        let field = doc.node(field_id);
1887
1888        // Check all extensions exist
1889        assert!(field.get_extension(&"variant".parse().unwrap()).is_some());
1890        assert!(
1891            field
1892                .get_extension(&"variant-repr".parse().unwrap())
1893                .is_some()
1894        );
1895        assert!(field.get_extension(&"schema".parse().unwrap()).is_some());
1896    }
1897
1898    #[test]
1899    fn test_eure_extension_on_array_element() {
1900        // Test extension on array element using indexed access
1901        // Note: items[] creates a new element each time, so we use items[0], items[1] etc.
1902        let doc = eure!({
1903            items[0].%variant = @code("text")
1904            items[0].value = "first"
1905            items[1].%variant = @code("number")
1906            items[1].value = 42
1907        });
1908
1909        let root_id = doc.get_root_id();
1910        let root = doc.node(root_id);
1911        let items_id = root.as_map().unwrap().get_node_id(&"items".into()).unwrap();
1912        let items = doc.node(items_id);
1913        let array = items.as_array().unwrap();
1914        assert_eq!(array.len(), 2);
1915
1916        // Check first element has extension and value
1917        let first_id = array.get(0).unwrap();
1918        let first = doc.node(first_id);
1919        let variant_id = first.get_extension(&"variant".parse().unwrap()).unwrap();
1920        let variant = doc.node(variant_id);
1921        assert_eq!(
1922            variant.as_primitive().unwrap().as_text().unwrap().as_str(),
1923            "text"
1924        );
1925        let value_id = first
1926            .as_map()
1927            .unwrap()
1928            .get_node_id(&"value".into())
1929            .unwrap();
1930        let value = doc.node(value_id);
1931        assert_eq!(value.as_primitive().unwrap().as_str(), Some("first"));
1932
1933        // Check second element
1934        let second_id = array.get(1).unwrap();
1935        let second = doc.node(second_id);
1936        let variant_id = second.get_extension(&"variant".parse().unwrap()).unwrap();
1937        let variant = doc.node(variant_id);
1938        assert_eq!(
1939            variant.as_primitive().unwrap().as_text().unwrap().as_str(),
1940            "number"
1941        );
1942    }
1943
1944    #[test]
1945    fn test_eure_deep_nesting() {
1946        // Test deeply nested paths (5+ levels)
1947        let doc = eure!({ a.b.c.d.e.f = "deep" });
1948
1949        let root_id = doc.get_root_id();
1950        let root = doc.node(root_id);
1951
1952        let a_id = root.as_map().unwrap().get_node_id(&"a".into()).unwrap();
1953        let a = doc.node(a_id);
1954        let b_id = a.as_map().unwrap().get_node_id(&"b".into()).unwrap();
1955        let b = doc.node(b_id);
1956        let c_id = b.as_map().unwrap().get_node_id(&"c".into()).unwrap();
1957        let c = doc.node(c_id);
1958        let d_id = c.as_map().unwrap().get_node_id(&"d".into()).unwrap();
1959        let d = doc.node(d_id);
1960        let e_id = d.as_map().unwrap().get_node_id(&"e".into()).unwrap();
1961        let e = doc.node(e_id);
1962        let f_id = e.as_map().unwrap().get_node_id(&"f".into()).unwrap();
1963        let f = doc.node(f_id);
1964
1965        assert_eq!(f.as_primitive().unwrap().as_str(), Some("deep"));
1966    }
1967
1968    #[test]
1969    fn test_eure_empty_array_literal() {
1970        // Test empty array literal: items = []
1971        let doc = eure!({ items = [] });
1972
1973        let root_id = doc.get_root_id();
1974        let root = doc.node(root_id);
1975        let items_id = root.as_map().unwrap().get_node_id(&"items".into()).unwrap();
1976        let items = doc.node(items_id);
1977        let array = items.as_array().unwrap();
1978        assert!(array.is_empty());
1979    }
1980
1981    #[test]
1982    fn test_eure_empty_tuple_literal() {
1983        // Test empty tuple literal: point = ()
1984        let doc = eure!({ point = () });
1985
1986        let root_id = doc.get_root_id();
1987        let root = doc.node(root_id);
1988        let point_id = root.as_map().unwrap().get_node_id(&"point".into()).unwrap();
1989        let point = doc.node(point_id);
1990        let tuple = point.as_tuple().unwrap();
1991        assert!(tuple.is_empty());
1992    }
1993
1994    #[test]
1995    fn test_eure_empty_map_literal() {
1996        // Test empty map literal: data = {}
1997        // Note: This uses block syntax which creates empty map
1998        let doc = eure!({ data {} });
1999
2000        let root_id = doc.get_root_id();
2001        let root = doc.node(root_id);
2002        let data_id = root.as_map().unwrap().get_node_id(&"data".into()).unwrap();
2003        let data = doc.node(data_id);
2004        let map = data.as_map().unwrap();
2005        assert!(map.is_empty());
2006    }
2007
2008    #[test]
2009    fn test_eure_mixed_null_and_values() {
2010        // Test mixing null with other values
2011        let doc = eure!({
2012            name = "Alice"
2013            age = null
2014            active = true
2015            score = null
2016        });
2017
2018        let root_id = doc.get_root_id();
2019        let root = doc.node(root_id);
2020        let map = root.as_map().unwrap();
2021        assert_eq!(map.len(), 4);
2022
2023        let age_id = map.get_node_id(&"age".into()).unwrap();
2024        let age = doc.node(age_id);
2025        assert!(matches!(
2026            age.as_primitive(),
2027            Some(crate::value::PrimitiveValue::Null)
2028        ));
2029    }
2030
2031    // ========================================================================
2032    // Tests for section syntax
2033    // ========================================================================
2034
2035    #[test]
2036    fn test_eure_section_basic() {
2037        // Test basic section syntax: @section followed by bindings
2038        let doc = eure!({
2039            @user
2040            name = "Alice"
2041            age = 30
2042        });
2043
2044        let root_id = doc.get_root_id();
2045        let root = doc.node(root_id);
2046        let user_id = root.as_map().unwrap().get_node_id(&"user".into()).unwrap();
2047        let user = doc.node(user_id);
2048        let name_id = user.as_map().unwrap().get_node_id(&"name".into()).unwrap();
2049        let name = doc.node(name_id);
2050        assert_eq!(name.as_primitive().unwrap().as_str(), Some("Alice"));
2051    }
2052
2053    #[test]
2054    fn test_eure_section_multiple() {
2055        // Test multiple sections
2056        let doc = eure!({
2057            @user
2058            name = "Alice"
2059
2060            @settings
2061            theme = "dark"
2062        });
2063
2064        let root_id = doc.get_root_id();
2065        let root = doc.node(root_id);
2066
2067        let user_id = root.as_map().unwrap().get_node_id(&"user".into()).unwrap();
2068        let user = doc.node(user_id);
2069        assert!(user.as_map().unwrap().get_node_id(&"name".into()).is_some());
2070
2071        let settings_id = root
2072            .as_map()
2073            .unwrap()
2074            .get_node_id(&"settings".into())
2075            .unwrap();
2076        let settings = doc.node(settings_id);
2077        assert!(
2078            settings
2079                .as_map()
2080                .unwrap()
2081                .get_node_id(&"theme".into())
2082                .is_some()
2083        );
2084    }
2085
2086    #[test]
2087    fn test_eure_section_dotted_path() {
2088        // Test section with dotted path
2089        let doc = eure!({
2090            @user.profile
2091            name = "Alice"
2092        });
2093
2094        let root_id = doc.get_root_id();
2095        let root = doc.node(root_id);
2096        let user_id = root.as_map().unwrap().get_node_id(&"user".into()).unwrap();
2097        let user = doc.node(user_id);
2098        let profile_id = user
2099            .as_map()
2100            .unwrap()
2101            .get_node_id(&"profile".into())
2102            .unwrap();
2103        let profile = doc.node(profile_id);
2104        assert!(
2105            profile
2106                .as_map()
2107                .unwrap()
2108                .get_node_id(&"name".into())
2109                .is_some()
2110        );
2111    }
2112
2113    #[test]
2114    fn test_eure_section_root_array_marker() {
2115        let doc = eure!({
2116            @[]
2117            text = "Hello"
2118            @[]
2119            text = "World"
2120        });
2121
2122        let root_id = doc.get_root_id();
2123        let root = doc.node(root_id);
2124        let array = root.as_array().unwrap();
2125        assert_eq!(array.len(), 2);
2126
2127        let first = doc.node(array.get(0).unwrap());
2128        let first_text_id = first.as_map().unwrap().get_node_id(&"text".into()).unwrap();
2129        let first_text = doc.node(first_text_id);
2130        assert_eq!(first_text.as_primitive().unwrap().as_str(), Some("Hello"));
2131
2132        let second = doc.node(array.get(1).unwrap());
2133        let second_text_id = second
2134            .as_map()
2135            .unwrap()
2136            .get_node_id(&"text".into())
2137            .unwrap();
2138        let second_text = doc.node(second_text_id);
2139        assert_eq!(second_text.as_primitive().unwrap().as_str(), Some("World"));
2140    }
2141
2142    #[test]
2143    fn test_eure_section_explicit_array_continuation() {
2144        let doc = eure!({
2145            @dialog #[]
2146            text = "Hello"
2147            @dialog #[]
2148            text = "World"
2149        });
2150
2151        let root_id = doc.get_root_id();
2152        let root = doc.node(root_id);
2153        let dialog_id = root
2154            .as_map()
2155            .unwrap()
2156            .get_node_id(&"dialog".into())
2157            .unwrap();
2158        let dialog = doc.node(dialog_id);
2159        let array = dialog.as_array().unwrap();
2160        assert_eq!(array.len(), 2);
2161
2162        let first = doc.node(array.get(0).unwrap());
2163        let first_text_id = first.as_map().unwrap().get_node_id(&"text".into()).unwrap();
2164        let first_text = doc.node(first_text_id);
2165        assert_eq!(first_text.as_primitive().unwrap().as_str(), Some("Hello"));
2166
2167        let second = doc.node(array.get(1).unwrap());
2168        let second_text_id = second
2169            .as_map()
2170            .unwrap()
2171            .get_node_id(&"text".into())
2172            .unwrap();
2173        let second_text = doc.node(second_text_id);
2174        assert_eq!(second_text.as_primitive().unwrap().as_str(), Some("World"));
2175    }
2176
2177    #[test]
2178    fn test_eure_section_with_block() {
2179        // Test section with block syntax: @path { ... }
2180        let doc = eure!({
2181            @user {
2182                name = "Alice"
2183                age = 30
2184            }
2185        });
2186
2187        let root_id = doc.get_root_id();
2188        let root = doc.node(root_id);
2189        let user_id = root.as_map().unwrap().get_node_id(&"user".into()).unwrap();
2190        let user = doc.node(user_id);
2191        assert!(user.as_map().unwrap().get_node_id(&"name".into()).is_some());
2192        assert!(user.as_map().unwrap().get_node_id(&"age".into()).is_some());
2193    }
2194
2195    #[test]
2196    fn test_eure_section_block_with_nested() {
2197        // Test section block with nested structure
2198        let doc = eure!({
2199            @config {
2200                server {
2201                    host = "localhost"
2202                    port = 8080
2203                }
2204                debug = true
2205            }
2206        });
2207
2208        let root_id = doc.get_root_id();
2209        let root = doc.node(root_id);
2210        let config_id = root
2211            .as_map()
2212            .unwrap()
2213            .get_node_id(&"config".into())
2214            .unwrap();
2215        let config = doc.node(config_id);
2216        assert!(
2217            config
2218                .as_map()
2219                .unwrap()
2220                .get_node_id(&"server".into())
2221                .is_some()
2222        );
2223        assert!(
2224            config
2225                .as_map()
2226                .unwrap()
2227                .get_node_id(&"debug".into())
2228                .is_some()
2229        );
2230
2231        let server_id = config
2232            .as_map()
2233            .unwrap()
2234            .get_node_id(&"server".into())
2235            .unwrap();
2236        let server = doc.node(server_id);
2237        assert!(
2238            server
2239                .as_map()
2240                .unwrap()
2241                .get_node_id(&"host".into())
2242                .is_some()
2243        );
2244        assert!(
2245            server
2246                .as_map()
2247                .unwrap()
2248                .get_node_id(&"port".into())
2249                .is_some()
2250        );
2251    }
2252
2253    #[test]
2254    fn test_eure_section_block_multiple() {
2255        // Test multiple sections with block syntax
2256        let doc = eure!({
2257            @user {
2258                name = "Alice"
2259            }
2260            @settings {
2261                theme = "dark"
2262            }
2263        });
2264
2265        let root_id = doc.get_root_id();
2266        let root = doc.node(root_id);
2267        assert!(root.as_map().unwrap().get_node_id(&"user".into()).is_some());
2268        assert!(
2269            root.as_map()
2270                .unwrap()
2271                .get_node_id(&"settings".into())
2272                .is_some()
2273        );
2274    }
2275
2276    #[test]
2277    fn test_eure_section_block_dotted_path() {
2278        // Test section block with dotted path: @a.b { ... }
2279        let doc = eure!({
2280            @server.config {
2281                host = "localhost"
2282                port = 8080
2283            }
2284        });
2285
2286        let root_id = doc.get_root_id();
2287        let root = doc.node(root_id);
2288        let server_id = root
2289            .as_map()
2290            .unwrap()
2291            .get_node_id(&"server".into())
2292            .unwrap();
2293        let server = doc.node(server_id);
2294        let config_id = server
2295            .as_map()
2296            .unwrap()
2297            .get_node_id(&"config".into())
2298            .unwrap();
2299        let config = doc.node(config_id);
2300        assert!(
2301            config
2302                .as_map()
2303                .unwrap()
2304                .get_node_id(&"host".into())
2305                .is_some()
2306        );
2307        assert!(
2308            config
2309                .as_map()
2310                .unwrap()
2311                .get_node_id(&"port".into())
2312                .is_some()
2313        );
2314    }
2315
2316    #[test]
2317    fn test_eure_section_block_empty() {
2318        // Test section with empty block
2319        let doc = eure!({
2320            @empty {}
2321        });
2322
2323        let root_id = doc.get_root_id();
2324        let root = doc.node(root_id);
2325        let empty_id = root.as_map().unwrap().get_node_id(&"empty".into()).unwrap();
2326        let empty = doc.node(empty_id);
2327        // Empty block creates empty map
2328        assert!(empty.as_map().unwrap().is_empty());
2329    }
2330
2331    #[test]
2332    fn test_eure_section_mixed_styles() {
2333        // Test mixing section styles: some with blocks, some without
2334        let doc = eure!({
2335            @user {
2336                name = "Alice"
2337            }
2338
2339            @settings
2340            theme = "dark"
2341            debug = true
2342
2343            @logging {
2344                level = "info"
2345            }
2346        });
2347
2348        let root_id = doc.get_root_id();
2349        let root = doc.node(root_id);
2350
2351        // user should have name
2352        let user_id = root.as_map().unwrap().get_node_id(&"user".into()).unwrap();
2353        let user = doc.node(user_id);
2354        assert!(user.as_map().unwrap().get_node_id(&"name".into()).is_some());
2355
2356        // settings should have theme and debug
2357        let settings_id = root
2358            .as_map()
2359            .unwrap()
2360            .get_node_id(&"settings".into())
2361            .unwrap();
2362        let settings = doc.node(settings_id);
2363        assert!(
2364            settings
2365                .as_map()
2366                .unwrap()
2367                .get_node_id(&"theme".into())
2368                .is_some()
2369        );
2370        assert!(
2371            settings
2372                .as_map()
2373                .unwrap()
2374                .get_node_id(&"debug".into())
2375                .is_some()
2376        );
2377
2378        // logging should have level
2379        let logging_id = root
2380            .as_map()
2381            .unwrap()
2382            .get_node_id(&"logging".into())
2383            .unwrap();
2384        let logging = doc.node(logging_id);
2385        assert!(
2386            logging
2387                .as_map()
2388                .unwrap()
2389                .get_node_id(&"level".into())
2390                .is_some()
2391        );
2392    }
2393
2394    #[test]
2395    fn test_eure_section_in_section_block() {
2396        // Test section in section block
2397        let doc = eure!({
2398            @ settings {
2399                theme = "dark"
2400                @ logging
2401                level = "info"
2402            }
2403        });
2404
2405        let settings = doc
2406            .parse_context(doc.get_root_id())
2407            .parse_record()
2408            .expect("Failed to parse record")
2409            .field_record("settings")
2410            .expect("Failed to parse settings");
2411        let theme = settings
2412            .parse_field::<&str>("theme")
2413            .expect("Failed to parse theme");
2414        let logging = settings
2415            .field_record("logging")
2416            .expect("Failed to parse logging")
2417            .parse_field::<&str>("level")
2418            .expect("Failed to parse level");
2419        settings
2420            .deny_unknown_fields()
2421            .expect("Failed to deny unknown fields");
2422        assert_eq!(theme, "dark");
2423        assert_eq!(logging, "info");
2424    }
2425
2426    #[test]
2427    fn test_eure_variable_text() {
2428        // Test using a variable for Text value
2429        use crate::text::Text;
2430        let code = Text::inline_implicit("fn main() {}");
2431        let doc = eure!({ snippet = code });
2432
2433        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2434        let snippet_ctx = root.field("snippet").unwrap();
2435        let snippet_node = doc.node(snippet_ctx.node_id());
2436        let text = snippet_node.as_primitive().unwrap().as_text().unwrap();
2437        assert_eq!(text.as_str(), "fn main() {}");
2438    }
2439
2440    #[test]
2441    fn test_eure_variable_in_array() {
2442        // Test using variables in array literals
2443        use alloc::vec::Vec;
2444        let first = "one";
2445        let second = "two";
2446        let third = "three";
2447        let doc = eure!({ items = [first, second, third] });
2448
2449        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2450        let items = root.parse_field::<Vec<&str>>("items").unwrap();
2451        assert_eq!(items, vec!["one", "two", "three"]);
2452    }
2453
2454    #[test]
2455    fn test_eure_variable_in_tuple() {
2456        // Test using variables in tuple literals
2457        let x = 1.5;
2458        let y = 2.5;
2459        let doc = eure!({ point = (x, y) });
2460
2461        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2462        let point = root.parse_field::<(f64, f64)>("point").unwrap();
2463        assert_eq!(point, (1.5, 2.5));
2464    }
2465
2466    #[test]
2467    fn test_eure_variable_in_object_literal() {
2468        // Test using variables in object literal values
2469        let x_val = 10.0;
2470        let y_val = 20.0;
2471        let doc = eure!({
2472            coords = {
2473                "x" => x_val
2474                "y" => y_val
2475            }
2476        });
2477
2478        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2479        let coords = root.field_record("coords").unwrap();
2480        let x = coords.parse_field::<f64>("x").unwrap();
2481        let y = coords.parse_field::<f64>("y").unwrap();
2482        assert_eq!(x, 10.0);
2483        assert_eq!(y, 20.0);
2484    }
2485
2486    #[test]
2487    #[allow(clippy::bool_assert_comparison)] // Testing parse_field deserialization returns correct value
2488    fn test_eure_variable_mixed_with_literals() {
2489        // Test mixing variables and literals
2490        let username = "bob";
2491        let is_active = true;
2492        let doc = eure!({
2493            user.name = username
2494            user.active = is_active
2495            user.role = "admin"
2496            user.level = 5
2497        });
2498
2499        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2500        let user = root.field_record("user").unwrap();
2501        assert_eq!(user.parse_field::<&str>("name").unwrap(), "bob");
2502        assert_eq!(user.parse_field::<bool>("active").unwrap(), true);
2503        assert_eq!(user.parse_field::<&str>("role").unwrap(), "admin");
2504        assert_eq!(user.parse_field::<i32>("level").unwrap(), 5);
2505    }
2506
2507    #[test]
2508    fn test_eure_variable_in_nested_array() {
2509        // Test using variables in nested array structures
2510        use alloc::vec::Vec;
2511        let tag1 = "rust";
2512        let tag2 = "macro";
2513        let doc = eure!({
2514            tags[] = tag1
2515            tags[] = tag2
2516        });
2517
2518        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2519        let tags = root.parse_field::<Vec<&str>>("tags").unwrap();
2520        assert_eq!(tags, vec!["rust", "macro"]);
2521    }
2522
2523    #[test]
2524    fn test_eure_variable_at_root() {
2525        // Test using a variable for root value binding
2526        let value = 42;
2527        let doc = eure!({
2528            = value
2529        });
2530
2531        let ctx = doc.parse_context(doc.get_root_id());
2532        let root_value = ctx.parse::<i32>().unwrap();
2533        assert_eq!(root_value, 42);
2534    }
2535
2536    #[test]
2537    fn test_eure_variable_in_section() {
2538        // Test using variables in section syntax
2539        let theme_value = "dark";
2540        let lang_value = "en";
2541        let doc = eure!({
2542            @settings
2543            theme = theme_value
2544            language = lang_value
2545        });
2546
2547        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2548        let settings = root.field_record("settings").unwrap();
2549        assert_eq!(settings.parse_field::<&str>("theme").unwrap(), "dark");
2550        assert_eq!(settings.parse_field::<&str>("language").unwrap(), "en");
2551    }
2552
2553    #[test]
2554    fn test_eure_variable_null_and_primitive() {
2555        // Test using PrimitiveValue::Null from a variable
2556        use crate::value::PrimitiveValue;
2557        let null_value = PrimitiveValue::Null;
2558        let doc = eure!({ optional = null_value });
2559
2560        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2561        let optional_ctx = root.field("optional").unwrap();
2562        let optional_node = doc.node(optional_ctx.node_id());
2563        assert!(matches!(
2564            optional_node.as_primitive().unwrap(),
2565            PrimitiveValue::Null
2566        ));
2567    }
2568
2569    // ========================================================================
2570    // eure_source! macro tests
2571    // ========================================================================
2572
2573    #[test]
2574    fn test_eure_source_empty() {
2575        let source_doc = eure_source!({});
2576        assert_eq!(source_doc.document(), &EureDocument::new_empty());
2577        assert!(source_doc.root_source().bindings.is_empty());
2578        assert!(source_doc.root_source().sections.is_empty());
2579    }
2580
2581    #[test]
2582    fn test_eure_source_simple_bindings() {
2583        let source_doc = eure_source!({
2584            name = "Alice"
2585            age = 30
2586        });
2587
2588        // Verify document content
2589        let doc = source_doc.document();
2590        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2591        assert_eq!(root.parse_field::<&str>("name").unwrap(), "Alice");
2592        assert_eq!(root.parse_field::<i64>("age").unwrap(), 30);
2593
2594        // Verify source structure
2595        let root_source = source_doc.root_source();
2596        assert_eq!(root_source.bindings.len(), 2);
2597        assert!(root_source.sections.is_empty());
2598    }
2599
2600    #[test]
2601    fn test_eure_source_nested() {
2602        use crate::source::BindSource;
2603
2604        let source_doc = eure_source!({
2605            user {
2606                name = "Bob"
2607                active = true
2608            }
2609        });
2610
2611        let doc = source_doc.document();
2612        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2613        let user = root.field_record("user").unwrap();
2614        assert_eq!(user.parse_field::<&str>("name").unwrap(), "Bob");
2615        assert!(user.parse_field::<bool>("active").unwrap());
2616
2617        // Verify source structure - should have one binding with a block
2618        let root_source = source_doc.root_source();
2619        assert_eq!(root_source.bindings.len(), 1);
2620        match &root_source.bindings[0].bind {
2621            BindSource::Block(source_id) => {
2622                let inner = source_doc.source(*source_id);
2623                assert_eq!(inner.bindings.len(), 2);
2624            }
2625            _ => panic!("Expected BindSource::Block"),
2626        }
2627    }
2628
2629    #[test]
2630    fn test_eure_generic_entry_point() {
2631        // Test the generic entry point with explicit constructor
2632        use crate::document::constructor::DocumentConstructor;
2633
2634        let mut c = DocumentConstructor::new();
2635        eure!(c; {
2636            x = 1
2637            y = 2
2638        });
2639        let doc = c.finish();
2640
2641        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2642        assert_eq!(root.parse_field::<i64>("x").unwrap(), 1);
2643        assert_eq!(root.parse_field::<i64>("y").unwrap(), 2);
2644    }
2645
2646    #[test]
2647    fn test_eure_source_generic_entry_point() {
2648        // Test the generic entry point with SourceConstructor
2649        use crate::document::source_constructor::SourceConstructor;
2650
2651        let mut c = SourceConstructor::new();
2652        eure!(c; {
2653            message = "hello"
2654        });
2655        let source_doc = c.finish();
2656
2657        let doc = source_doc.document();
2658        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2659        assert_eq!(root.parse_field::<&str>("message").unwrap(), "hello");
2660    }
2661}