eure_document/
lib.rs

1#![no_std]
2extern crate alloc;
3
4#[cfg(feature = "std")]
5extern crate std;
6
7// Re-export commonly used types for eure! macro users
8pub use text::Text;
9
10/// A data structure for representing a Eure document without any span information.
11pub mod tree;
12
13/// Identifier type and parser.
14pub mod identifier;
15
16/// Unified text type for strings and code.
17pub mod text;
18
19/// A type-safe data-type of Eure data-model.
20pub mod value;
21
22/// A data structure for representing a Eure document including extensions.
23pub mod document;
24
25/// Data structure for representing a path in a Eure document.
26pub mod path;
27
28/// Data structure for representing a data-model of Eure.
29pub mod data_model;
30
31/// Trait for parsing Rust types from Eure documents.
32pub mod parse;
33
34#[cfg(feature = "std")]
35pub use ahash::AHashMap as Map;
36#[cfg(not(feature = "std"))]
37pub type Map<K, V> = alloc::collections::BTreeMap<K, V>;
38
39pub(crate) mod prelude_internal {
40    #![allow(unused_imports)]
41    #![allow(deprecated)]
42    pub use crate::Map;
43    pub use crate::data_model::*;
44    pub use crate::document::constructor::DocumentConstructor;
45    pub use crate::document::node::{Node, NodeMut, NodeValue};
46    pub use crate::document::{EureDocument, InsertError, InsertErrorKind, NodeId};
47    pub use crate::identifier::Identifier;
48    pub use crate::path::{EurePath, PathSegment};
49    pub use crate::text::{Language, SyntaxHint, Text, TextParseError};
50    pub use crate::value::PrimitiveValue;
51    pub use crate::value::{ObjectKey, Value};
52    pub use alloc::boxed::Box;
53    pub use alloc::{string::String, string::ToString, vec, vec::Vec};
54    pub use thisisplural::Plural;
55}
56
57/// A declarative macro for building Eure documents, inspired by serde_json's `json!` macro.
58///
59/// # Syntax
60///
61/// The macro uses a TT muncher pattern to support arbitrary path combinations:
62/// - Idents: `a.b.c`
63/// - Extensions: `a.%ext` (use `%` instead of `$` since `$` is reserved in macros)
64/// - Tuple index: `a.#0`, `a.#1`
65/// - Array markers: `a[]` (push), `a[0]` (index)
66/// - Tuple keys: `a.(1, "key")` (composite map keys)
67/// - Mixed paths: `a.%ext[].b`, `a[].%ext.#0`, `a.(1, 2).name`
68///
69/// # Examples
70///
71/// ```
72/// use eure_document::{eure, Text};
73///
74/// // Simple assignment
75/// let doc = eure!({
76///     name = "Alice",
77///     age = 30,
78/// });
79///
80/// // Nested paths
81/// let doc = eure!({
82///     user.name = "Bob",
83///     user.active = true,
84/// });
85///
86/// // Blocks (for grouping)
87/// let doc = eure!({
88///     user {
89///         name = "Charlie",
90///         role = "admin",
91///     },
92/// });
93///
94/// // Extensions
95/// let doc = eure!({
96///     field.%variant = Text::inline_implicit("text"),
97/// });
98///
99/// // Tuple index
100/// let doc = eure!({
101///     point.#0 = 1.0f64,
102///     point.#1 = 2.0f64,
103/// });
104///
105/// // Array markers
106/// let doc = eure!({
107///     items[] = 1,
108///     items[] = 2,
109/// });
110///
111/// // Tuple keys (composite map keys)
112/// let doc = eure!({
113///     map.(1, "key") = "value",
114///     map.(true, 2) = "another",
115/// });
116///
117/// // Arrays (literal)
118/// let doc = eure!({
119///     tags = [Text::inline_implicit("a"), Text::inline_implicit("b")],
120/// });
121///
122/// // Tuples (literal)
123/// let doc = eure!({
124///     point = (1.0f64, 2.0f64),
125/// });
126/// ```
127#[macro_export]
128macro_rules! eure {
129    // ========================================================================
130    // Entry points
131    // ========================================================================
132
133    // Empty document
134    ({}) => {{
135        $crate::document::EureDocument::new_empty()
136    }};
137
138    // Document with body
139    ({ $($body:tt)* }) => {{
140        let mut c = $crate::document::constructor::DocumentConstructor::new();
141        $crate::eure!(@body c; $($body)*);
142        c.finish()
143    }};
144
145    // ========================================================================
146    // Body handlers
147    // ========================================================================
148
149    // Empty body
150    (@body $c:ident;) => {};
151
152    // Value binding at root: = value, ...
153    (@body $c:ident; = $val:expr $(, $($tail:tt)*)?) => {{
154        $c.bind_from($val).unwrap();
155        $($crate::eure!(@body $c; $($tail)*);)?
156    }};
157
158    // Start parsing a statement - delegate to segment parser
159    (@body $c:ident; $($tokens:tt)+) => {{
160        let scope = $c.begin_scope();
161        $crate::eure!(@parse_seg $c scope; $($tokens)+);
162    }};
163
164    // ========================================================================
165    // Segment parsing - parse one path segment at a time
166    // ========================================================================
167
168    // Segment: ident
169    (@parse_seg $c:ident $scope:ident; $seg:ident $($rest:tt)*) => {{
170        $c.navigate($crate::path::PathSegment::Ident(
171            $crate::identifier::Identifier::new_unchecked(stringify!($seg))
172        )).unwrap();
173        $crate::eure!(@after_seg $c $scope; $($rest)*);
174    }};
175
176    // Segment: extension (%) with identifier
177    (@parse_seg $c:ident $scope:ident; % $ext:ident $($rest:tt)*) => {{
178        $c.navigate($crate::path::PathSegment::Extension(
179            $crate::identifier::Identifier::new_unchecked(stringify!($ext))
180        )).unwrap();
181        $crate::eure!(@after_seg $c $scope; $($rest)*);
182    }};
183
184    // Segment: extension (%) with string literal (for hyphenated names like "variant-repr")
185    (@parse_seg $c:ident $scope:ident; % $ext:literal $($rest:tt)*) => {{
186        $c.navigate($crate::path::PathSegment::Extension(
187            $ext.parse().unwrap()
188        )).unwrap();
189        $crate::eure!(@after_seg $c $scope; $($rest)*);
190    }};
191
192    // Segment: tuple index (#N)
193    (@parse_seg $c:ident $scope:ident; # $idx:literal $($rest:tt)*) => {{
194        $c.navigate($crate::path::PathSegment::TupleIndex($idx)).unwrap();
195        $crate::eure!(@after_seg $c $scope; $($rest)*);
196    }};
197
198    // Segment: tuple key ((a, b, ...))
199    (@parse_seg $c:ident $scope:ident; ($($tuple:tt)*) $($rest:tt)*) => {{
200        let key = $crate::eure!(@build_tuple_key; $($tuple)*);
201        $c.navigate($crate::path::PathSegment::Value(key)).unwrap();
202        $crate::eure!(@after_seg $c $scope; $($rest)*);
203    }};
204
205    // Segment: string literal key ("key" for hyphenated identifiers)
206    (@parse_seg $c:ident $scope:ident; $key:literal $($rest:tt)*) => {{
207        $c.navigate($crate::path::PathSegment::Value($key.into())).unwrap();
208        $crate::eure!(@after_seg $c $scope; $($rest)*);
209    }};
210
211    // ========================================================================
212    // Build tuple key from contents
213    // ========================================================================
214
215    // Empty tuple
216    (@build_tuple_key;) => {{
217        $crate::value::ObjectKey::Tuple($crate::value::Tuple(Default::default()))
218    }};
219
220    // Non-empty tuple - items converted via Into<ObjectKey>
221    (@build_tuple_key; $($item:expr),+ $(,)?) => {{
222        $crate::value::ObjectKey::Tuple($crate::value::Tuple::from_iter(
223            [$(<_ as Into<$crate::value::ObjectKey>>::into($item)),+]
224        ))
225    }};
226
227    // ========================================================================
228    // After segment - check for optional array marker
229    // ========================================================================
230
231    // Has array marker (captured as token tree)
232    (@after_seg $c:ident $scope:ident; [$($arr:tt)*] $($rest:tt)*) => {{
233        $crate::eure!(@handle_arr $c $scope [$($arr)*]; $($rest)*);
234    }};
235
236    // No array marker - go to after_arr
237    (@after_seg $c:ident $scope:ident; $($rest:tt)*) => {{
238        $crate::eure!(@after_arr $c $scope; $($rest)*);
239    }};
240
241    // ========================================================================
242    // Handle array marker content
243    // ========================================================================
244
245    // Empty array marker (push)
246    (@handle_arr $c:ident $scope:ident []; $($rest:tt)*) => {{
247        $c.navigate($crate::path::PathSegment::ArrayIndex(None)).unwrap();
248        $crate::eure!(@after_arr $c $scope; $($rest)*);
249    }};
250
251    // Array marker with index
252    (@handle_arr $c:ident $scope:ident [$idx:literal]; $($rest:tt)*) => {{
253        $c.navigate($crate::path::PathSegment::ArrayIndex(Some($idx))).unwrap();
254        $crate::eure!(@after_arr $c $scope; $($rest)*);
255    }};
256
257    // ========================================================================
258    // After array marker - check for continuation, assignment, or block
259    // ========================================================================
260
261    // Continuation: more path segments
262    (@after_arr $c:ident $scope:ident; . $($rest:tt)+) => {{
263        $crate::eure!(@parse_seg $c $scope; $($rest)+);
264    }};
265
266    // Terminal: array literal assignment
267    (@after_arr $c:ident $scope:ident; = [$($items:expr),* $(,)?] $(, $($tail:tt)*)?) => {{
268        $c.bind_empty_array().unwrap();
269        $(
270            let item_scope = $c.begin_scope();
271            $c.navigate($crate::path::PathSegment::ArrayIndex(None)).unwrap();
272            $c.bind_from($items).unwrap();
273            $c.end_scope(item_scope).unwrap();
274        )*
275        $c.end_scope($scope).unwrap();
276        $($crate::eure!(@body $c; $($tail)*);)?
277    }};
278
279    // Terminal: tuple literal assignment
280    (@after_arr $c:ident $scope:ident; = ($($items:expr),* $(,)?) $(, $($tail:tt)*)?) => {{
281        $c.bind_empty_tuple().unwrap();
282        #[allow(unused_mut)]
283        let mut _idx: u8 = 0;
284        $(
285            let item_scope = $c.begin_scope();
286            $c.navigate($crate::path::PathSegment::TupleIndex(_idx)).unwrap();
287            $c.bind_from($items).unwrap();
288            $c.end_scope(item_scope).unwrap();
289            _idx += 1;
290        )*
291        $c.end_scope($scope).unwrap();
292        $($crate::eure!(@body $c; $($tail)*);)?
293    }};
294
295    // Terminal: object literal assignment (map with => syntax)
296    (@after_arr $c:ident $scope:ident; = { $($key:expr => $val:expr),* $(,)? } $(, $($tail:tt)*)?) => {{
297        $c.bind_empty_map().unwrap();
298        $(
299            let item_scope = $c.begin_scope();
300            $c.navigate($crate::path::PathSegment::Value($key.into())).unwrap();
301            $c.bind_from($val).unwrap();
302            $c.end_scope(item_scope).unwrap();
303        )*
304        $c.end_scope($scope).unwrap();
305        $($crate::eure!(@body $c; $($tail)*);)?
306    }};
307
308    // Terminal: simple assignment
309    (@after_arr $c:ident $scope:ident; = $val:expr $(, $($tail:tt)*)?) => {{
310        $c.bind_from($val).unwrap();
311        $c.end_scope($scope).unwrap();
312        $($crate::eure!(@body $c; $($tail)*);)?
313    }};
314
315    // Terminal: empty block -> empty map
316    (@after_arr $c:ident $scope:ident; {} $(, $($tail:tt)*)?) => {{
317        $c.bind_empty_map().unwrap();
318        $c.end_scope($scope).unwrap();
319        $($crate::eure!(@body $c; $($tail)*);)?
320    }};
321
322    // Terminal: non-empty block
323    (@after_arr $c:ident $scope:ident; { $($inner:tt)+ } $(, $($tail:tt)*)?) => {{
324        $crate::eure!(@body $c; $($inner)+);
325        $c.end_scope($scope).unwrap();
326        $($crate::eure!(@body $c; $($tail)*);)?
327    }};
328}
329
330#[cfg(test)]
331mod tests {
332    use crate::document::EureDocument;
333    use crate::text::Text;
334
335    #[test]
336    fn test_eure_empty() {
337        let doc = eure!({});
338        assert_eq!(doc, EureDocument::new_empty());
339    }
340
341    #[test]
342    fn test_eure_simple_assignment() {
343        let doc = eure!({
344            name = "Alice",
345        });
346
347        // Verify the structure
348        let root_id = doc.get_root_id();
349        let root = doc.node(root_id);
350        let name_node_id = root.as_map().unwrap().get(&"name".into()).unwrap();
351        let name_node = doc.node(name_node_id);
352        let prim = name_node.as_primitive().unwrap();
353        assert_eq!(prim.as_str(), Some("Alice"));
354    }
355
356    #[test]
357    fn test_eure_nested_path() {
358        let doc = eure!({
359            user.name = "Bob",
360            user.age = 30,
361        });
362
363        // Verify structure: root.user.name = "Bob", root.user.age = 30
364        let root_id = doc.get_root_id();
365        let root = doc.node(root_id);
366        let user_id = root.as_map().unwrap().get(&"user".into()).unwrap();
367        let user = doc.node(user_id);
368        let name_id = user.as_map().unwrap().get(&"name".into()).unwrap();
369        let name = doc.node(name_id);
370        assert_eq!(name.as_primitive().unwrap().as_str(), Some("Bob"));
371
372        let age_id = user.as_map().unwrap().get(&"age".into()).unwrap();
373        let age = doc.node(age_id);
374        assert!(matches!(
375            age.as_primitive(),
376            Some(crate::value::PrimitiveValue::Integer(_))
377        ));
378    }
379
380    #[test]
381    fn test_eure_block() {
382        let doc = eure!({
383            user {
384                name = "Charlie",
385                active = true,
386            }
387        });
388
389        let root_id = doc.get_root_id();
390        let root = doc.node(root_id);
391        let user_id = root.as_map().unwrap().get(&"user".into()).unwrap();
392        let user = doc.node(user_id);
393        let name_id = user.as_map().unwrap().get(&"name".into()).unwrap();
394        let name = doc.node(name_id);
395        assert_eq!(name.as_primitive().unwrap().as_str(), Some("Charlie"));
396    }
397
398    #[test]
399    fn test_eure_extension() {
400        let doc = eure!({
401            field.%variant = Text::inline_implicit("text"),
402        });
403
404        let root_id = doc.get_root_id();
405        let root = doc.node(root_id);
406        let field_id = root.as_map().unwrap().get(&"field".into()).unwrap();
407        let field = doc.node(field_id);
408
409        // Check extension
410        let variant_id = field.get_extension(&"variant".parse().unwrap()).unwrap();
411        let variant = doc.node(variant_id);
412        let text = variant.as_primitive().unwrap().as_text().unwrap();
413        assert_eq!(text.as_str(), "text");
414    }
415
416    #[test]
417    fn test_eure_extension_with_child() {
418        // Test pattern: a.%ext.b = value
419        let doc = eure!({
420            field.%variant.name = Text::inline_implicit("text"),
421            field.%variant.min_length = 3
422        });
423
424        let root_id = doc.get_root_id();
425        let root = doc.node(root_id);
426        let field_id = root.as_map().unwrap().get(&"field".into()).unwrap();
427        let field = doc.node(field_id);
428
429        // Check extension
430        let variant_id = field.get_extension(&"variant".parse().unwrap()).unwrap();
431        let variant = doc.node(variant_id);
432
433        // Check child of extension
434        let name_id = variant.as_map().unwrap().get(&"name".into()).unwrap();
435        let name = doc.node(name_id);
436        let text = name.as_primitive().unwrap().as_text().unwrap();
437        assert_eq!(text.as_str(), "text");
438
439        let min_length_id = variant.as_map().unwrap().get(&"min_length".into()).unwrap();
440        let min_length = doc.node(min_length_id);
441        assert!(matches!(
442            min_length.as_primitive(),
443            Some(crate::value::PrimitiveValue::Integer(_))
444        ));
445    }
446
447    #[test]
448    fn test_eure_array() {
449        let doc = eure!({
450            tags = [Text::inline_implicit("a"), Text::inline_implicit("b"), Text::inline_implicit("c")],
451        });
452
453        let root_id = doc.get_root_id();
454        let root = doc.node(root_id);
455        let tags_id = root.as_map().unwrap().get(&"tags".into()).unwrap();
456        let tags = doc.node(tags_id);
457        let array = tags.as_array().unwrap();
458        assert_eq!(array.len(), 3);
459    }
460
461    #[test]
462    fn test_eure_tuple() {
463        let doc = eure!({
464            point = (1.5f64, 2.5f64),
465        });
466
467        let root_id = doc.get_root_id();
468        let root = doc.node(root_id);
469        let point_id = root.as_map().unwrap().get(&"point".into()).unwrap();
470        let point = doc.node(point_id);
471        let tuple = point.as_tuple().unwrap();
472        assert_eq!(tuple.len(), 2);
473    }
474
475    #[test]
476    fn test_eure_multiple_assignments() {
477        let doc = eure!({
478            a = 1,
479            b = 2,
480            c = 3,
481        });
482
483        let root_id = doc.get_root_id();
484        let root = doc.node(root_id);
485        let map = root.as_map().unwrap();
486        assert_eq!(map.len(), 3);
487    }
488
489    #[test]
490    fn test_eure_complex() {
491        // A more complex example combining features
492        let doc = eure!({
493            schema {
494                field.%variant = Text::inline_implicit("text"),
495                field.min_length = 3,
496                field.max_length = 20,
497            },
498            tags = [Text::inline_implicit("required")],
499        });
500
501        let root_id = doc.get_root_id();
502        let root = doc.node(root_id);
503
504        // Check schema exists
505        let schema_id = root.as_map().unwrap().get(&"schema".into()).unwrap();
506        let schema = doc.node(schema_id);
507
508        // Check field exists with extension
509        let field_id = schema.as_map().unwrap().get(&"field".into()).unwrap();
510        let field = doc.node(field_id);
511        assert!(field.get_extension(&"variant".parse().unwrap()).is_some());
512
513        // Check tags array
514        let tags_id = root.as_map().unwrap().get(&"tags".into()).unwrap();
515        let tags = doc.node(tags_id);
516        assert_eq!(tags.as_array().unwrap().len(), 1);
517    }
518
519    #[test]
520    fn test_eure_array_push() {
521        // Test array push syntax: items[] = value
522        let doc = eure!({
523            items[] = 1,
524            items[] = 2,
525            items[] = 3,
526        });
527
528        let root_id = doc.get_root_id();
529        let root = doc.node(root_id);
530        let items_id = root.as_map().unwrap().get(&"items".into()).unwrap();
531        let items = doc.node(items_id);
532        let array = items.as_array().unwrap();
533        assert_eq!(array.len(), 3);
534    }
535
536    #[test]
537    fn test_eure_array_push_with_child() {
538        // Test: items[].name = value (array push then navigate to child)
539        let doc = eure!({
540            items[].name = "first",
541            items[].name = "second",
542        });
543
544        let root_id = doc.get_root_id();
545        let root = doc.node(root_id);
546        let items_id = root.as_map().unwrap().get(&"items".into()).unwrap();
547        let items = doc.node(items_id);
548        let array = items.as_array().unwrap();
549        assert_eq!(array.len(), 2);
550
551        // Check first element has name = "first"
552        let first_id = array.get(0).unwrap();
553        let first = doc.node(first_id);
554        let name_id = first.as_map().unwrap().get(&"name".into()).unwrap();
555        let name = doc.node(name_id);
556        assert_eq!(name.as_primitive().unwrap().as_str(), Some("first"));
557    }
558
559    #[test]
560    fn test_eure_tuple_index() {
561        // Test tuple index syntax: point.#0, point.#1
562        let doc = eure!({
563            point.#0 = 1.5f64,
564            point.#1 = 2.5f64,
565        });
566
567        let root_id = doc.get_root_id();
568        let root = doc.node(root_id);
569        let point_id = root.as_map().unwrap().get(&"point".into()).unwrap();
570        let point = doc.node(point_id);
571        let tuple = point.as_tuple().unwrap();
572        assert_eq!(tuple.len(), 2);
573    }
574
575    #[test]
576    fn test_eure_mixed_path_extension_array() {
577        // Test: a.%ext[].b = value
578        let doc = eure!({
579            field.%items[].name = "item1",
580            field.%items[].name = "item2",
581        });
582
583        let root_id = doc.get_root_id();
584        let root = doc.node(root_id);
585        let field_id = root.as_map().unwrap().get(&"field".into()).unwrap();
586        let field = doc.node(field_id);
587
588        // Get extension
589        let items_id = field.get_extension(&"items".parse().unwrap()).unwrap();
590        let items = doc.node(items_id);
591        let array = items.as_array().unwrap();
592        assert_eq!(array.len(), 2);
593    }
594
595    #[test]
596    fn test_eure_mixed_path_array_extension() {
597        // Test: items[].%variant = value
598        let doc = eure!({
599            items[].%variant = Text::inline_implicit("text"),
600            items[].%variant = Text::inline_implicit("number"),
601        });
602
603        let root_id = doc.get_root_id();
604        let root = doc.node(root_id);
605        let items_id = root.as_map().unwrap().get(&"items".into()).unwrap();
606        let items = doc.node(items_id);
607        let array = items.as_array().unwrap();
608        assert_eq!(array.len(), 2);
609
610        // Check first element has extension
611        let first_id = array.get(0).unwrap();
612        let first = doc.node(first_id);
613        let variant_id = first.get_extension(&"variant".parse().unwrap()).unwrap();
614        let variant = doc.node(variant_id);
615        assert_eq!(
616            variant.as_primitive().unwrap().as_text().unwrap().as_str(),
617            "text"
618        );
619    }
620
621    #[test]
622    fn test_eure_tuple_key() {
623        use crate::value::{ObjectKey, Tuple};
624
625        // Test tuple key: map.(1, "a") = value
626        let doc = eure!({
627            map.(1, "key") = "value1",
628            map.(2, "key") = "value2",
629        });
630
631        let root_id = doc.get_root_id();
632        let root = doc.node(root_id);
633        let map_id = root.as_map().unwrap().get(&"map".into()).unwrap();
634        let map_node = doc.node(map_id);
635        let map = map_node.as_map().unwrap();
636        assert_eq!(map.len(), 2);
637
638        // Check key (1, "key") exists
639        let tuple_key = ObjectKey::Tuple(Tuple(alloc::vec![1.into(), "key".into()]));
640        let value_id = map.get(&tuple_key).unwrap();
641        let value = doc.node(value_id);
642        assert_eq!(value.as_primitive().unwrap().as_str(), Some("value1"));
643    }
644
645    #[test]
646    fn test_eure_tuple_key_with_bool() {
647        use crate::value::{ObjectKey, Tuple};
648
649        // Test tuple key with bool: map.(true, 1) = value
650        let doc = eure!({
651            map.(true, 1) = "yes",
652            map.(false, 1) = "no",
653        });
654
655        let root_id = doc.get_root_id();
656        let root = doc.node(root_id);
657        let map_id = root.as_map().unwrap().get(&"map".into()).unwrap();
658        let map_node = doc.node(map_id);
659        let map = map_node.as_map().unwrap();
660        assert_eq!(map.len(), 2);
661
662        // Check key (true, 1) exists
663        let tuple_key = ObjectKey::Tuple(Tuple(alloc::vec![true.into(), 1.into()]));
664        let value_id = map.get(&tuple_key).unwrap();
665        let value = doc.node(value_id);
666        assert_eq!(value.as_primitive().unwrap().as_str(), Some("yes"));
667    }
668
669    #[test]
670    fn test_eure_tuple_key_with_child() {
671        use crate::value::{ObjectKey, Tuple};
672
673        // Test tuple key with child path: map.(1, 2).name = value
674        let doc = eure!({
675            map.(1, 2).name = "point_a",
676            map.(1, 2).value = 42,
677        });
678
679        let root_id = doc.get_root_id();
680        let root = doc.node(root_id);
681        let map_id = root.as_map().unwrap().get(&"map".into()).unwrap();
682        let map_node = doc.node(map_id);
683        let map = map_node.as_map().unwrap();
684
685        // Check key (1, 2) has children
686        let tuple_key = ObjectKey::Tuple(Tuple(alloc::vec![1.into(), 2.into()]));
687        let entry_id = map.get(&tuple_key).unwrap();
688        let entry = doc.node(entry_id);
689        let entry_map = entry.as_map().unwrap();
690
691        let name_id = entry_map.get(&"name".into()).unwrap();
692        let name = doc.node(name_id);
693        assert_eq!(name.as_primitive().unwrap().as_str(), Some("point_a"));
694    }
695
696    #[test]
697    fn test_eure_string_key() {
698        // Test string literal key for hyphenated identifiers: "min-length" = 3
699        let doc = eure!({
700            field."min-length" = 3,
701            field."max-length" = 20,
702        });
703
704        let root_id = doc.get_root_id();
705        let root = doc.node(root_id);
706        let field_id = root.as_map().unwrap().get(&"field".into()).unwrap();
707        let field = doc.node(field_id);
708        let field_map = field.as_map().unwrap();
709
710        // Check "min-length" key exists
711        let min_id = field_map.get(&"min-length".into()).unwrap();
712        let min_node = doc.node(min_id);
713        assert!(matches!(
714            min_node.as_primitive(),
715            Some(crate::value::PrimitiveValue::Integer(_))
716        ));
717    }
718
719    #[test]
720    fn test_eure_object_literal() {
721        // Test object literal with => syntax
722        let doc = eure!({
723            variants.click = { "x" => 1.0f64, "y" => 2.0f64 },
724        });
725
726        let root_id = doc.get_root_id();
727        let root = doc.node(root_id);
728        let variants_id = root.as_map().unwrap().get(&"variants".into()).unwrap();
729        let variants = doc.node(variants_id);
730        let click_id = variants.as_map().unwrap().get(&"click".into()).unwrap();
731        let click = doc.node(click_id);
732        let click_map = click.as_map().unwrap();
733
734        assert_eq!(click_map.len(), 2);
735        assert!(click_map.get(&"x".into()).is_some());
736        assert!(click_map.get(&"y".into()).is_some());
737    }
738
739    #[test]
740    fn test_eure_object_literal_with_text() {
741        // Test object literal for schema-like patterns
742        let doc = eure!({
743            schema.variants.success = { "data" => Text::inline_implicit("any") },
744        });
745
746        let root_id = doc.get_root_id();
747        let root = doc.node(root_id);
748        let schema_id = root.as_map().unwrap().get(&"schema".into()).unwrap();
749        let schema = doc.node(schema_id);
750        let variants_id = schema.as_map().unwrap().get(&"variants".into()).unwrap();
751        let variants = doc.node(variants_id);
752        let success_id = variants.as_map().unwrap().get(&"success".into()).unwrap();
753        let success = doc.node(success_id);
754        let success_map = success.as_map().unwrap();
755
756        let data_id = success_map.get(&"data".into()).unwrap();
757        let data = doc.node(data_id);
758        let text = data.as_primitive().unwrap().as_text().unwrap();
759        assert_eq!(text.as_str(), "any");
760    }
761
762    #[test]
763    fn test_eure_value_binding() {
764        // Test value binding at root: = value
765        let doc = eure!({
766            = Text::inline_implicit("hello"),
767        });
768
769        let root_id = doc.get_root_id();
770        let root = doc.node(root_id);
771        let text = root.as_primitive().unwrap().as_text().unwrap();
772        assert_eq!(text.as_str(), "hello");
773    }
774
775    #[test]
776    fn test_eure_value_binding_with_extension() {
777        // Test value binding with extension: = value, %ext = value
778        let doc = eure!({
779            = Text::inline_implicit("any"),
780            %variant = "literal",
781        });
782
783        let root_id = doc.get_root_id();
784        let root = doc.node(root_id);
785
786        // Check value
787        let text = root.as_primitive().unwrap().as_text().unwrap();
788        assert_eq!(text.as_str(), "any");
789
790        // Check extension
791        let variant_id = root.get_extension(&"variant".parse().unwrap()).unwrap();
792        let variant = doc.node(variant_id);
793        assert_eq!(variant.as_primitive().unwrap().as_str(), Some("literal"));
794    }
795
796    #[test]
797    fn test_eure_empty_block() {
798        // Empty block should create an empty map, not a Hole
799        let doc = eure!({
800            config {},
801        });
802
803        let root_id = doc.get_root_id();
804        let root = doc.node(root_id);
805        let config_id = root.as_map().unwrap().get(&"config".into()).unwrap();
806        let config = doc.node(config_id);
807
808        // Should be an empty map, not Hole
809        let map = config
810            .as_map()
811            .expect("Empty block should create an empty map");
812        assert!(map.is_empty());
813    }
814}