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(None) 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(None)).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(None)).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(None)).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(None)).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(None)).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: @path followed by bindings
451    (@stmt $c:ident; @ $seg:ident $($rest:tt)*) => {{
452        $c.begin_section();
453        let scope = $c.begin_scope();
454        $c.navigate($crate::path::PathSegment::Ident(
455            $crate::identifier::Identifier::new_unchecked(stringify!($seg))
456        )).unwrap();
457        $crate::eure!(@section_after_seg $c scope; $($rest)*);
458    }};
459
460    // Start parsing a path-based statement - delegate to path parser
461    // Creates a scope that will be closed when the statement ends
462    (@stmt $c:ident; $($tokens:tt)+) => {{
463        $c.begin_binding();
464        let scope = $c.begin_scope();
465        $crate::eure!(@path $c scope; $($tokens)+);
466    }};
467
468    // ========================================================================
469    // Section handlers (@section_*)
470    //
471    // Parse section syntax: @path followed by bindings until next section or end.
472    // ========================================================================
473
474    // After parsing a segment, check for more path or bindings
475    (@section_after_seg $c:ident $scope:ident; . $seg:ident $($rest:tt)*) => {{
476        $c.navigate($crate::path::PathSegment::Ident(
477            $crate::identifier::Identifier::new_unchecked(stringify!($seg))
478        )).unwrap();
479        $crate::eure!(@section_after_seg $c $scope; $($rest)*);
480    }};
481
482    // Section with value binding: @path = value (pattern #4 with value)
483    (@section_after_seg $c:ident $scope:ident; = $v:tt $($rest:tt)*) => {{
484        $c.bind_from($crate::eure!(@value_tt $v)).unwrap();
485        $c.begin_section_items();
486        // Continue parsing bindings within this section
487        $crate::eure!(@section_bindings $c $scope; $($rest)*);
488    }};
489
490    // Section with empty block: @path {} (pattern #5)
491    (@section_after_seg $c:ident $scope:ident; {} $($rest:tt)*) => {{
492        $c.begin_eure_block();
493        $c.bind_empty_map().unwrap();
494        $c.end_eure_block().unwrap();
495        $c.end_scope($scope).unwrap();
496        $c.end_section_block().unwrap();
497        $crate::eure!(@stmt $c; $($rest)*);
498    }};
499
500    // Section with non-empty block: @path { ... } (pattern #5/#6)
501    (@section_after_seg $c:ident $scope:ident; { $($inner:tt)+ } $($rest:tt)*) => {{
502        $c.begin_eure_block();
503        $crate::eure!(@stmt $c; $($inner)+);
504        $c.end_eure_block().unwrap();
505        $c.end_scope($scope).unwrap();
506        $c.end_section_block().unwrap();
507        $crate::eure!(@stmt $c; $($rest)*);
508    }};
509
510    // Section body starts (no value binding, no block) (pattern #4)
511    (@section_after_seg $c:ident $scope:ident; $($rest:tt)*) => {{
512        $c.begin_section_items();
513        $crate::eure!(@section_bindings $c $scope; $($rest)*);
514    }};
515
516    // Section bindings: empty - close scope
517    (@section_bindings $c:ident $scope:ident;) => {{
518        $c.end_section_items().unwrap();
519        $c.end_scope($scope).unwrap();
520    }};
521
522    // Section bindings: skip optional comma
523    (@section_bindings $c:ident $scope:ident; , $($rest:tt)*) => {{
524        $crate::eure!(@section_bindings $c $scope; $($rest)*);
525    }};
526
527    // Section bindings: new section starts - close current and start new
528    (@section_bindings $c:ident $scope:ident; @ $seg:ident $($rest:tt)*) => {{
529        $c.end_section_items().unwrap();
530        $c.end_scope($scope).unwrap();
531        $crate::eure!(@stmt $c; @ $seg $($rest)*);
532    }};
533
534    // Section bindings: regular binding (path-based)
535    (@section_bindings $c:ident $scope:ident; $($tokens:tt)+) => {{
536        $c.begin_binding();
537        let inner_scope = $c.begin_scope();
538        $crate::eure!(@section_path $c $scope inner_scope; $($tokens)+);
539    }};
540
541    // Section path parsing - similar to @path but returns to @section_bindings
542    (@section_path $c:ident $section_scope:ident $scope:ident; $seg:ident $($rest:tt)*) => {{
543        $c.navigate($crate::path::PathSegment::Ident(
544            $crate::identifier::Identifier::new_unchecked(stringify!($seg))
545        )).unwrap();
546        $crate::eure!(@section_after_path $c $section_scope $scope; $($rest)*);
547    }};
548
549    // Section path: extension segment
550    (@section_path $c:ident $section_scope:ident $scope:ident; % $ext:ident $($rest:tt)*) => {{
551        $c.navigate($crate::path::PathSegment::Extension(
552            $crate::identifier::Identifier::new_unchecked(stringify!($ext))
553        )).unwrap();
554        $crate::eure!(@section_after_path $c $section_scope $scope; $($rest)*);
555    }};
556
557    // Section path: extension with string literal
558    (@section_path $c:ident $section_scope:ident $scope:ident; % $ext:literal $($rest:tt)*) => {{
559        $c.navigate($crate::path::PathSegment::Extension(
560            $ext.parse().unwrap()
561        )).unwrap();
562        $crate::eure!(@section_after_path $c $section_scope $scope; $($rest)*);
563    }};
564
565    // Section path: tuple index
566    (@section_path $c:ident $section_scope:ident $scope:ident; # $idx:literal $($rest:tt)*) => {{
567        $c.navigate($crate::path::PathSegment::TupleIndex($idx)).unwrap();
568        $crate::eure!(@section_after_path $c $section_scope $scope; $($rest)*);
569    }};
570
571    // Section path: tuple key
572    (@section_path $c:ident $section_scope:ident $scope:ident; ($($tuple:tt)*) $($rest:tt)*) => {{
573        let key = $crate::eure!(@build_tuple_key; $($tuple)*);
574        $c.navigate($crate::path::PathSegment::Value(key)).unwrap();
575        $crate::eure!(@section_after_path $c $section_scope $scope; $($rest)*);
576    }};
577
578    // Section path: string literal key
579    (@section_path $c:ident $section_scope:ident $scope:ident; $key:literal $($rest:tt)*) => {{
580        $c.navigate($crate::path::PathSegment::Value($key.into())).unwrap();
581        $crate::eure!(@section_after_path $c $section_scope $scope; $($rest)*);
582    }};
583
584    // After section path segment: array marker
585    (@section_after_path $c:ident $section_scope:ident $scope:ident; [$($arr:tt)*] $($rest:tt)*) => {{
586        $crate::eure!(@section_array_marker $c $section_scope $scope [$($arr)*]; $($rest)*);
587    }};
588
589    // After section path segment: continue to terminal
590    (@section_after_path $c:ident $section_scope:ident $scope:ident; $($rest:tt)*) => {{
591        $crate::eure!(@section_terminal $c $section_scope $scope; $($rest)*);
592    }};
593
594    // Section array marker: empty (push)
595    (@section_array_marker $c:ident $section_scope:ident $scope:ident []; $($rest:tt)*) => {{
596        $c.navigate($crate::path::PathSegment::ArrayIndex(None)).unwrap();
597        $crate::eure!(@section_terminal $c $section_scope $scope; $($rest)*);
598    }};
599
600    // Section array marker: with index
601    (@section_array_marker $c:ident $section_scope:ident $scope:ident [$idx:literal]; $($rest:tt)*) => {{
602        $c.navigate($crate::path::PathSegment::ArrayIndex(Some($idx))).unwrap();
603        $crate::eure!(@section_terminal $c $section_scope $scope; $($rest)*);
604    }};
605
606    // Section terminal: more path
607    (@section_terminal $c:ident $section_scope:ident $scope:ident; . $($rest:tt)+) => {{
608        $crate::eure!(@section_path $c $section_scope $scope; $($rest)+);
609    }};
610
611    // Section terminal: hole
612    (@section_terminal $c:ident $section_scope:ident $scope:ident; = ! $($rest:tt)*) => {{
613        $c.bind_hole(None).unwrap();
614        $c.end_scope($scope).unwrap();
615        $c.end_binding_value().unwrap();
616        $crate::eure!(@section_bindings $c $section_scope; $($rest)*);
617    }};
618
619    // Section terminal: inline code with implicit language
620    (@section_terminal $c:ident $section_scope:ident $scope:ident; = @ code ($content:literal) $($rest:tt)*) => {{
621        $c.bind_from($crate::text::Text::inline_implicit($content)).unwrap();
622        $c.end_scope($scope).unwrap();
623        $c.end_binding_value().unwrap();
624        $crate::eure!(@section_bindings $c $section_scope; $($rest)*);
625    }};
626
627    // Section terminal: inline code with explicit language
628    (@section_terminal $c:ident $section_scope:ident $scope:ident; = @ code ($lang:literal, $content:literal) $($rest:tt)*) => {{
629        $c.bind_from($crate::text::Text::inline($content, $lang)).unwrap();
630        $c.end_scope($scope).unwrap();
631        $c.end_binding_value().unwrap();
632        $crate::eure!(@section_bindings $c $section_scope; $($rest)*);
633    }};
634
635    // Section terminal: block code with implicit language
636    (@section_terminal $c:ident $section_scope:ident $scope:ident; = @ block ($content:literal) $($rest:tt)*) => {{
637        $c.bind_from($crate::text::Text::block_implicit($content)).unwrap();
638        $c.end_scope($scope).unwrap();
639        $c.end_binding_value().unwrap();
640        $crate::eure!(@section_bindings $c $section_scope; $($rest)*);
641    }};
642
643    // Section terminal: block code with explicit language
644    (@section_terminal $c:ident $section_scope:ident $scope:ident; = @ block ($lang:literal, $content:literal) $($rest:tt)*) => {{
645        $c.bind_from($crate::text::Text::block($content, $lang)).unwrap();
646        $c.end_scope($scope).unwrap();
647        $c.end_binding_value().unwrap();
648        $crate::eure!(@section_bindings $c $section_scope; $($rest)*);
649    }};
650
651    // Section terminal: value assignment
652    (@section_terminal $c:ident $section_scope:ident $scope:ident; = $v:tt $($rest:tt)*) => {{
653        $c.bind_from($crate::eure!(@value_tt $v)).unwrap();
654        $c.end_scope($scope).unwrap();
655        $c.end_binding_value().unwrap();
656        $crate::eure!(@section_bindings $c $section_scope; $($rest)*);
657    }};
658
659    // Section terminal: empty block
660    (@section_terminal $c:ident $section_scope:ident $scope:ident; {} $($rest:tt)*) => {{
661        $c.begin_eure_block();
662        $c.bind_empty_map().unwrap();
663        $c.end_eure_block().unwrap();
664        $c.end_scope($scope).unwrap();
665        $c.end_binding_block().unwrap();
666        $crate::eure!(@section_bindings $c $section_scope; $($rest)*);
667    }};
668
669    // Section terminal: non-empty block
670    (@section_terminal $c:ident $section_scope:ident $scope:ident; { $($inner:tt)+ } $($rest:tt)*) => {{
671        $c.begin_eure_block();
672        $crate::eure!(@stmt $c; $($inner)+);
673        $c.end_eure_block().unwrap();
674        $c.end_scope($scope).unwrap();
675        $c.end_binding_block().unwrap();
676        $crate::eure!(@section_bindings $c $section_scope; $($rest)*);
677    }};
678
679    // ========================================================================
680    // Path segment parsing (@path)
681    //
682    // Parse one path segment at a time using TT muncher pattern.
683    // Each segment type navigates to a child node and then delegates to @after_path.
684    //
685    // Supported segment types:
686    // - `ident`: Regular identifier (a, user, field_name)
687    // - `%ext`: Extension namespace ($variant becomes %variant in macro)
688    // - `#N`: Tuple index (#0, #1, #2)
689    // - `(a, b)`: Tuple key for composite map keys
690    // - `"key"`: String literal for non-identifier keys (e.g., "min-length")
691    // ========================================================================
692
693    // Segment: identifier (e.g., `field`, `user`, `name`)
694    (@path $c:ident $scope:ident; $seg:ident $($rest:tt)*) => {{
695        $c.navigate($crate::path::PathSegment::Ident(
696            $crate::identifier::Identifier::new_unchecked(stringify!($seg))
697        )).unwrap();
698        $crate::eure!(@after_path $c $scope; $($rest)*);
699    }};
700
701    // Segment: extension with identifier (e.g., `%variant`, `%schema`)
702    // Note: Uses % instead of $ because $ is reserved in macros
703    (@path $c:ident $scope:ident; % $ext:ident $($rest:tt)*) => {{
704        $c.navigate($crate::path::PathSegment::Extension(
705            $crate::identifier::Identifier::new_unchecked(stringify!($ext))
706        )).unwrap();
707        $crate::eure!(@after_path $c $scope; $($rest)*);
708    }};
709
710    // Segment: extension with string literal (e.g., `%"variant-repr"`)
711    // Used for hyphenated extension names that aren't valid Rust identifiers
712    (@path $c:ident $scope:ident; % $ext:literal $($rest:tt)*) => {{
713        $c.navigate($crate::path::PathSegment::Extension(
714            $ext.parse().unwrap()
715        )).unwrap();
716        $crate::eure!(@after_path $c $scope; $($rest)*);
717    }};
718
719    // Segment: tuple index (e.g., `#0`, `#1`, `#255`)
720    (@path $c:ident $scope:ident; # $idx:literal $($rest:tt)*) => {{
721        $c.navigate($crate::path::PathSegment::TupleIndex($idx)).unwrap();
722        $crate::eure!(@after_path $c $scope; $($rest)*);
723    }};
724
725    // Segment: tuple key (e.g., `(1, "key")`, `(true, 2)`)
726    // Used as composite map keys
727    (@path $c:ident $scope:ident; ($($tuple:tt)*) $($rest:tt)*) => {{
728        let key = $crate::eure!(@build_tuple_key; $($tuple)*);
729        $c.navigate($crate::path::PathSegment::Value(key)).unwrap();
730        $crate::eure!(@after_path $c $scope; $($rest)*);
731    }};
732
733    // Segment: string literal key (e.g., `"min-length"`, `"Content-Type"`)
734    // Used for keys that aren't valid identifiers
735    (@path $c:ident $scope:ident; $key:literal $($rest:tt)*) => {{
736        $c.navigate($crate::path::PathSegment::Value($key.into())).unwrap();
737        $crate::eure!(@after_path $c $scope; $($rest)*);
738    }};
739
740    // ========================================================================
741    // Build tuple key (@build_tuple_key)
742    //
743    // Constructs an ObjectKey::Tuple from comma-separated values.
744    // Used for composite map keys like (1, "key").
745    // ========================================================================
746
747    // Empty tuple key: ()
748    (@build_tuple_key;) => {{
749        $crate::value::ObjectKey::Tuple($crate::value::Tuple(Default::default()))
750    }};
751
752    // Non-empty tuple key: (a, b, c) - each item converted via Into<ObjectKey>
753    (@build_tuple_key; $($item:expr),+ $(,)?) => {{
754        $crate::value::ObjectKey::Tuple($crate::value::Tuple::from_iter(
755            [$(<_ as Into<$crate::value::ObjectKey>>::into($item)),+]
756        ))
757    }};
758
759    // ========================================================================
760    // After path segment (@after_path)
761    //
762    // After parsing a segment, check if there's an optional array marker [].
763    // If found, handle it; otherwise proceed to terminal handling.
764    // ========================================================================
765
766    // Has array marker - delegate to @array_marker
767    (@after_path $c:ident $scope:ident; [$($arr:tt)*] $($rest:tt)*) => {{
768        $crate::eure!(@array_marker $c $scope [$($arr)*]; $($rest)*);
769    }};
770
771    // No array marker - proceed to terminal handling
772    (@after_path $c:ident $scope:ident; $($rest:tt)*) => {{
773        $crate::eure!(@terminal $c $scope; $($rest)*);
774    }};
775
776    // ========================================================================
777    // Array marker handling (@array_marker)
778    //
779    // Process the content of array markers:
780    // - `[]`: Push to array (creates new element)
781    // - `[N]`: Access array at index N
782    // ========================================================================
783
784    // Empty array marker: push operation (creates new element at end)
785    (@array_marker $c:ident $scope:ident []; $($rest:tt)*) => {{
786        $c.navigate($crate::path::PathSegment::ArrayIndex(None)).unwrap();
787        $crate::eure!(@terminal $c $scope; $($rest)*);
788    }};
789
790    // Array marker with index: access at specific position
791    (@array_marker $c:ident $scope:ident [$idx:literal]; $($rest:tt)*) => {{
792        $c.navigate($crate::path::PathSegment::ArrayIndex(Some($idx))).unwrap();
793        $crate::eure!(@terminal $c $scope; $($rest)*);
794    }};
795
796    // ========================================================================
797    // Terminal handling (@terminal)
798    //
799    // Handle what comes after the path:
800    // - `.more.path`: Continue parsing more segments
801    // - `= value`: Bind a value (single token tree)
802    // - `= !`: Leave as hole (explicit placeholder)
803    // - `= @code(...)`: Bind inline code
804    // - `= @block(...)`: Bind block code
805    // - `{ ... }`: Block syntax (grouped bindings)
806    // - `{}`: Empty block (creates empty map)
807    //
808    // Uses $v:tt for values to enable comma-free syntax.
809    // Note: @code and @block must be handled specially as they span multiple tokens.
810    // ========================================================================
811
812    // Continuation: more path segments after dot
813    (@terminal $c:ident $scope:ident; . $($rest:tt)+) => {{
814        $crate::eure!(@path $c $scope; $($rest)+);
815    }};
816
817    // Terminal: hole (!) - explicit unbound placeholder
818    (@terminal $c:ident $scope:ident; = ! $($rest:tt)*) => {{
819        $c.bind_hole(None).unwrap();
820        $c.end_scope($scope).unwrap();
821        $c.end_binding_value().unwrap();
822        $crate::eure!(@stmt $c; $($rest)*);
823    }};
824
825    // Terminal: negative literal (e.g., path = -42)
826    (@terminal $c:ident $scope:ident; = - $v:literal $($rest:tt)*) => {{
827        $c.bind_from(-$v).unwrap();
828        $c.end_scope($scope).unwrap();
829        $c.end_binding_value().unwrap();
830        $crate::eure!(@stmt $c; $($rest)*);
831    }};
832
833    // Terminal: inline code with implicit language - @code("content")
834    (@terminal $c:ident $scope:ident; = @ code ($content:literal) $($rest:tt)*) => {{
835        $c.bind_from($crate::text::Text::inline_implicit($content)).unwrap();
836        $c.end_scope($scope).unwrap();
837        $c.end_binding_value().unwrap();
838        $crate::eure!(@stmt $c; $($rest)*);
839    }};
840
841    // Terminal: inline code with explicit language - @code("lang", "content")
842    (@terminal $c:ident $scope:ident; = @ code ($lang:literal, $content:literal) $($rest:tt)*) => {{
843        $c.bind_from($crate::text::Text::inline($content, $lang)).unwrap();
844        $c.end_scope($scope).unwrap();
845        $c.end_binding_value().unwrap();
846        $crate::eure!(@stmt $c; $($rest)*);
847    }};
848
849    // Terminal: block code with implicit language - @block("content")
850    (@terminal $c:ident $scope:ident; = @ block ($content:literal) $($rest:tt)*) => {{
851        $c.bind_from($crate::text::Text::block_implicit($content)).unwrap();
852        $c.end_scope($scope).unwrap();
853        $c.end_binding_value().unwrap();
854        $crate::eure!(@stmt $c; $($rest)*);
855    }};
856
857    // Terminal: block code with explicit language - @block("lang", "content")
858    (@terminal $c:ident $scope:ident; = @ block ($lang:literal, $content:literal) $($rest:tt)*) => {{
859        $c.bind_from($crate::text::Text::block($content, $lang)).unwrap();
860        $c.end_scope($scope).unwrap();
861        $c.end_binding_value().unwrap();
862        $crate::eure!(@stmt $c; $($rest)*);
863    }};
864
865    // Terminal: empty array
866    (@terminal $c:ident $scope:ident; = [] $($rest:tt)*) => {{
867        $c.bind_empty_array().unwrap();
868        $c.end_scope($scope).unwrap();
869        $c.end_binding_value().unwrap();
870        $crate::eure!(@stmt $c; $($rest)*);
871    }};
872
873    // Terminal: array with items
874    (@terminal $c:ident $scope:ident; = [$($items:tt)+] $($rest:tt)*) => {{
875        $c.bind_empty_array().unwrap();
876        $crate::eure!(@array_items $c; $($items)+);
877        $c.end_scope($scope).unwrap();
878        $c.end_binding_value().unwrap();
879        $crate::eure!(@stmt $c; $($rest)*);
880    }};
881
882    // Terminal: empty tuple
883    (@terminal $c:ident $scope:ident; = () $($rest:tt)*) => {{
884        $c.bind_empty_tuple().unwrap();
885        $c.end_scope($scope).unwrap();
886        $c.end_binding_value().unwrap();
887        $crate::eure!(@stmt $c; $($rest)*);
888    }};
889
890    // Terminal: tuple with items
891    (@terminal $c:ident $scope:ident; = ($($items:tt)+) $($rest:tt)*) => {{
892        $c.bind_empty_tuple().unwrap();
893        $crate::eure!(@tuple_items $c 0; $($items)+);
894        $c.end_scope($scope).unwrap();
895        $c.end_binding_value().unwrap();
896        $crate::eure!(@stmt $c; $($rest)*);
897    }};
898
899    // Terminal: object literal with map syntax { k => v, ... }
900    (@terminal $c:ident $scope:ident; = { $key:tt => $($inner:tt)+ } $($rest:tt)*) => {{
901        $c.bind_empty_map().unwrap();
902        $crate::eure!(@object_items $c; $key => $($inner)+);
903        $c.end_scope($scope).unwrap();
904        $c.end_binding_value().unwrap();
905        $crate::eure!(@stmt $c; $($rest)*);
906    }};
907
908    // Terminal: value assignment (single token tree for primitives)
909    (@terminal $c:ident $scope:ident; = $v:tt $($rest:tt)*) => {{
910        $c.bind_from($crate::eure!(@value_tt $v)).unwrap();
911        $c.end_scope($scope).unwrap();
912        $c.end_binding_value().unwrap();
913        $crate::eure!(@stmt $c; $($rest)*);
914    }};
915
916    // Terminal: empty block -> empty map
917    (@terminal $c:ident $scope:ident; {} $($rest:tt)*) => {{
918        $c.begin_eure_block();
919        $c.bind_empty_map().unwrap();
920        $c.end_eure_block().unwrap();
921        $c.end_scope($scope).unwrap();
922        $c.end_binding_block().unwrap();
923        $crate::eure!(@stmt $c; $($rest)*);
924    }};
925
926    // Terminal: non-empty block
927    (@terminal $c:ident $scope:ident; { $($inner:tt)+ } $($rest:tt)*) => {{
928        $c.begin_eure_block();
929        $crate::eure!(@stmt $c; $($inner)+);
930        $c.end_eure_block().unwrap();
931        $c.end_scope($scope).unwrap();
932        $c.end_binding_block().unwrap();
933        $crate::eure!(@stmt $c; $($rest)*);
934    }};
935}
936
937/// A macro for building [`SourceDocument`]s with source layout tracking.
938///
939/// This macro is similar to [`eure!`] but creates a [`SourceDocument`] which
940/// preserves source structure information for round-trip formatting.
941///
942/// # Example
943///
944/// ```
945/// use eure_document::eure_source;
946///
947/// let source_doc = eure_source!({
948///     name = "Alice"
949///     age = 30
950/// });
951///
952/// // The SourceDocument contains both the document and source structure
953/// let doc = source_doc.document();
954/// let root_source = source_doc.root_source();
955/// ```
956#[macro_export]
957macro_rules! eure_source {
958    // Empty source document
959    ({}) => {{
960        $crate::source::SourceDocument::empty()
961    }};
962
963    // Source document with body
964    ({ $($body:tt)* }) => {{
965        #[allow(unused_mut)]
966        let mut c = $crate::document::source_constructor::SourceConstructor::new();
967        $crate::eure!(c; { $($body)* });
968        c.finish()
969    }};
970}
971
972#[cfg(test)]
973mod tests {
974    use crate::document::EureDocument;
975    use alloc::vec;
976
977    #[test]
978    fn test_eure_empty() {
979        let doc = eure!({});
980        assert_eq!(doc, EureDocument::new_empty());
981    }
982
983    #[test]
984    fn test_eure_simple_assignment() {
985        let doc = eure!({ name = "Alice" });
986
987        // Verify the structure
988        let root_id = doc.get_root_id();
989        let root = doc.node(root_id);
990        let name_node_id = root.as_map().unwrap().get_node_id(&"name".into()).unwrap();
991        let name_node = doc.node(name_node_id);
992        let prim = name_node.as_primitive().unwrap();
993        assert_eq!(prim.as_str(), Some("Alice"));
994    }
995
996    #[test]
997    fn test_eure_nested_path() {
998        let doc = eure!({
999            user.name = "Bob"
1000            user.age = 30
1001        });
1002
1003        // Verify structure: root.user.name = "Bob", root.user.age = 30
1004        let root_id = doc.get_root_id();
1005        let root = doc.node(root_id);
1006        let user_id = root.as_map().unwrap().get_node_id(&"user".into()).unwrap();
1007        let user = doc.node(user_id);
1008        let name_id = user.as_map().unwrap().get_node_id(&"name".into()).unwrap();
1009        let name = doc.node(name_id);
1010        assert_eq!(name.as_primitive().unwrap().as_str(), Some("Bob"));
1011
1012        let age_id = user.as_map().unwrap().get_node_id(&"age".into()).unwrap();
1013        let age = doc.node(age_id);
1014        assert!(matches!(
1015            age.as_primitive(),
1016            Some(crate::value::PrimitiveValue::Integer(_))
1017        ));
1018    }
1019
1020    #[test]
1021    fn test_eure_block() {
1022        let doc = eure!({
1023            user {
1024                name = "Charlie"
1025                active = true
1026            }
1027        });
1028
1029        let root_id = doc.get_root_id();
1030        let root = doc.node(root_id);
1031        let user_id = root.as_map().unwrap().get_node_id(&"user".into()).unwrap();
1032        let user = doc.node(user_id);
1033        let name_id = user.as_map().unwrap().get_node_id(&"name".into()).unwrap();
1034        let name = doc.node(name_id);
1035        assert_eq!(name.as_primitive().unwrap().as_str(), Some("Charlie"));
1036    }
1037
1038    #[test]
1039    fn test_eure_extension() {
1040        let doc = eure!({
1041            field.%variant = @code("text")
1042        });
1043
1044        let root_id = doc.get_root_id();
1045        let root = doc.node(root_id);
1046        let field_id = root.as_map().unwrap().get_node_id(&"field".into()).unwrap();
1047        let field = doc.node(field_id);
1048
1049        // Check extension
1050        let variant_id = field.get_extension(&"variant".parse().unwrap()).unwrap();
1051        let variant = doc.node(variant_id);
1052        let text = variant.as_primitive().unwrap().as_text().unwrap();
1053        assert_eq!(text.as_str(), "text");
1054    }
1055
1056    #[test]
1057    fn test_eure_extension_with_child() {
1058        // Test pattern: a.%ext.b = value
1059        let doc = eure!({
1060            field.%variant.name = @code("text")
1061            field.%variant.min_length = 3
1062        });
1063
1064        let root_id = doc.get_root_id();
1065        let root = doc.node(root_id);
1066        let field_id = root.as_map().unwrap().get_node_id(&"field".into()).unwrap();
1067        let field = doc.node(field_id);
1068
1069        // Check extension
1070        let variant_id = field.get_extension(&"variant".parse().unwrap()).unwrap();
1071        let variant = doc.node(variant_id);
1072
1073        // Check child of extension
1074        let name_id = variant
1075            .as_map()
1076            .unwrap()
1077            .get_node_id(&"name".into())
1078            .unwrap();
1079        let name = doc.node(name_id);
1080        let text = name.as_primitive().unwrap().as_text().unwrap();
1081        assert_eq!(text.as_str(), "text");
1082
1083        let min_length_id = variant
1084            .as_map()
1085            .unwrap()
1086            .get_node_id(&"min_length".into())
1087            .unwrap();
1088        let min_length = doc.node(min_length_id);
1089        assert!(matches!(
1090            min_length.as_primitive(),
1091            Some(crate::value::PrimitiveValue::Integer(_))
1092        ));
1093    }
1094
1095    #[test]
1096    fn test_eure_array() {
1097        let doc = eure!({ tags = ["a", "b", "c"] });
1098
1099        let root_id = doc.get_root_id();
1100        let root = doc.node(root_id);
1101        let tags_id = root.as_map().unwrap().get_node_id(&"tags".into()).unwrap();
1102        let tags = doc.node(tags_id);
1103        let array = tags.as_array().unwrap();
1104        assert_eq!(array.len(), 3);
1105    }
1106
1107    #[test]
1108    fn test_eure_tuple() {
1109        let doc = eure!({ point = (1.5, 2.5) });
1110
1111        let root_id = doc.get_root_id();
1112        let root = doc.node(root_id);
1113        let point_id = root.as_map().unwrap().get_node_id(&"point".into()).unwrap();
1114        let point = doc.node(point_id);
1115        let tuple = point.as_tuple().unwrap();
1116        assert_eq!(tuple.len(), 2);
1117    }
1118
1119    #[test]
1120    fn test_eure_multiple_assignments() {
1121        let doc = eure!({
1122            a = 1
1123            b = 2
1124            c = 3
1125        });
1126
1127        let root_id = doc.get_root_id();
1128        let root = doc.node(root_id);
1129        let map = root.as_map().unwrap();
1130        assert_eq!(map.len(), 3);
1131    }
1132
1133    #[test]
1134    fn test_eure_complex() {
1135        // A more complex example combining features
1136        let doc = eure!({
1137            schema {
1138                field.%variant = @code("text")
1139                field.min_length = 3
1140                field.max_length = 20
1141            }
1142            tags = ["required"]
1143        });
1144
1145        let root_id = doc.get_root_id();
1146        let root = doc.node(root_id);
1147
1148        // Check schema exists
1149        let schema_id = root
1150            .as_map()
1151            .unwrap()
1152            .get_node_id(&"schema".into())
1153            .unwrap();
1154        let schema = doc.node(schema_id);
1155
1156        // Check field exists with extension
1157        let field_id = schema
1158            .as_map()
1159            .unwrap()
1160            .get_node_id(&"field".into())
1161            .unwrap();
1162        let field = doc.node(field_id);
1163        assert!(field.get_extension(&"variant".parse().unwrap()).is_some());
1164
1165        // Check tags array
1166        let tags_id = root.as_map().unwrap().get_node_id(&"tags".into()).unwrap();
1167        let tags = doc.node(tags_id);
1168        assert_eq!(tags.as_array().unwrap().len(), 1);
1169    }
1170
1171    #[test]
1172    fn test_eure_array_push() {
1173        // Test array push syntax: items[] = value
1174        let doc = eure!({
1175            items[] = 1
1176            items[] = 2
1177            items[] = 3
1178        });
1179
1180        let root_id = doc.get_root_id();
1181        let root = doc.node(root_id);
1182        let items_id = root.as_map().unwrap().get_node_id(&"items".into()).unwrap();
1183        let items = doc.node(items_id);
1184        let array = items.as_array().unwrap();
1185        assert_eq!(array.len(), 3);
1186    }
1187
1188    #[test]
1189    fn test_eure_array_push_with_child() {
1190        // Test: items[].name = value (array push then navigate to child)
1191        let doc = eure!({
1192            items[].name = "first"
1193            items[].name = "second"
1194        });
1195
1196        let root_id = doc.get_root_id();
1197        let root = doc.node(root_id);
1198        let items_id = root.as_map().unwrap().get_node_id(&"items".into()).unwrap();
1199        let items = doc.node(items_id);
1200        let array = items.as_array().unwrap();
1201        assert_eq!(array.len(), 2);
1202
1203        // Check first element has name = "first"
1204        let first_id = array.get(0).unwrap();
1205        let first = doc.node(first_id);
1206        let name_id = first.as_map().unwrap().get_node_id(&"name".into()).unwrap();
1207        let name = doc.node(name_id);
1208        assert_eq!(name.as_primitive().unwrap().as_str(), Some("first"));
1209    }
1210
1211    #[test]
1212    fn test_eure_tuple_index() {
1213        // Test tuple index syntax: point.#0, point.#1
1214        let doc = eure!({
1215            point.#0 = 1.5
1216            point.#1 = 2.5
1217        });
1218
1219        let root_id = doc.get_root_id();
1220        let root = doc.node(root_id);
1221        let point_id = root.as_map().unwrap().get_node_id(&"point".into()).unwrap();
1222        let point = doc.node(point_id);
1223        let tuple = point.as_tuple().unwrap();
1224        assert_eq!(tuple.len(), 2);
1225    }
1226
1227    #[test]
1228    fn test_eure_mixed_path_extension_array() {
1229        // Test: a.%ext[].b = value
1230        let doc = eure!({
1231            field.%items[].name = "item1"
1232            field.%items[].name = "item2"
1233        });
1234
1235        let root_id = doc.get_root_id();
1236        let root = doc.node(root_id);
1237        let field_id = root.as_map().unwrap().get_node_id(&"field".into()).unwrap();
1238        let field = doc.node(field_id);
1239
1240        // Get extension
1241        let items_id = field.get_extension(&"items".parse().unwrap()).unwrap();
1242        let items = doc.node(items_id);
1243        let array = items.as_array().unwrap();
1244        assert_eq!(array.len(), 2);
1245    }
1246
1247    #[test]
1248    fn test_eure_mixed_path_array_extension() {
1249        // Test: items[].%variant = value
1250        let doc = eure!({
1251            items[].%variant = @code("text")
1252            items[].%variant = @code("number")
1253        });
1254
1255        let root_id = doc.get_root_id();
1256        let root = doc.node(root_id);
1257        let items_id = root.as_map().unwrap().get_node_id(&"items".into()).unwrap();
1258        let items = doc.node(items_id);
1259        let array = items.as_array().unwrap();
1260        assert_eq!(array.len(), 2);
1261
1262        // Check first element has extension
1263        let first_id = array.get(0).unwrap();
1264        let first = doc.node(first_id);
1265        let variant_id = first.get_extension(&"variant".parse().unwrap()).unwrap();
1266        let variant = doc.node(variant_id);
1267        assert_eq!(
1268            variant.as_primitive().unwrap().as_text().unwrap().as_str(),
1269            "text"
1270        );
1271    }
1272
1273    #[test]
1274    fn test_eure_tuple_key() {
1275        use crate::value::{ObjectKey, Tuple};
1276
1277        // Test tuple key: map.(1, "a") = value
1278        let doc = eure!({
1279            map.(1, "key") = "value1"
1280            map.(2, "key") = "value2"
1281        });
1282
1283        let root_id = doc.get_root_id();
1284        let root = doc.node(root_id);
1285        let map_id = root.as_map().unwrap().get_node_id(&"map".into()).unwrap();
1286        let map_node = doc.node(map_id);
1287        let map = map_node.as_map().unwrap();
1288        assert_eq!(map.len(), 2);
1289
1290        // Check key (1, "key") exists
1291        let tuple_key = ObjectKey::Tuple(Tuple(alloc::vec![1.into(), "key".into()]));
1292        let value_id = map.get_node_id(&tuple_key).unwrap();
1293        let value = doc.node(value_id);
1294        assert_eq!(value.as_primitive().unwrap().as_str(), Some("value1"));
1295    }
1296
1297    #[test]
1298    fn test_eure_tuple_key_with_bool() {
1299        use crate::value::{ObjectKey, Tuple};
1300
1301        // Test tuple key with bool: map.(true, 1) = value
1302        let doc = eure!({
1303            map.(true, 1) = "yes"
1304            map.(false, 1) = "no"
1305        });
1306
1307        let root_id = doc.get_root_id();
1308        let root = doc.node(root_id);
1309        let map_id = root.as_map().unwrap().get_node_id(&"map".into()).unwrap();
1310        let map_node = doc.node(map_id);
1311        let map = map_node.as_map().unwrap();
1312        assert_eq!(map.len(), 2);
1313
1314        // Check key (true, 1) exists
1315        let tuple_key = ObjectKey::Tuple(Tuple(alloc::vec![true.into(), 1.into()]));
1316        let value_id = map.get_node_id(&tuple_key).unwrap();
1317        let value = doc.node(value_id);
1318        assert_eq!(value.as_primitive().unwrap().as_str(), Some("yes"));
1319    }
1320
1321    #[test]
1322    fn test_eure_tuple_key_with_child() {
1323        use crate::value::{ObjectKey, Tuple};
1324
1325        // Test tuple key with child path: map.(1, 2).name = value
1326        let doc = eure!({
1327            map.(1, 2).name = "point_a"
1328            map.(1, 2).value = 42
1329        });
1330
1331        let root_id = doc.get_root_id();
1332        let root = doc.node(root_id);
1333        let map_id = root.as_map().unwrap().get_node_id(&"map".into()).unwrap();
1334        let map_node = doc.node(map_id);
1335        let map = map_node.as_map().unwrap();
1336
1337        // Check key (1, 2) has children
1338        let tuple_key = ObjectKey::Tuple(Tuple(alloc::vec![1.into(), 2.into()]));
1339        let entry_id = map.get_node_id(&tuple_key).unwrap();
1340        let entry = doc.node(entry_id);
1341        let entry_map = entry.as_map().unwrap();
1342
1343        let name_id = entry_map.get_node_id(&"name".into()).unwrap();
1344        let name = doc.node(name_id);
1345        assert_eq!(name.as_primitive().unwrap().as_str(), Some("point_a"));
1346    }
1347
1348    #[test]
1349    fn test_eure_string_key() {
1350        // Test string literal key for hyphenated identifiers: "min-length" = 3
1351        let doc = eure!({
1352            field."min-length" = 3
1353            field."max-length" = 20
1354        });
1355
1356        let root_id = doc.get_root_id();
1357        let root = doc.node(root_id);
1358        let field_id = root.as_map().unwrap().get_node_id(&"field".into()).unwrap();
1359        let field = doc.node(field_id);
1360        let field_map = field.as_map().unwrap();
1361
1362        // Check "min-length" key exists
1363        let min_id = field_map.get_node_id(&"min-length".into()).unwrap();
1364        let min_node = doc.node(min_id);
1365        assert!(matches!(
1366            min_node.as_primitive(),
1367            Some(crate::value::PrimitiveValue::Integer(_))
1368        ));
1369    }
1370
1371    #[test]
1372    fn test_eure_object_literal() {
1373        // Test object literal with => syntax
1374        let doc = eure!({
1375            variants.click = { "x" => 1.0, "y" => 2.0 }
1376        });
1377
1378        let root_id = doc.get_root_id();
1379        let root = doc.node(root_id);
1380        let variants_id = root
1381            .as_map()
1382            .unwrap()
1383            .get_node_id(&"variants".into())
1384            .unwrap();
1385        let variants = doc.node(variants_id);
1386        let click_id = variants
1387            .as_map()
1388            .unwrap()
1389            .get_node_id(&"click".into())
1390            .unwrap();
1391        let click = doc.node(click_id);
1392        let click_map = click.as_map().unwrap();
1393
1394        assert_eq!(click_map.len(), 2);
1395        assert!(click_map.get(&"x".into()).is_some());
1396        assert!(click_map.get(&"y".into()).is_some());
1397    }
1398
1399    #[test]
1400    fn test_eure_object_literal_with_string() {
1401        // Test object literal for schema-like patterns
1402        let doc = eure!({
1403            schema.variants.success = { "data" => "any" }
1404        });
1405
1406        let root_id = doc.get_root_id();
1407        let root = doc.node(root_id);
1408        let schema_id = root
1409            .as_map()
1410            .unwrap()
1411            .get_node_id(&"schema".into())
1412            .unwrap();
1413        let schema = doc.node(schema_id);
1414        let variants_id = schema
1415            .as_map()
1416            .unwrap()
1417            .get_node_id(&"variants".into())
1418            .unwrap();
1419        let variants = doc.node(variants_id);
1420        let success_id = variants
1421            .as_map()
1422            .unwrap()
1423            .get_node_id(&"success".into())
1424            .unwrap();
1425        let success = doc.node(success_id);
1426        let success_map = success.as_map().unwrap();
1427
1428        let data_id = success_map.get_node_id(&"data".into()).unwrap();
1429        let data = doc.node(data_id);
1430        assert_eq!(data.as_primitive().unwrap().as_str(), Some("any"));
1431    }
1432
1433    #[test]
1434    fn test_eure_value_binding() {
1435        // Test value binding at root: = value
1436        let doc = eure!({
1437            = @code("hello")
1438        });
1439
1440        let root_id = doc.get_root_id();
1441        let root = doc.node(root_id);
1442        let text = root.as_primitive().unwrap().as_text().unwrap();
1443        assert_eq!(text.as_str(), "hello");
1444    }
1445
1446    #[test]
1447    fn test_eure_value_binding_with_extension() {
1448        // Test value binding with extension: = value, %ext = value
1449        let doc = eure!({
1450            = @code("any")
1451            %variant = "literal"
1452        });
1453
1454        let root_id = doc.get_root_id();
1455        let root = doc.node(root_id);
1456
1457        // Check value
1458        let text = root.as_primitive().unwrap().as_text().unwrap();
1459        assert_eq!(text.as_str(), "any");
1460
1461        // Check extension
1462        let variant_id = root.get_extension(&"variant".parse().unwrap()).unwrap();
1463        let variant = doc.node(variant_id);
1464        assert_eq!(variant.as_primitive().unwrap().as_str(), Some("literal"));
1465    }
1466
1467    #[test]
1468    fn test_eure_empty_block() {
1469        // Empty block should create an empty map, not a Hole
1470        let doc = eure!({ config {} });
1471
1472        let root_id = doc.get_root_id();
1473        let root = doc.node(root_id);
1474        let config_id = root
1475            .as_map()
1476            .unwrap()
1477            .get_node_id(&"config".into())
1478            .unwrap();
1479        let config = doc.node(config_id);
1480
1481        // Should be an empty map, not Hole
1482        let map = config
1483            .as_map()
1484            .expect("Empty block should create an empty map");
1485        assert!(map.is_empty());
1486    }
1487
1488    // ========================================================================
1489    // Tests for new features: null, !, @code, @block
1490    // ========================================================================
1491
1492    #[test]
1493    fn test_eure_null_literal() {
1494        // Test null literal at field level
1495        let doc = eure!({ optional = null });
1496
1497        let root_id = doc.get_root_id();
1498        let root = doc.node(root_id);
1499        let opt_id = root
1500            .as_map()
1501            .unwrap()
1502            .get_node_id(&"optional".into())
1503            .unwrap();
1504        let opt = doc.node(opt_id);
1505        assert!(matches!(
1506            opt.as_primitive(),
1507            Some(crate::value::PrimitiveValue::Null)
1508        ));
1509    }
1510
1511    #[test]
1512    fn test_eure_null_root() {
1513        // Test null literal at root level
1514        let doc = eure!({
1515            = null
1516        });
1517
1518        let root_id = doc.get_root_id();
1519        let root = doc.node(root_id);
1520        assert!(matches!(
1521            root.as_primitive(),
1522            Some(crate::value::PrimitiveValue::Null)
1523        ));
1524    }
1525
1526    #[test]
1527    fn test_eure_hole_literal() {
1528        use crate::document::node::NodeValue;
1529
1530        // Test hole (!) literal at field level
1531        let doc = eure!({
1532            placeholder = !
1533        });
1534
1535        let root_id = doc.get_root_id();
1536        let root = doc.node(root_id);
1537        let placeholder_id = root
1538            .as_map()
1539            .unwrap()
1540            .get_node_id(&"placeholder".into())
1541            .unwrap();
1542        let placeholder = doc.node(placeholder_id);
1543        assert_eq!(placeholder.content, NodeValue::Hole(None));
1544    }
1545
1546    #[test]
1547    fn test_eure_hole_root() {
1548        use crate::document::node::NodeValue;
1549
1550        // Test hole at root level - explicit `= !` should preserve the Hole
1551        let doc = eure!({
1552            = !
1553        });
1554
1555        let root_id = doc.get_root_id();
1556        let root = doc.node(root_id);
1557        assert_eq!(root.content, NodeValue::Hole(None));
1558    }
1559
1560    #[test]
1561    fn test_eure_code_inline_implicit() {
1562        // Test @code("content") - inline code with implicit language
1563        let doc = eure!({
1564            snippet = @code("let x = 1")
1565        });
1566
1567        let root_id = doc.get_root_id();
1568        let root = doc.node(root_id);
1569        let snippet_id = root
1570            .as_map()
1571            .unwrap()
1572            .get_node_id(&"snippet".into())
1573            .unwrap();
1574        let snippet = doc.node(snippet_id);
1575        let text = snippet.as_primitive().unwrap().as_text().unwrap();
1576        assert_eq!(text.as_str(), "let x = 1");
1577        assert!(text.language.is_implicit());
1578    }
1579
1580    #[test]
1581    fn test_eure_code_inline_with_language() {
1582        // Test @code("lang", "content") - inline code with explicit language
1583        let doc = eure!({
1584            query = @code("sql", "SELECT * FROM users")
1585        });
1586
1587        let root_id = doc.get_root_id();
1588        let root = doc.node(root_id);
1589        let query_id = root.as_map().unwrap().get_node_id(&"query".into()).unwrap();
1590        let query = doc.node(query_id);
1591        let text = query.as_primitive().unwrap().as_text().unwrap();
1592        assert_eq!(text.as_str(), "SELECT * FROM users");
1593        assert_eq!(text.language.as_str(), Some("sql"));
1594    }
1595
1596    #[test]
1597    fn test_eure_block_implicit() {
1598        // Test @block("content") - block code with implicit language
1599        let doc = eure!({
1600            script = @block("fn main() {}")
1601        });
1602
1603        let root_id = doc.get_root_id();
1604        let root = doc.node(root_id);
1605        let script_id = root
1606            .as_map()
1607            .unwrap()
1608            .get_node_id(&"script".into())
1609            .unwrap();
1610        let script = doc.node(script_id);
1611        let text = script.as_primitive().unwrap().as_text().unwrap();
1612        // block adds trailing newline
1613        assert_eq!(text.as_str(), "fn main() {}\n");
1614        assert!(text.language.is_implicit());
1615    }
1616
1617    #[test]
1618    fn test_eure_block_with_language() {
1619        // Test @block("lang", "content") - block code with explicit language
1620        let doc = eure!({
1621            code = @block("rust", "fn main() {\n    println!(\"Hello\");\n}")
1622        });
1623
1624        let root_id = doc.get_root_id();
1625        let root = doc.node(root_id);
1626        let code_id = root.as_map().unwrap().get_node_id(&"code".into()).unwrap();
1627        let code = doc.node(code_id);
1628        let text = code.as_primitive().unwrap().as_text().unwrap();
1629        assert_eq!(text.language.as_str(), Some("rust"));
1630        assert!(text.as_str().contains("println!"));
1631    }
1632
1633    #[test]
1634    fn test_eure_code_at_root() {
1635        // Test @code at root level
1636        let doc = eure!({
1637            = @code("hello")
1638        });
1639
1640        let root_id = doc.get_root_id();
1641        let root = doc.node(root_id);
1642        let text = root.as_primitive().unwrap().as_text().unwrap();
1643        assert_eq!(text.as_str(), "hello");
1644    }
1645
1646    #[test]
1647    fn test_eure_code_with_language_at_root() {
1648        // Test @code("lang", "content") at root level
1649        let doc = eure!({
1650            = @code("sql", "SELECT 1")
1651        });
1652
1653        let root_id = doc.get_root_id();
1654        let root = doc.node(root_id);
1655        let text = root.as_primitive().unwrap().as_text().unwrap();
1656        assert_eq!(text.as_str(), "SELECT 1");
1657        assert_eq!(text.language.as_str(), Some("sql"));
1658    }
1659
1660    #[test]
1661    fn test_eure_block_at_root() {
1662        // Test @block("content") at root level
1663        let doc = eure!({
1664            = @block("fn main() {}")
1665        });
1666
1667        let root_id = doc.get_root_id();
1668        let root = doc.node(root_id);
1669        let text = root.as_primitive().unwrap().as_text().unwrap();
1670        assert_eq!(text.as_str(), "fn main() {}\n");
1671        assert!(text.language.is_implicit());
1672    }
1673
1674    #[test]
1675    fn test_eure_block_with_language_at_root() {
1676        // Test @block("lang", "content") at root level
1677        let doc = eure!({
1678            = @block("rust", "fn main() {}")
1679        });
1680
1681        let root_id = doc.get_root_id();
1682        let root = doc.node(root_id);
1683        let text = root.as_primitive().unwrap().as_text().unwrap();
1684        assert_eq!(text.as_str(), "fn main() {}\n");
1685        assert_eq!(text.language.as_str(), Some("rust"));
1686    }
1687
1688    // ========================================================================
1689    // Tests for edge cases and missing coverage
1690    // ========================================================================
1691
1692    #[test]
1693    fn test_eure_array_specific_index() {
1694        // Test array with specific index: items[0] = value, items[1] = value
1695        let doc = eure!({
1696            items[0] = "first"
1697            items[1] = "second"
1698        });
1699
1700        let root_id = doc.get_root_id();
1701        let root = doc.node(root_id);
1702        let items_id = root.as_map().unwrap().get_node_id(&"items".into()).unwrap();
1703        let items = doc.node(items_id);
1704        let array = items.as_array().unwrap();
1705        assert_eq!(array.len(), 2);
1706
1707        // Check values
1708        let first_id = array.get(0).unwrap();
1709        let first = doc.node(first_id);
1710        assert_eq!(first.as_primitive().unwrap().as_str(), Some("first"));
1711
1712        let second_id = array.get(1).unwrap();
1713        let second = doc.node(second_id);
1714        assert_eq!(second.as_primitive().unwrap().as_str(), Some("second"));
1715    }
1716
1717    #[test]
1718    fn test_eure_array_index_with_child() {
1719        // Test array index with child path: items[0].name = value
1720        let doc = eure!({
1721            items[0].name = "first"
1722            items[0].value = 1
1723            items[1].name = "second"
1724        });
1725
1726        let root_id = doc.get_root_id();
1727        let root = doc.node(root_id);
1728        let items_id = root.as_map().unwrap().get_node_id(&"items".into()).unwrap();
1729        let items = doc.node(items_id);
1730        let array = items.as_array().unwrap();
1731        assert_eq!(array.len(), 2);
1732
1733        // Check first element
1734        let first_id = array.get(0).unwrap();
1735        let first = doc.node(first_id);
1736        let name_id = first.as_map().unwrap().get_node_id(&"name".into()).unwrap();
1737        let name = doc.node(name_id);
1738        assert_eq!(name.as_primitive().unwrap().as_str(), Some("first"));
1739    }
1740
1741    #[test]
1742    fn test_eure_nested_empty_blocks() {
1743        // Test nested empty blocks: a { b { c {} } }
1744        let doc = eure!({
1745            a {
1746                b {
1747                    c {}
1748                }
1749            }
1750        });
1751
1752        let root_id = doc.get_root_id();
1753        let root = doc.node(root_id);
1754
1755        let a_id = root.as_map().unwrap().get_node_id(&"a".into()).unwrap();
1756        let a = doc.node(a_id);
1757
1758        let b_id = a.as_map().unwrap().get_node_id(&"b".into()).unwrap();
1759        let b = doc.node(b_id);
1760
1761        let c_id = b.as_map().unwrap().get_node_id(&"c".into()).unwrap();
1762        let c = doc.node(c_id);
1763
1764        // c should be an empty map
1765        let map = c.as_map().expect("c should be an empty map");
1766        assert!(map.is_empty());
1767    }
1768
1769    #[test]
1770    fn test_eure_multiple_extensions() {
1771        // Test multiple extensions on same node
1772        let doc = eure!({
1773            field.%variant = @code("text")
1774            field.%"variant-repr" = "internal"
1775            field.%schema = "custom"
1776        });
1777
1778        let root_id = doc.get_root_id();
1779        let root = doc.node(root_id);
1780        let field_id = root.as_map().unwrap().get_node_id(&"field".into()).unwrap();
1781        let field = doc.node(field_id);
1782
1783        // Check all extensions exist
1784        assert!(field.get_extension(&"variant".parse().unwrap()).is_some());
1785        assert!(
1786            field
1787                .get_extension(&"variant-repr".parse().unwrap())
1788                .is_some()
1789        );
1790        assert!(field.get_extension(&"schema".parse().unwrap()).is_some());
1791    }
1792
1793    #[test]
1794    fn test_eure_extension_on_array_element() {
1795        // Test extension on array element using indexed access
1796        // Note: items[] creates a new element each time, so we use items[0], items[1] etc.
1797        let doc = eure!({
1798            items[0].%variant = @code("text")
1799            items[0].value = "first"
1800            items[1].%variant = @code("number")
1801            items[1].value = 42
1802        });
1803
1804        let root_id = doc.get_root_id();
1805        let root = doc.node(root_id);
1806        let items_id = root.as_map().unwrap().get_node_id(&"items".into()).unwrap();
1807        let items = doc.node(items_id);
1808        let array = items.as_array().unwrap();
1809        assert_eq!(array.len(), 2);
1810
1811        // Check first element has extension and value
1812        let first_id = array.get(0).unwrap();
1813        let first = doc.node(first_id);
1814        let variant_id = first.get_extension(&"variant".parse().unwrap()).unwrap();
1815        let variant = doc.node(variant_id);
1816        assert_eq!(
1817            variant.as_primitive().unwrap().as_text().unwrap().as_str(),
1818            "text"
1819        );
1820        let value_id = first
1821            .as_map()
1822            .unwrap()
1823            .get_node_id(&"value".into())
1824            .unwrap();
1825        let value = doc.node(value_id);
1826        assert_eq!(value.as_primitive().unwrap().as_str(), Some("first"));
1827
1828        // Check second element
1829        let second_id = array.get(1).unwrap();
1830        let second = doc.node(second_id);
1831        let variant_id = second.get_extension(&"variant".parse().unwrap()).unwrap();
1832        let variant = doc.node(variant_id);
1833        assert_eq!(
1834            variant.as_primitive().unwrap().as_text().unwrap().as_str(),
1835            "number"
1836        );
1837    }
1838
1839    #[test]
1840    fn test_eure_deep_nesting() {
1841        // Test deeply nested paths (5+ levels)
1842        let doc = eure!({ a.b.c.d.e.f = "deep" });
1843
1844        let root_id = doc.get_root_id();
1845        let root = doc.node(root_id);
1846
1847        let a_id = root.as_map().unwrap().get_node_id(&"a".into()).unwrap();
1848        let a = doc.node(a_id);
1849        let b_id = a.as_map().unwrap().get_node_id(&"b".into()).unwrap();
1850        let b = doc.node(b_id);
1851        let c_id = b.as_map().unwrap().get_node_id(&"c".into()).unwrap();
1852        let c = doc.node(c_id);
1853        let d_id = c.as_map().unwrap().get_node_id(&"d".into()).unwrap();
1854        let d = doc.node(d_id);
1855        let e_id = d.as_map().unwrap().get_node_id(&"e".into()).unwrap();
1856        let e = doc.node(e_id);
1857        let f_id = e.as_map().unwrap().get_node_id(&"f".into()).unwrap();
1858        let f = doc.node(f_id);
1859
1860        assert_eq!(f.as_primitive().unwrap().as_str(), Some("deep"));
1861    }
1862
1863    #[test]
1864    fn test_eure_empty_array_literal() {
1865        // Test empty array literal: items = []
1866        let doc = eure!({ items = [] });
1867
1868        let root_id = doc.get_root_id();
1869        let root = doc.node(root_id);
1870        let items_id = root.as_map().unwrap().get_node_id(&"items".into()).unwrap();
1871        let items = doc.node(items_id);
1872        let array = items.as_array().unwrap();
1873        assert!(array.is_empty());
1874    }
1875
1876    #[test]
1877    fn test_eure_empty_tuple_literal() {
1878        // Test empty tuple literal: point = ()
1879        let doc = eure!({ point = () });
1880
1881        let root_id = doc.get_root_id();
1882        let root = doc.node(root_id);
1883        let point_id = root.as_map().unwrap().get_node_id(&"point".into()).unwrap();
1884        let point = doc.node(point_id);
1885        let tuple = point.as_tuple().unwrap();
1886        assert!(tuple.is_empty());
1887    }
1888
1889    #[test]
1890    fn test_eure_empty_map_literal() {
1891        // Test empty map literal: data = {}
1892        // Note: This uses block syntax which creates empty map
1893        let doc = eure!({ data {} });
1894
1895        let root_id = doc.get_root_id();
1896        let root = doc.node(root_id);
1897        let data_id = root.as_map().unwrap().get_node_id(&"data".into()).unwrap();
1898        let data = doc.node(data_id);
1899        let map = data.as_map().unwrap();
1900        assert!(map.is_empty());
1901    }
1902
1903    #[test]
1904    fn test_eure_mixed_null_and_values() {
1905        // Test mixing null with other values
1906        let doc = eure!({
1907            name = "Alice"
1908            age = null
1909            active = true
1910            score = null
1911        });
1912
1913        let root_id = doc.get_root_id();
1914        let root = doc.node(root_id);
1915        let map = root.as_map().unwrap();
1916        assert_eq!(map.len(), 4);
1917
1918        let age_id = map.get_node_id(&"age".into()).unwrap();
1919        let age = doc.node(age_id);
1920        assert!(matches!(
1921            age.as_primitive(),
1922            Some(crate::value::PrimitiveValue::Null)
1923        ));
1924    }
1925
1926    // ========================================================================
1927    // Tests for section syntax
1928    // ========================================================================
1929
1930    #[test]
1931    fn test_eure_section_basic() {
1932        // Test basic section syntax: @section followed by bindings
1933        let doc = eure!({
1934            @user
1935            name = "Alice"
1936            age = 30
1937        });
1938
1939        let root_id = doc.get_root_id();
1940        let root = doc.node(root_id);
1941        let user_id = root.as_map().unwrap().get_node_id(&"user".into()).unwrap();
1942        let user = doc.node(user_id);
1943        let name_id = user.as_map().unwrap().get_node_id(&"name".into()).unwrap();
1944        let name = doc.node(name_id);
1945        assert_eq!(name.as_primitive().unwrap().as_str(), Some("Alice"));
1946    }
1947
1948    #[test]
1949    fn test_eure_section_multiple() {
1950        // Test multiple sections
1951        let doc = eure!({
1952            @user
1953            name = "Alice"
1954
1955            @settings
1956            theme = "dark"
1957        });
1958
1959        let root_id = doc.get_root_id();
1960        let root = doc.node(root_id);
1961
1962        let user_id = root.as_map().unwrap().get_node_id(&"user".into()).unwrap();
1963        let user = doc.node(user_id);
1964        assert!(user.as_map().unwrap().get_node_id(&"name".into()).is_some());
1965
1966        let settings_id = root
1967            .as_map()
1968            .unwrap()
1969            .get_node_id(&"settings".into())
1970            .unwrap();
1971        let settings = doc.node(settings_id);
1972        assert!(
1973            settings
1974                .as_map()
1975                .unwrap()
1976                .get_node_id(&"theme".into())
1977                .is_some()
1978        );
1979    }
1980
1981    #[test]
1982    fn test_eure_section_dotted_path() {
1983        // Test section with dotted path
1984        let doc = eure!({
1985            @user.profile
1986            name = "Alice"
1987        });
1988
1989        let root_id = doc.get_root_id();
1990        let root = doc.node(root_id);
1991        let user_id = root.as_map().unwrap().get_node_id(&"user".into()).unwrap();
1992        let user = doc.node(user_id);
1993        let profile_id = user
1994            .as_map()
1995            .unwrap()
1996            .get_node_id(&"profile".into())
1997            .unwrap();
1998        let profile = doc.node(profile_id);
1999        assert!(
2000            profile
2001                .as_map()
2002                .unwrap()
2003                .get_node_id(&"name".into())
2004                .is_some()
2005        );
2006    }
2007
2008    #[test]
2009    fn test_eure_section_with_block() {
2010        // Test section with block syntax: @path { ... }
2011        let doc = eure!({
2012            @user {
2013                name = "Alice"
2014                age = 30
2015            }
2016        });
2017
2018        let root_id = doc.get_root_id();
2019        let root = doc.node(root_id);
2020        let user_id = root.as_map().unwrap().get_node_id(&"user".into()).unwrap();
2021        let user = doc.node(user_id);
2022        assert!(user.as_map().unwrap().get_node_id(&"name".into()).is_some());
2023        assert!(user.as_map().unwrap().get_node_id(&"age".into()).is_some());
2024    }
2025
2026    #[test]
2027    fn test_eure_section_block_with_nested() {
2028        // Test section block with nested structure
2029        let doc = eure!({
2030            @config {
2031                server {
2032                    host = "localhost"
2033                    port = 8080
2034                }
2035                debug = true
2036            }
2037        });
2038
2039        let root_id = doc.get_root_id();
2040        let root = doc.node(root_id);
2041        let config_id = root
2042            .as_map()
2043            .unwrap()
2044            .get_node_id(&"config".into())
2045            .unwrap();
2046        let config = doc.node(config_id);
2047        assert!(
2048            config
2049                .as_map()
2050                .unwrap()
2051                .get_node_id(&"server".into())
2052                .is_some()
2053        );
2054        assert!(
2055            config
2056                .as_map()
2057                .unwrap()
2058                .get_node_id(&"debug".into())
2059                .is_some()
2060        );
2061
2062        let server_id = config
2063            .as_map()
2064            .unwrap()
2065            .get_node_id(&"server".into())
2066            .unwrap();
2067        let server = doc.node(server_id);
2068        assert!(
2069            server
2070                .as_map()
2071                .unwrap()
2072                .get_node_id(&"host".into())
2073                .is_some()
2074        );
2075        assert!(
2076            server
2077                .as_map()
2078                .unwrap()
2079                .get_node_id(&"port".into())
2080                .is_some()
2081        );
2082    }
2083
2084    #[test]
2085    fn test_eure_section_block_multiple() {
2086        // Test multiple sections with block syntax
2087        let doc = eure!({
2088            @user {
2089                name = "Alice"
2090            }
2091            @settings {
2092                theme = "dark"
2093            }
2094        });
2095
2096        let root_id = doc.get_root_id();
2097        let root = doc.node(root_id);
2098        assert!(root.as_map().unwrap().get_node_id(&"user".into()).is_some());
2099        assert!(
2100            root.as_map()
2101                .unwrap()
2102                .get_node_id(&"settings".into())
2103                .is_some()
2104        );
2105    }
2106
2107    #[test]
2108    fn test_eure_section_block_dotted_path() {
2109        // Test section block with dotted path: @a.b { ... }
2110        let doc = eure!({
2111            @server.config {
2112                host = "localhost"
2113                port = 8080
2114            }
2115        });
2116
2117        let root_id = doc.get_root_id();
2118        let root = doc.node(root_id);
2119        let server_id = root
2120            .as_map()
2121            .unwrap()
2122            .get_node_id(&"server".into())
2123            .unwrap();
2124        let server = doc.node(server_id);
2125        let config_id = server
2126            .as_map()
2127            .unwrap()
2128            .get_node_id(&"config".into())
2129            .unwrap();
2130        let config = doc.node(config_id);
2131        assert!(
2132            config
2133                .as_map()
2134                .unwrap()
2135                .get_node_id(&"host".into())
2136                .is_some()
2137        );
2138        assert!(
2139            config
2140                .as_map()
2141                .unwrap()
2142                .get_node_id(&"port".into())
2143                .is_some()
2144        );
2145    }
2146
2147    #[test]
2148    fn test_eure_section_block_empty() {
2149        // Test section with empty block
2150        let doc = eure!({
2151            @empty {}
2152        });
2153
2154        let root_id = doc.get_root_id();
2155        let root = doc.node(root_id);
2156        let empty_id = root.as_map().unwrap().get_node_id(&"empty".into()).unwrap();
2157        let empty = doc.node(empty_id);
2158        // Empty block creates empty map
2159        assert!(empty.as_map().unwrap().is_empty());
2160    }
2161
2162    #[test]
2163    fn test_eure_section_mixed_styles() {
2164        // Test mixing section styles: some with blocks, some without
2165        let doc = eure!({
2166            @user {
2167                name = "Alice"
2168            }
2169
2170            @settings
2171            theme = "dark"
2172            debug = true
2173
2174            @logging {
2175                level = "info"
2176            }
2177        });
2178
2179        let root_id = doc.get_root_id();
2180        let root = doc.node(root_id);
2181
2182        // user should have name
2183        let user_id = root.as_map().unwrap().get_node_id(&"user".into()).unwrap();
2184        let user = doc.node(user_id);
2185        assert!(user.as_map().unwrap().get_node_id(&"name".into()).is_some());
2186
2187        // settings should have theme and debug
2188        let settings_id = root
2189            .as_map()
2190            .unwrap()
2191            .get_node_id(&"settings".into())
2192            .unwrap();
2193        let settings = doc.node(settings_id);
2194        assert!(
2195            settings
2196                .as_map()
2197                .unwrap()
2198                .get_node_id(&"theme".into())
2199                .is_some()
2200        );
2201        assert!(
2202            settings
2203                .as_map()
2204                .unwrap()
2205                .get_node_id(&"debug".into())
2206                .is_some()
2207        );
2208
2209        // logging should have level
2210        let logging_id = root
2211            .as_map()
2212            .unwrap()
2213            .get_node_id(&"logging".into())
2214            .unwrap();
2215        let logging = doc.node(logging_id);
2216        assert!(
2217            logging
2218                .as_map()
2219                .unwrap()
2220                .get_node_id(&"level".into())
2221                .is_some()
2222        );
2223    }
2224
2225    #[test]
2226    fn test_eure_section_in_section_block() {
2227        // Test section in section block
2228        let doc = eure!({
2229            @ settings {
2230                theme = "dark"
2231                @ logging
2232                level = "info"
2233            }
2234        });
2235
2236        let settings = doc
2237            .parse_context(doc.get_root_id())
2238            .parse_record()
2239            .expect("Failed to parse record")
2240            .field_record("settings")
2241            .expect("Failed to parse settings");
2242        let theme = settings
2243            .parse_field::<&str>("theme")
2244            .expect("Failed to parse theme");
2245        let logging = settings
2246            .field_record("logging")
2247            .expect("Failed to parse logging")
2248            .parse_field::<&str>("level")
2249            .expect("Failed to parse level");
2250        settings
2251            .deny_unknown_fields()
2252            .expect("Failed to deny unknown fields");
2253        assert_eq!(theme, "dark");
2254        assert_eq!(logging, "info");
2255    }
2256
2257    #[test]
2258    fn test_eure_variable_text() {
2259        // Test using a variable for Text value
2260        use crate::text::Text;
2261        let code = Text::inline_implicit("fn main() {}");
2262        let doc = eure!({ snippet = code });
2263
2264        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2265        let snippet_ctx = root.field("snippet").unwrap();
2266        let snippet_node = doc.node(snippet_ctx.node_id());
2267        let text = snippet_node.as_primitive().unwrap().as_text().unwrap();
2268        assert_eq!(text.as_str(), "fn main() {}");
2269    }
2270
2271    #[test]
2272    fn test_eure_variable_in_array() {
2273        // Test using variables in array literals
2274        use alloc::vec::Vec;
2275        let first = "one";
2276        let second = "two";
2277        let third = "three";
2278        let doc = eure!({ items = [first, second, third] });
2279
2280        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2281        let items = root.parse_field::<Vec<&str>>("items").unwrap();
2282        assert_eq!(items, vec!["one", "two", "three"]);
2283    }
2284
2285    #[test]
2286    fn test_eure_variable_in_tuple() {
2287        // Test using variables in tuple literals
2288        let x = 1.5;
2289        let y = 2.5;
2290        let doc = eure!({ point = (x, y) });
2291
2292        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2293        let point = root.parse_field::<(f64, f64)>("point").unwrap();
2294        assert_eq!(point, (1.5, 2.5));
2295    }
2296
2297    #[test]
2298    fn test_eure_variable_in_object_literal() {
2299        // Test using variables in object literal values
2300        let x_val = 10.0;
2301        let y_val = 20.0;
2302        let doc = eure!({
2303            coords = {
2304                "x" => x_val
2305                "y" => y_val
2306            }
2307        });
2308
2309        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2310        let coords = root.field_record("coords").unwrap();
2311        let x = coords.parse_field::<f64>("x").unwrap();
2312        let y = coords.parse_field::<f64>("y").unwrap();
2313        assert_eq!(x, 10.0);
2314        assert_eq!(y, 20.0);
2315    }
2316
2317    #[test]
2318    #[allow(clippy::bool_assert_comparison)] // Testing parse_field deserialization returns correct value
2319    fn test_eure_variable_mixed_with_literals() {
2320        // Test mixing variables and literals
2321        let username = "bob";
2322        let is_active = true;
2323        let doc = eure!({
2324            user.name = username
2325            user.active = is_active
2326            user.role = "admin"
2327            user.level = 5
2328        });
2329
2330        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2331        let user = root.field_record("user").unwrap();
2332        assert_eq!(user.parse_field::<&str>("name").unwrap(), "bob");
2333        assert_eq!(user.parse_field::<bool>("active").unwrap(), true);
2334        assert_eq!(user.parse_field::<&str>("role").unwrap(), "admin");
2335        assert_eq!(user.parse_field::<i32>("level").unwrap(), 5);
2336    }
2337
2338    #[test]
2339    fn test_eure_variable_in_nested_array() {
2340        // Test using variables in nested array structures
2341        use alloc::vec::Vec;
2342        let tag1 = "rust";
2343        let tag2 = "macro";
2344        let doc = eure!({
2345            tags[] = tag1
2346            tags[] = tag2
2347        });
2348
2349        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2350        let tags = root.parse_field::<Vec<&str>>("tags").unwrap();
2351        assert_eq!(tags, vec!["rust", "macro"]);
2352    }
2353
2354    #[test]
2355    fn test_eure_variable_at_root() {
2356        // Test using a variable for root value binding
2357        let value = 42;
2358        let doc = eure!({
2359            = value
2360        });
2361
2362        let ctx = doc.parse_context(doc.get_root_id());
2363        let root_value = ctx.parse::<i32>().unwrap();
2364        assert_eq!(root_value, 42);
2365    }
2366
2367    #[test]
2368    fn test_eure_variable_in_section() {
2369        // Test using variables in section syntax
2370        let theme_value = "dark";
2371        let lang_value = "en";
2372        let doc = eure!({
2373            @settings
2374            theme = theme_value
2375            language = lang_value
2376        });
2377
2378        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2379        let settings = root.field_record("settings").unwrap();
2380        assert_eq!(settings.parse_field::<&str>("theme").unwrap(), "dark");
2381        assert_eq!(settings.parse_field::<&str>("language").unwrap(), "en");
2382    }
2383
2384    #[test]
2385    fn test_eure_variable_null_and_primitive() {
2386        // Test using PrimitiveValue::Null from a variable
2387        use crate::value::PrimitiveValue;
2388        let null_value = PrimitiveValue::Null;
2389        let doc = eure!({ optional = null_value });
2390
2391        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2392        let optional_ctx = root.field("optional").unwrap();
2393        let optional_node = doc.node(optional_ctx.node_id());
2394        assert!(matches!(
2395            optional_node.as_primitive().unwrap(),
2396            PrimitiveValue::Null
2397        ));
2398    }
2399
2400    // ========================================================================
2401    // eure_source! macro tests
2402    // ========================================================================
2403
2404    #[test]
2405    fn test_eure_source_empty() {
2406        let source_doc = eure_source!({});
2407        assert_eq!(source_doc.document(), &EureDocument::new_empty());
2408        assert!(source_doc.root_source().bindings.is_empty());
2409        assert!(source_doc.root_source().sections.is_empty());
2410    }
2411
2412    #[test]
2413    fn test_eure_source_simple_bindings() {
2414        let source_doc = eure_source!({
2415            name = "Alice"
2416            age = 30
2417        });
2418
2419        // Verify document content
2420        let doc = source_doc.document();
2421        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2422        assert_eq!(root.parse_field::<&str>("name").unwrap(), "Alice");
2423        assert_eq!(root.parse_field::<i64>("age").unwrap(), 30);
2424
2425        // Verify source structure
2426        let root_source = source_doc.root_source();
2427        assert_eq!(root_source.bindings.len(), 2);
2428        assert!(root_source.sections.is_empty());
2429    }
2430
2431    #[test]
2432    fn test_eure_source_nested() {
2433        use crate::source::BindSource;
2434
2435        let source_doc = eure_source!({
2436            user {
2437                name = "Bob"
2438                active = true
2439            }
2440        });
2441
2442        let doc = source_doc.document();
2443        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2444        let user = root.field_record("user").unwrap();
2445        assert_eq!(user.parse_field::<&str>("name").unwrap(), "Bob");
2446        assert!(user.parse_field::<bool>("active").unwrap());
2447
2448        // Verify source structure - should have one binding with a block
2449        let root_source = source_doc.root_source();
2450        assert_eq!(root_source.bindings.len(), 1);
2451        match &root_source.bindings[0].bind {
2452            BindSource::Block(source_id) => {
2453                let inner = source_doc.source(*source_id);
2454                assert_eq!(inner.bindings.len(), 2);
2455            }
2456            _ => panic!("Expected BindSource::Block"),
2457        }
2458    }
2459
2460    #[test]
2461    fn test_eure_generic_entry_point() {
2462        // Test the generic entry point with explicit constructor
2463        use crate::document::constructor::DocumentConstructor;
2464
2465        let mut c = DocumentConstructor::new();
2466        eure!(c; {
2467            x = 1
2468            y = 2
2469        });
2470        let doc = c.finish();
2471
2472        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2473        assert_eq!(root.parse_field::<i64>("x").unwrap(), 1);
2474        assert_eq!(root.parse_field::<i64>("y").unwrap(), 2);
2475    }
2476
2477    #[test]
2478    fn test_eure_source_generic_entry_point() {
2479        // Test the generic entry point with SourceConstructor
2480        use crate::document::source_constructor::SourceConstructor;
2481
2482        let mut c = SourceConstructor::new();
2483        eure!(c; {
2484            message = "hello"
2485        });
2486        let source_doc = c.finish();
2487
2488        let doc = source_doc.document();
2489        let root = doc.parse_context(doc.get_root_id()).parse_record().unwrap();
2490        assert_eq!(root.parse_field::<&str>("message").unwrap(), "hello");
2491    }
2492}