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