Skip to main content

sda_lib/
lib.rs

1//! Structured Data Algebra as a pure Rust library.
2//!
3//! `sda-lib` parses, validates, formats, and evaluates standalone SDA programs.
4//! The public API is intentionally small so host applications can bind JSON input,
5//! run a program, and recover canonical JSON output without embedding CLI concerns.
6//!
7//! ```rust
8//! let output = sda_lib::run("input<\"name\">!", serde_json::json!({"name": "Ada"}))?;
9//! assert_eq!(output, serde_json::json!({"$type": "ok", "$value": "Ada"}));
10//! # Ok::<(), sda_lib::SdaError>(())
11//! ```
12
13mod ast;
14mod format;
15mod lexer;
16mod number;
17mod parser;
18
19pub mod eval;
20pub mod stdlib;
21
22pub use eval::EvalError;
23pub use lexer::LexError;
24pub use number::{ExactNum, ParseNumError};
25pub use parser::ParseError;
26
27use ast::Expr;
28use std::collections::HashMap;
29use thiserror::Error;
30
31pub type Env = HashMap<String, Value>;
32
33#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
34pub struct SdaRuntime;
35
36impl SdaRuntime {
37    #[must_use]
38    pub fn name() -> &'static str {
39        "sda-lib"
40    }
41}
42
43#[derive(Debug, Clone)]
44pub enum Value {
45    Null,
46    Bool(bool),
47    Num(ExactNum),
48    Str(String),
49    Bytes(Vec<u8>),
50    Seq(Vec<Value>),
51    Set(Vec<Value>),
52    Bag(Vec<Value>),
53    Map(Vec<(String, Value)>),
54    Prod(Vec<(String, Value)>),
55    BagKV(Vec<(Value, Value)>),
56    Bind(Box<Value>, Box<Value>),
57    Some_(Box<Value>),
58    None_,
59    Ok_(Box<Value>),
60    Fail_(String, String),
61    Lambda(String, Box<Expr>, Box<Env>),
62}
63
64impl PartialEq for Value {
65    fn eq(&self, other: &Self) -> bool {
66        match (self, other) {
67            (Value::Null, Value::Null) => true,
68            (Value::Bool(a), Value::Bool(b)) => a == b,
69            (Value::Num(a), Value::Num(b)) => a == b,
70            (Value::Str(a), Value::Str(b)) => a == b,
71            (Value::Bytes(a), Value::Bytes(b)) => a == b,
72            (Value::Seq(a), Value::Seq(b)) => a == b,
73            (Value::Set(a), Value::Set(b)) => set_equal(a, b),
74            (Value::Bag(a), Value::Bag(b)) => multiset_equal(a, b),
75            (Value::Map(a), Value::Map(b)) => kv_extensional_equal(a, b),
76            (Value::Prod(a), Value::Prod(b)) => kv_extensional_equal(a, b),
77            (Value::BagKV(a), Value::BagKV(b)) => bagkv_equal(a, b),
78            (Value::Bind(k1, v1), Value::Bind(k2, v2)) => k1 == k2 && v1 == v2,
79            (Value::Some_(a), Value::Some_(b)) => a == b,
80            (Value::None_, Value::None_) => true,
81            (Value::Ok_(a), Value::Ok_(b)) => a == b,
82            (Value::Fail_(c1, m1), Value::Fail_(c2, m2)) => c1 == c2 && m1 == m2,
83            (Value::Lambda(_, _, _), _) => false,
84            (_, Value::Lambda(_, _, _)) => false,
85            _ => false,
86        }
87    }
88}
89
90fn set_equal(left: &[Value], right: &[Value]) -> bool {
91    left.len() == right.len()
92        && left.iter().all(|left_value| right.iter().any(|right_value| left_value == right_value))
93}
94
95fn multiset_equal(left: &[Value], right: &[Value]) -> bool {
96    if left.len() != right.len() {
97        return false;
98    }
99
100    let mut matched = vec![false; right.len()];
101    for left_value in left {
102        let mut found = false;
103        for (index, right_value) in right.iter().enumerate() {
104            if !matched[index] && left_value == right_value {
105                matched[index] = true;
106                found = true;
107                break;
108            }
109        }
110        if !found {
111            return false;
112        }
113    }
114
115    true
116}
117
118fn kv_extensional_equal(left: &[(String, Value)], right: &[(String, Value)]) -> bool {
119    if left.len() != right.len() {
120        return false;
121    }
122
123    left.iter().all(|(left_key, left_value)| {
124        right
125            .iter()
126            .find(|(right_key, _)| right_key == left_key)
127            .is_some_and(|(_, right_value)| left_value == right_value)
128    })
129}
130
131fn bagkv_equal(left: &[(Value, Value)], right: &[(Value, Value)]) -> bool {
132    if left.len() != right.len() {
133        return false;
134    }
135
136    let mut matched = vec![false; right.len()];
137    for (left_key, left_value) in left {
138        let mut found = false;
139        for (index, (right_key, right_value)) in right.iter().enumerate() {
140            if !matched[index] && left_key == right_key && left_value == right_value {
141                matched[index] = true;
142                found = true;
143                break;
144            }
145        }
146        if !found {
147            return false;
148        }
149    }
150
151    true
152}
153
154#[derive(Debug, Error)]
155pub enum SdaError {
156    #[error("Lex error: {0}")]
157    Lex(#[from] LexError),
158    #[error("Parse error: {0}")]
159    Parse(#[from] ParseError),
160    #[error("Eval error: {0}")]
161    Eval(#[from] EvalError),
162}
163
164pub fn run(expr: &str, input: serde_json::Value) -> Result<serde_json::Value, SdaError> {
165    run_with_input_binding(expr, "input", input)
166}
167
168pub fn run_with_input_binding(
169    expr: &str,
170    binding_name: &str,
171    input: serde_json::Value,
172) -> Result<serde_json::Value, SdaError> {
173    let input_val = from_json(input);
174    let program = parse_source(expr)?;
175    let mut env = Env::new();
176    env.insert(binding_name.to_string(), input_val);
177    let result = eval::eval_program(&program, &mut env)?;
178    Ok(to_json(result.unwrap_or(Value::Null)))
179}
180
181pub fn check(expr: &str) -> Result<(), SdaError> {
182    parse_source(expr).map(|_| ())
183}
184
185pub fn format_source(expr: &str) -> Result<String, SdaError> {
186    let program = parse_source(expr)?;
187    Ok(format::format_program(&program))
188}
189
190fn parse_source(expr: &str) -> Result<ast::Program, SdaError> {
191    let expr_normalized = {
192        let trimmed = expr.trim_end();
193        if trimmed.ends_with(';') {
194            trimmed.to_string()
195        } else {
196            format!("{};", trimmed)
197        }
198    };
199    let tokens = lexer::lex(&expr_normalized)?;
200    let program = parser::parse(tokens)?;
201    Ok(program)
202}
203
204pub fn from_json(v: serde_json::Value) -> Value {
205    match v {
206        serde_json::Value::Null => Value::Null,
207        serde_json::Value::Bool(b) => Value::Bool(b),
208        serde_json::Value::Number(n) => {
209            Value::Num(ExactNum::parse_literal(&n.to_string()).expect("serde_json number should parse exactly"))
210        }
211        serde_json::Value::String(s) => Value::Str(s),
212        serde_json::Value::Array(arr) => Value::Seq(arr.into_iter().map(from_json).collect()),
213        serde_json::Value::Object(obj) => {
214            if let Some(serde_json::Value::String(ty)) = obj.get("$type") {
215                match ty.as_str() {
216                    "bytes" => {
217                        if let Some(serde_json::Value::String(base16)) = obj.get("$base16") {
218                            return Value::Bytes(
219                                decode_base16(base16)
220                                    .expect("canonical bytes wrapper should contain valid base16"),
221                            );
222                        }
223                    }
224                    "map" => {
225                        if let Some(serde_json::Value::Object(entries)) = obj.get("$entries") {
226                            return Value::Map(
227                                entries
228                                    .iter()
229                                    .map(|(k, v)| (k.clone(), from_json(v.clone())))
230                                    .collect(),
231                            );
232                        }
233                    }
234                    "num" => {
235                        if let Some(serde_json::Value::String(value)) = obj.get("$value") {
236                            return Value::Num(
237                                ExactNum::parse_canonical(value)
238                                    .expect("canonical numeric wrapper should parse")
239                            );
240                        }
241                    }
242                    "set" => {
243                        if let Some(serde_json::Value::Array(items)) = obj.get("$items") {
244                            return Value::Set(items.iter().cloned().map(from_json).collect());
245                        }
246                    }
247                    "bag" => {
248                        if let Some(serde_json::Value::Array(items)) = obj.get("$items") {
249                            return Value::Bag(items.iter().cloned().map(from_json).collect());
250                        }
251                    }
252                    "prod" => {
253                        if let Some(serde_json::Value::Object(fields)) = obj.get("$fields") {
254                            let entries = fields
255                                .iter()
256                                .map(|(k, v)| (k.clone(), from_json(v.clone())))
257                                .collect();
258                            return Value::Prod(entries);
259                        }
260                    }
261                    "bagkv" => {
262                        if let Some(serde_json::Value::Array(items)) = obj.get("$items") {
263                            let pairs = items
264                                .iter()
265                                .filter_map(|item| {
266                                    if let serde_json::Value::Array(pair) = item {
267                                        if pair.len() == 2 {
268                                            return Some((
269                                                from_json(pair[0].clone()),
270                                                from_json(pair[1].clone()),
271                                            ));
272                                        }
273                                    }
274                                    None
275                                })
276                                .collect();
277                            return Value::BagKV(pairs);
278                        }
279                    }
280                    "bind" => {
281                        if let (Some(k), Some(v)) = (obj.get("$key"), obj.get("$val")) {
282                            return Value::Bind(
283                                Box::new(from_json(k.clone())),
284                                Box::new(from_json(v.clone())),
285                            );
286                        }
287                    }
288                    "some" => {
289                        if let Some(inner) = obj.get("$value") {
290                            return Value::Some_(Box::new(from_json(inner.clone())));
291                        }
292                    }
293                    "none" => return Value::None_,
294                    "ok" => {
295                        if let Some(inner) = obj.get("$value") {
296                            return Value::Ok_(Box::new(from_json(inner.clone())));
297                        }
298                    }
299                    "fail" => {
300                        let code = obj
301                            .get("$code")
302                            .and_then(|value| value.as_str())
303                            .unwrap_or("")
304                            .to_string();
305                        let msg = obj
306                            .get("$msg")
307                            .and_then(|value| value.as_str())
308                            .unwrap_or("")
309                            .to_string();
310                        return Value::Fail_(code, msg);
311                    }
312                    _ => {}
313                }
314            }
315            Value::Map(obj.into_iter().map(|(k, v)| (k, from_json(v))).collect())
316        }
317    }
318}
319
320fn canonical_json_text(value: &serde_json::Value) -> String {
321    match value {
322        serde_json::Value::Null | serde_json::Value::Bool(_) | serde_json::Value::Number(_) | serde_json::Value::String(_) => {
323            serde_json::to_string(value).expect("canonical JSON rendering should succeed")
324        }
325        serde_json::Value::Array(items) => {
326            let rendered_items = items
327                .iter()
328                .map(canonical_json_text)
329                .collect::<Vec<_>>()
330                .join(",");
331            format!("[{rendered_items}]")
332        }
333        serde_json::Value::Object(entries) => {
334            let mut sorted_entries: Vec<_> = entries.iter().collect();
335            sorted_entries.sort_by(|(left_key, _), (right_key, _)| left_key.cmp(right_key));
336            let rendered_entries = sorted_entries
337                .into_iter()
338                .map(|(key, value)| {
339                    format!(
340                        "{}:{}",
341                        serde_json::to_string(key)
342                            .expect("canonical JSON key rendering should succeed"),
343                        canonical_json_text(value)
344                    )
345                })
346                .collect::<Vec<_>>()
347                .join(",");
348            format!("{{{rendered_entries}}}")
349        }
350    }
351}
352
353fn canonicalize_set_items(items: Vec<Value>) -> Vec<serde_json::Value> {
354    let mut unique = Vec::new();
355    for item in items {
356        if !unique.iter().any(|existing| existing == &item) {
357            unique.push(item);
358        }
359    }
360
361    let mut rendered = unique.into_iter().map(to_json).collect::<Vec<_>>();
362    rendered.sort_by_key(canonical_json_text);
363    rendered
364}
365
366fn canonicalize_bag_items(items: Vec<Value>) -> Vec<serde_json::Value> {
367    let mut rendered = items.into_iter().map(to_json).collect::<Vec<_>>();
368    rendered.sort_by_key(canonical_json_text);
369    rendered
370}
371
372fn canonicalize_map_entries(entries: Vec<(String, Value)>) -> Vec<(String, serde_json::Value)> {
373    let mut mapped_entries: Vec<(String, serde_json::Value)> =
374        entries.into_iter().map(|(key, value)| (key, to_json(value))).collect();
375    mapped_entries.sort_by(|(left_key, _), (right_key, _)| left_key.cmp(right_key));
376    mapped_entries
377}
378
379fn canonicalize_bagkv_items(pairs: Vec<(Value, Value)>) -> Vec<serde_json::Value> {
380    let mut rendered = pairs
381        .into_iter()
382        .map(|(key, value)| serde_json::json!([to_json(key), to_json(value)]))
383        .collect::<Vec<_>>();
384    rendered.sort_by_key(canonical_json_text);
385    rendered
386}
387
388pub fn to_json(v: Value) -> serde_json::Value {
389    match v {
390        Value::Null => serde_json::Value::Null,
391        Value::Bool(b) => serde_json::Value::Bool(b),
392        Value::Num(n) => n.to_json_value(),
393        Value::Str(s) => serde_json::Value::String(s),
394        Value::Bytes(bytes) => serde_json::json!({
395            "$type": "bytes",
396            "$base16": encode_base16(&bytes)
397        }),
398        Value::Seq(items) => serde_json::Value::Array(items.into_iter().map(to_json).collect()),
399        Value::Set(items) => serde_json::json!({
400            "$type": "set",
401            "$items": canonicalize_set_items(items)
402        }),
403        Value::Bag(items) => serde_json::json!({
404            "$type": "bag",
405            "$items": canonicalize_bag_items(items)
406        }),
407        Value::Map(entries) => {
408            let mapped_entries = canonicalize_map_entries(entries);
409            if should_wrap_map(&mapped_entries) {
410                let entries_obj: serde_json::Map<String, serde_json::Value> =
411                    mapped_entries.into_iter().collect();
412                serde_json::json!({
413                    "$type": "map",
414                    "$entries": entries_obj
415                })
416            } else {
417                let mut map = serde_json::Map::new();
418                for (k, v) in mapped_entries {
419                    map.insert(k, v);
420                }
421                serde_json::Value::Object(map)
422            }
423        }
424        Value::Prod(fields) => {
425            let mut map = serde_json::Map::new();
426            map.insert(
427                "$type".to_string(),
428                serde_json::Value::String("prod".to_string()),
429            );
430            let fields_map: serde_json::Map<String, serde_json::Value> =
431                fields.into_iter().map(|(k, v)| (k, to_json(v))).collect();
432            map.insert("$fields".to_string(), serde_json::Value::Object(fields_map));
433            serde_json::Value::Object(map)
434        }
435        Value::BagKV(pairs) => serde_json::json!({
436            "$type": "bagkv",
437            "$items": canonicalize_bagkv_items(pairs)
438        }),
439        Value::Bind(k, v) => serde_json::json!({
440            "$type": "bind",
441            "$key": to_json(*k),
442            "$val": to_json(*v)
443        }),
444        Value::Some_(inner) => serde_json::json!({
445            "$type": "some",
446            "$value": to_json(*inner)
447        }),
448        Value::None_ => serde_json::json!({"$type": "none"}),
449        Value::Ok_(inner) => serde_json::json!({
450            "$type": "ok",
451            "$value": to_json(*inner)
452        }),
453        Value::Fail_(code, msg) => serde_json::json!({
454            "$type": "fail",
455            "$code": code,
456            "$msg": msg
457        }),
458        Value::Lambda(_, _, _) => serde_json::json!({"$type": "fn"}),
459    }
460}
461
462fn should_wrap_map(entries: &[(String, serde_json::Value)]) -> bool {
463    let Some((_, type_value)) = entries.iter().find(|(key, _)| key == "$type") else {
464        return false;
465    };
466
467    match type_value {
468        serde_json::Value::String(tag) => reserved_json_tag(tag),
469        _ => false,
470    }
471}
472
473fn reserved_json_tag(tag: &str) -> bool {
474    matches!(
475        tag,
476        "map"
477            | "num"
478            | "bytes"
479            | "set"
480            | "bag"
481            | "prod"
482            | "bagkv"
483            | "bind"
484            | "some"
485            | "none"
486            | "ok"
487            | "fail"
488            | "fn"
489    )
490}
491
492#[cfg(test)]
493mod tests {
494    use super::*;
495
496    fn r(expr: &str) -> serde_json::Value {
497        run(expr, serde_json::Value::Null).expect("run failed")
498    }
499
500    fn ri(expr: &str, input: serde_json::Value) -> serde_json::Value {
501        run(expr, input).expect("run failed")
502    }
503
504    fn rib(expr: &str, binding_name: &str, input: serde_json::Value) -> serde_json::Value {
505        run_with_input_binding(expr, binding_name, input).expect("run failed")
506    }
507
508    fn assert_same(expr_a: &str, expr_b: &str) {
509        assert_eq!(r(expr_a), r(expr_b));
510    }
511
512    fn assert_same_input(expr_a: &str, expr_b: &str, input: serde_json::Value) {
513        assert_eq!(ri(expr_a, input.clone()), ri(expr_b, input));
514    }
515
516    fn assert_json_round_trip(value: Value, expected_json: serde_json::Value) {
517        let encoded = to_json(value.clone());
518        assert_eq!(encoded, expected_json);
519        assert_eq!(from_json(encoded), value);
520    }
521
522    fn num(src: &str) -> Value {
523        Value::Num(ExactNum::parse_literal(src).expect("valid exact number literal"))
524    }
525
526    fn bytes(src: &str) -> Value {
527        Value::Bytes(decode_base16(src).expect("valid base16 bytes literal"))
528    }
529
530    #[test]
531    fn test_null_literal() {
532        assert_eq!(r("null;"), serde_json::Value::Null);
533    }
534
535    #[test]
536    fn test_bool_literals() {
537        assert_eq!(r("true;"), serde_json::Value::Bool(true));
538        assert_eq!(r("false;"), serde_json::Value::Bool(false));
539    }
540
541    #[test]
542    fn test_num_arithmetic() {
543        assert_eq!(r("1 + 2;"), serde_json::json!(3));
544        assert_eq!(r("10 - 3;"), serde_json::json!(7));
545        assert_eq!(r("4 * 5;"), serde_json::json!(20));
546        assert_eq!(r("10 / 2;"), serde_json::json!(5));
547        assert_eq!(r("1 / 0;"), serde_json::json!({"$type": "fail", "$code": "t_sda_div_by_zero", "$msg": "division by zero"}));
548    }
549
550    #[test]
551    fn test_non_terminating_rational_uses_wrapper() {
552        assert_eq!(
553            r("1 / 3;"),
554            serde_json::json!({
555                "$type": "num",
556                "$value": "1/3"
557            })
558        );
559    }
560
561    #[test]
562    fn test_numeric_wrapper_round_trips_exactly() {
563        let result = ri(
564            "input = 1 / 3;",
565            serde_json::json!({
566                "$type": "num",
567                "$value": "1/3"
568            }),
569        );
570        assert_eq!(result, serde_json::Value::Bool(true));
571    }
572
573    #[test]
574    fn test_public_run_no_longer_binds_placeholder() {
575        let result = run("_;", serde_json::json!({"name": "steve"})).expect("run failed");
576        assert_eq!(
577            result,
578            serde_json::json!({
579                "$type": "fail",
580                "$code": "t_sda_unbound_placeholder",
581                "$msg": "unbound placeholder"
582            })
583        );
584    }
585
586    #[test]
587    fn test_unbound_name_is_stable() {
588        assert_eq!(
589            r("missing;"),
590            serde_json::json!({"$type": "fail", "$code": "t_sda_unbound_name", "$msg": "unbound name"})
591        );
592    }
593
594    #[test]
595    fn test_not_callable_is_stable() {
596        assert_eq!(
597            r("1(2);"),
598            serde_json::json!({"$type": "fail", "$code": "t_sda_not_callable", "$msg": "not callable"})
599        );
600    }
601
602    #[test]
603    fn test_arity_mismatch_is_stable() {
604        assert_eq!(
605            r("(x => x)(1, 2);"),
606            serde_json::json!({"$type": "fail", "$code": "t_sda_arity_mismatch", "$msg": "arity mismatch"})
607        );
608        assert_eq!(
609            r("normalizeUnique();"),
610            serde_json::json!({"$type": "fail", "$code": "t_sda_arity_mismatch", "$msg": "arity mismatch"})
611        );
612    }
613
614    #[test]
615    fn test_custom_input_binding_name() {
616        let result = rib(r#"root<"name">!;"#, "root", serde_json::json!({"name": "Ada"}));
617        assert_eq!(result, serde_json::json!({"$type": "ok", "$value": "Ada"}));
618    }
619
620    #[test]
621    fn test_string_concat() {
622        assert_eq!(r(r#""hello" ++ " world";"#), serde_json::json!("hello world"));
623    }
624
625    #[test]
626    fn test_seq_literal() {
627        let result = r("seq[1, 2, 3];");
628        assert_eq!(result, serde_json::json!([1, 2, 3]));
629    }
630
631    #[test]
632    fn test_set_literal() {
633        let result = r("set{1, 2, 2, 3};");
634        if let serde_json::Value::Object(obj) = &result {
635            assert_eq!(obj["$type"], serde_json::json!("set"));
636            if let serde_json::Value::Array(items) = &obj["$items"] {
637                assert_eq!(items.len(), 3);
638            }
639        }
640    }
641
642    #[test]
643    fn test_map_literal() {
644        let result = r(r#"map{"a" -> 1, "b" -> 2};"#);
645        assert_eq!(result, serde_json::json!({"a": 1, "b": 2}));
646    }
647
648    #[test]
649    fn test_bytes_literal_and_equality() {
650        assert_eq!(
651            r(r#"Bytes("00ff");"#),
652            serde_json::json!({"$type": "bytes", "$base16": "00ff"})
653        );
654        assert_eq!(r(r#"Bytes("00ff") = Bytes("00FF");"#), serde_json::json!(true));
655    }
656
657    #[test]
658    fn test_bytes_literal_rejects_invalid_hex() {
659        let err = run(r#"Bytes("0fg");"#, serde_json::Value::Null).unwrap_err();
660        assert!(matches!(err, SdaError::Parse(ParseError::InvalidBytesLiteral { .. })));
661    }
662
663    #[test]
664    fn test_reserved_placeholder_in_let_is_stable_parse_error() {
665        let err = run("let _ = 1;", serde_json::Value::Null).unwrap_err();
666        assert!(matches!(err, SdaError::Parse(ParseError::ReservedPlaceholder)));
667    }
668
669    #[test]
670    fn test_reserved_placeholder_as_lambda_param_is_stable_parse_error() {
671        let err = run("_ => 1;", serde_json::Value::Null).unwrap_err();
672        assert!(matches!(err, SdaError::Parse(ParseError::ReservedPlaceholder)));
673    }
674
675    #[test]
676    fn test_invalid_map_key_is_stable_parse_error() {
677        let err = run(r#"Map{a -> 1};"#, serde_json::Value::Null).unwrap_err();
678        assert!(matches!(err, SdaError::Parse(ParseError::InvalidMapKey)));
679    }
680
681    #[test]
682    fn test_invalid_bagkv_key_is_stable_parse_error() {
683        let err = run(r#"BagKV{1 -> 1};"#, serde_json::Value::Null).unwrap_err();
684        assert!(matches!(err, SdaError::Parse(ParseError::InvalidBagkvKey)));
685    }
686
687    #[test]
688    fn test_plain_map_json_bridge_stays_plain_object() {
689        assert_json_round_trip(
690            Value::Map(vec![
691                ("a".to_string(), num("1")),
692                ("b".to_string(), Value::Str("x".to_string())),
693            ]),
694            serde_json::json!({"a": 1, "b": "x"}),
695        );
696    }
697
698    #[test]
699    fn test_reserved_tag_map_uses_explicit_map_wrapper() {
700        assert_json_round_trip(
701            Value::Map(vec![
702                ("$type".to_string(), Value::Str("set".to_string())),
703                (
704                    "$items".to_string(),
705                    Value::Seq(vec![num("1"), num("2")]),
706                ),
707            ]),
708            serde_json::json!({
709                "$type": "map",
710                "$entries": {
711                    "$type": "set",
712                    "$items": [1, 2]
713                }
714            }),
715        );
716    }
717
718    #[test]
719    fn test_unknown_tag_object_remains_plain_map() {
720        let value = from_json(serde_json::json!({
721            "$type": "custom",
722            "x": 1
723        }));
724        assert_eq!(
725            value,
726            Value::Map(vec![
727                ("$type".to_string(), Value::Str("custom".to_string())),
728                ("x".to_string(), num("1")),
729            ])
730        );
731    }
732
733    #[test]
734    fn test_reserved_bytes_tag_map_uses_explicit_map_wrapper() {
735        assert_json_round_trip(
736            Value::Map(vec![
737                ("$type".to_string(), Value::Str("bytes".to_string())),
738                ("$base16".to_string(), Value::Str("not-a-wrapper".to_string())),
739            ]),
740            serde_json::json!({
741                "$type": "map",
742                "$entries": {
743                    "$type": "bytes",
744                    "$base16": "not-a-wrapper"
745                }
746            }),
747        );
748    }
749
750    #[test]
751    fn test_wrapper_json_bridge_round_trips() {
752        assert_json_round_trip(
753            bytes("00ff"),
754            serde_json::json!({"$type": "bytes", "$base16": "00ff"}),
755        );
756        assert_json_round_trip(
757            Value::Set(vec![Value::Str("a".to_string()), Value::Str("b".to_string())]),
758            serde_json::json!({"$type": "set", "$items": ["a", "b"]}),
759        );
760        assert_json_round_trip(
761            Value::Bag(vec![num("1"), num("1")]),
762            serde_json::json!({"$type": "bag", "$items": [1, 1]}),
763        );
764        assert_json_round_trip(
765            Value::Prod(vec![("name".to_string(), Value::Str("Ada".to_string()))]),
766            serde_json::json!({"$type": "prod", "$fields": {"name": "Ada"}}),
767        );
768        assert_json_round_trip(
769            Value::BagKV(vec![(Value::Str("k".to_string()), num("2"))]),
770            serde_json::json!({"$type": "bagkv", "$items": [["k", 2]]}),
771        );
772        assert_json_round_trip(
773            Value::Bind(
774                Box::new(Value::Str("k".to_string())),
775                Box::new(num("2")),
776            ),
777            serde_json::json!({"$type": "bind", "$key": "k", "$val": 2}),
778        );
779        assert_json_round_trip(
780            Value::Some_(Box::new(Value::Str("x".to_string()))),
781            serde_json::json!({"$type": "some", "$value": "x"}),
782        );
783        assert_json_round_trip(Value::None_, serde_json::json!({"$type": "none"}));
784        assert_json_round_trip(
785            Value::Ok_(Box::new(Value::Bool(true))),
786            serde_json::json!({"$type": "ok", "$value": true}),
787        );
788        assert_json_round_trip(
789            Value::Fail_("code".to_string(), "msg".to_string()),
790            serde_json::json!({"$type": "fail", "$code": "code", "$msg": "msg"}),
791        );
792    }
793
794    #[test]
795    fn test_map_equality_is_extensional() {
796        assert_eq!(
797            r(r#"Map{"a" -> 1, "b" -> 2} = Map{"b" -> 2, "a" -> 1};"#),
798            serde_json::Value::Bool(true)
799        );
800    }
801
802    #[test]
803    fn test_bag_equality_is_extensional_with_multiplicity() {
804        assert_eq!(
805            r(r#"Bag{1, 2, 1} = Bag{1, 1, 2};"#),
806            serde_json::Value::Bool(true)
807        );
808        assert_eq!(
809            r(r#"Bag{1, 2, 1} = Bag{1, 2, 2};"#),
810            serde_json::Value::Bool(false)
811        );
812    }
813
814    #[test]
815    fn test_set_union_is_canonical_and_commutative() {
816        let left_first = r("Set{3, 1} union Set{2, 1};");
817        let right_first = r("Set{2, 1} union Set{3, 1};");
818        let expected = serde_json::json!({"$type": "set", "$items": [1, 2, 3]});
819        assert_eq!(left_first, expected);
820        assert_eq!(right_first, expected);
821    }
822
823    #[test]
824    fn test_set_intersection_is_canonical_and_idempotent() {
825        let intersection = r("Set{3, 1, 2} inter Set{2, 3, 4};");
826        let idempotent = r("Set{3, 1, 2} inter Set{3, 1, 2};");
827        assert_eq!(intersection, serde_json::json!({"$type": "set", "$items": [2, 3]}));
828        assert_eq!(idempotent, serde_json::json!({"$type": "set", "$items": [1, 2, 3]}));
829    }
830
831    #[test]
832    fn test_set_difference_is_canonical_and_self_difference_is_empty() {
833        assert_eq!(
834            r("Set{3, 1, 2} diff Set{2};"),
835            serde_json::json!({"$type": "set", "$items": [1, 3]})
836        );
837        assert_eq!(
838            r("Set{3, 1, 2} diff Set{3, 1, 2};"),
839            serde_json::json!({"$type": "set", "$items": []})
840        );
841    }
842
843    #[test]
844    fn test_bag_union_is_canonical_and_commutative() {
845        let left_first = r("Bag{3, 1, 2} bunion Bag{2, 1};");
846        let right_first = r("Bag{2, 1} bunion Bag{3, 1, 2};");
847        let expected = serde_json::json!({"$type": "bag", "$items": [1, 1, 2, 2, 3]});
848        assert_eq!(left_first, expected);
849        assert_eq!(right_first, expected);
850    }
851
852    #[test]
853    fn test_bag_difference_is_canonical_and_floors_at_zero() {
854        assert_eq!(
855            r("Bag{3, 1, 2, 2, 1} bdiff Bag{2, 1, 4};"),
856            serde_json::json!({"$type": "bag", "$items": [1, 2, 3]})
857        );
858        assert_eq!(
859            r("Bag{1, 1} bdiff Bag{1, 1, 1};"),
860            serde_json::json!({"$type": "bag", "$items": []})
861        );
862    }
863
864    #[test]
865    fn test_set_algebra_is_associative_where_expected() {
866        assert_eq!(
867            r("(Set{3, 1} union Set{2}) union Set{4, 1};"),
868            r("Set{3, 1} union (Set{2} union Set{4, 1});")
869        );
870        assert_eq!(
871            r("(Set{3, 1, 2} inter Set{2, 3, 4}) inter Set{3, 5};"),
872            r("Set{3, 1, 2} inter (Set{2, 3, 4} inter Set{3, 5});")
873        );
874    }
875
876    #[test]
877    fn test_bag_union_is_associative() {
878        assert_eq!(
879            r("(Bag{3, 1} bunion Bag{2}) bunion Bag{2, 1};"),
880            r("Bag{3, 1} bunion (Bag{2} bunion Bag{2, 1});")
881        );
882    }
883
884    #[test]
885    fn test_prod_equality_is_extensional() {
886        assert_eq!(
887            r(r#"Prod{a: 1, b: 2} = Prod{b: 2, a: 1};"#),
888            serde_json::Value::Bool(true)
889        );
890    }
891
892    #[test]
893    fn test_let_binding() {
894        assert_eq!(r("let x = 42; x;"), serde_json::json!(42));
895    }
896
897    #[test]
898    fn test_lambda_and_call() {
899        assert_eq!(r("let f = x => x + 1; f(5);"), serde_json::json!(6));
900    }
901
902    #[test]
903    fn test_pipe() {
904        assert_eq!(r("5 |> _ + 1;"), serde_json::json!(6));
905    }
906
907    #[test]
908    fn test_comprehension() {
909        let result = r("{ x | x in seq[1, 2, 3] };");
910        assert_eq!(result, serde_json::json!([1, 2, 3]));
911    }
912
913    #[test]
914    fn test_comparison() {
915        assert_eq!(r("1 < 2;"), serde_json::Value::Bool(true));
916        assert_eq!(r("2 > 3;"), serde_json::Value::Bool(false));
917        assert_eq!(r("1 = 1;"), serde_json::Value::Bool(true));
918        assert_eq!(r("1 != 2;"), serde_json::Value::Bool(true));
919    }
920
921    #[test]
922    fn test_some_none() {
923        let result = r("some(42);");
924        assert_eq!(result, serde_json::json!({"$type": "some", "$value": 42}));
925        let result2 = r("none;");
926        assert_eq!(result2, serde_json::json!({"$type": "none"}));
927    }
928
929    #[test]
930    fn test_type_of() {
931        assert_eq!(r(r#"typeOf(null);"#), serde_json::json!("null"));
932        assert_eq!(r(r#"typeOf(42);"#), serde_json::json!("num"));
933        assert_eq!(r(r#"typeOf("hello");"#), serde_json::json!("str"));
934        assert_eq!(r(r#"typeOf(Bytes("00ff"));"#), serde_json::json!("bytes"));
935    }
936
937    #[test]
938    fn test_unicode_ascii_parity_membership_and_logic() {
939        assert_same("2 in Set{1, 2, 3};", "2 ∈ Set{1, 2, 3};");
940        assert_same("true and false;", "true ∧ false;");
941        assert_same("true or false;", "true ∨ false;");
942        assert_same("not false;", "¬false;");
943    }
944
945    #[test]
946    fn test_unicode_ascii_parity_comparisons() {
947        assert_same("1 != 2;", "1 ≠ 2;");
948        assert_same("1 <= 2;", "1 ≤ 2;");
949        assert_same("2 >= 1;", "2 ≥ 1;");
950    }
951
952    #[test]
953    fn test_unicode_ascii_parity_lambda_and_placeholder() {
954        assert_same("let f = x => x + 1; f(5);", "let f = x ↦ x + 1; f(5);");
955        assert_same("5 |> _ + 1;", "5 |> • + 1;");
956    }
957
958    #[test]
959    fn test_unicode_ascii_parity_selector_and_comprehension_bar() {
960        assert_same_input(
961            r#"input<"name">!;"#,
962            r#"input⟨"name"⟩!;"#,
963            serde_json::json!({"name": "Ada"}),
964        );
965        assert_same(
966            r#"{ x | x in Seq[1, 2, 3] | x >= 2 };"#,
967            r#"{ x ∣ x ∈ Seq[1, 2, 3] ∣ x ≥ 2 };"#,
968        );
969    }
970
971    #[test]
972    fn test_unicode_ascii_parity_binding_and_bag_operators() {
973        assert_same(r#"Map{"a" -> 1};"#, r#"Map{"a" → 1};"#);
974        assert_same(r#"BagKV{"a" -> 1};"#, r#"BagKV{"a" → 1};"#);
975        assert_same("Bag{1, 2} bunion Bag{2, 3};", "Bag{1, 2} ⊎ Bag{2, 3};");
976        assert_same("Bag{1, 2, 2} bdiff Bag{2};", "Bag{1, 2, 2} ⊖ Bag{2};");
977    }
978
979    #[test]
980    fn test_line_comments_are_ignored() {
981        assert_eq!(r("1 + ;; comment\n 2;"), serde_json::json!(3));
982        assert_eq!(r("Seq[1, ;; keep going\n 2, 3];"), serde_json::json!([1, 2, 3]));
983    }
984
985    #[test]
986    fn test_whitespace_is_insensitive() {
987        assert_eq!(
988            r(" \n\t let   x = 1 ; \n\t x   +   2 ; \n"),
989            serde_json::json!(3)
990        );
991        assert_eq!(
992            ri(" \n input < \"name\" > ! ; \n", serde_json::json!({"name": "Ada"})),
993            serde_json::json!({"$type": "ok", "$value": "Ada"})
994        );
995    }
996
997    #[test]
998    fn test_required_string_escapes() {
999        assert_eq!(r(r#""line\nindent\tquote\"slash\\";"#), serde_json::json!("line\nindent\tquote\"slash\\"));
1000        assert_eq!(r(r#"";; not a comment";"#), serde_json::json!(";; not a comment"));
1001    }
1002
1003    #[test]
1004    fn test_keys_returns_set() {
1005        let result = r(r#"keys(Map{"b" -> 2, "a" -> 1});"#);
1006        assert_eq!(
1007            result,
1008            serde_json::json!({
1009                "$type": "set",
1010                "$items": ["a", "b"]
1011            })
1012        );
1013    }
1014
1015    #[test]
1016    fn test_values_map_uses_canonical_key_order() {
1017        let result = r(r#"values(Map{"b" -> 2, "a" -> 1, "c" -> 3});"#);
1018        assert_eq!(result, serde_json::json!([1, 2, 3]));
1019    }
1020
1021    #[test]
1022    fn test_values_map_from_json_input_uses_canonical_key_order() {
1023        let result = ri(r#"values(input);"#, serde_json::json!({"z": 3, "a": 1, "m": 2}));
1024        assert_eq!(result, serde_json::json!([1, 2, 3]));
1025    }
1026
1027    #[test]
1028    fn test_values_prod_is_not_a_standalone_helper_contract() {
1029        assert_eq!(
1030            r(r#"values(Prod{b: 2, a: 1});"#),
1031            serde_json::json!({"$type": "fail", "$code": "t_sda_wrong_shape", "$msg": "wrong shape"})
1032        );
1033    }
1034
1035    #[test]
1036    fn test_select() {
1037        let result = ri(r#"input<"name">;"#, serde_json::json!({"name": "Alice"}));
1038        assert_eq!(
1039            result,
1040            serde_json::json!({"$type": "fail", "$code": "t_sda_wrong_shape", "$msg": "wrong shape"})
1041        );
1042    }
1043
1044    #[test]
1045    fn test_total_selector_on_prod_is_allowed() {
1046        let result = r(r#"Prod{name: "Alice"}<name>;"#);
1047        assert_eq!(result, serde_json::json!("Alice"));
1048    }
1049
1050    #[test]
1051    fn test_total_selector_on_prod_missing_field_returns_unknown_field() {
1052        let result = r(r#"Prod{name: "Alice"}<age>;"#);
1053        assert_eq!(
1054            result,
1055            serde_json::json!({"$type": "fail", "$code": "t_sda_unknown_field", "$msg": "unknown field"})
1056        );
1057    }
1058
1059    #[test]
1060    fn test_total_selector_on_map_is_wrong_shape() {
1061        let result = ri(r#"input<"name">;"#, serde_json::json!({"name": "Alice"}));
1062        assert_eq!(
1063            result,
1064            serde_json::json!({"$type": "fail", "$code": "t_sda_wrong_shape", "$msg": "wrong shape"})
1065        );
1066    }
1067
1068    #[test]
1069    fn test_total_selector_on_bagkv_is_wrong_shape() {
1070        let result = r(r#"BagKV{"k" -> 1}<"k">;"#);
1071        assert_eq!(
1072            result,
1073            serde_json::json!({"$type": "fail", "$code": "t_sda_wrong_shape", "$msg": "wrong shape"})
1074        );
1075    }
1076
1077    #[test]
1078    fn test_optional_selector_on_non_keyed_bag_is_wrong_shape() {
1079        let result = r(r#"Bag{1, 2}<"k">?;"#);
1080        assert_eq!(
1081            result,
1082            serde_json::json!({"$type": "fail", "$code": "t_sda_wrong_shape", "$msg": "wrong shape"})
1083        );
1084    }
1085
1086    #[test]
1087    fn test_required_selector_on_non_keyed_bag_is_wrong_shape() {
1088        let result = r(r#"Bag{1, 2}<"k">!;"#);
1089        assert_eq!(
1090            result,
1091            serde_json::json!({"$type": "fail", "$code": "t_sda_wrong_shape", "$msg": "wrong shape"})
1092        );
1093    }
1094
1095    #[test]
1096    fn test_optional_selector_on_prod_is_wrong_shape() {
1097        let result = r(r#"Prod{name: "Alice"}<name>?;"#);
1098        assert_eq!(
1099            result,
1100            serde_json::json!({"$type": "fail", "$code": "t_sda_wrong_shape", "$msg": "wrong shape"})
1101        );
1102    }
1103
1104    #[test]
1105    fn test_required_selector_on_prod_is_wrong_shape() {
1106        let result = r(r#"Prod{name: "Alice"}<name>!;"#);
1107        assert_eq!(
1108            result,
1109            serde_json::json!({"$type": "fail", "$code": "t_sda_wrong_shape", "$msg": "wrong shape"})
1110        );
1111    }
1112
1113    #[test]
1114    fn test_keys_prod_is_not_a_standalone_helper_contract() {
1115        assert_eq!(
1116            r(r#"keys(Prod{b: 2, a: 1});"#),
1117            serde_json::json!({"$type": "fail", "$code": "t_sda_wrong_shape", "$msg": "wrong shape"})
1118        );
1119    }
1120
1121    #[test]
1122    fn test_count_seq_is_not_a_standalone_helper_contract() {
1123        assert_eq!(
1124            r(r#"count(1, Seq[1, 2, 1]);"#),
1125            serde_json::json!({"$type": "fail", "$code": "t_sda_wrong_shape", "$msg": "wrong shape"})
1126        );
1127    }
1128
1129    #[test]
1130    fn test_or_else_opt_preserves_some_wrapper() {
1131        assert_eq!(
1132            r(r#"orElseOpt(Some(1), Some(2));"#),
1133            serde_json::json!({"$type": "some", "$value": 1})
1134        );
1135        assert_eq!(
1136            r(r#"orElseOpt(None, Some(2));"#),
1137            serde_json::json!({"$type": "some", "$value": 2})
1138        );
1139    }
1140
1141    #[test]
1142    fn test_or_else_res_preserves_ok_wrapper() {
1143        assert_eq!(
1144            r(r#"orElseRes(Ok(1), Ok(2));"#),
1145            serde_json::json!({"$type": "ok", "$value": 1})
1146        );
1147        assert_eq!(
1148            r(r#"orElseRes(Fail("x", "y"), Ok(2));"#),
1149            serde_json::json!({"$type": "ok", "$value": 2})
1150        );
1151    }
1152
1153    #[test]
1154    fn test_bagkv_equality_is_extensional_with_multiplicity() {
1155        assert_eq!(
1156            r(r#"BagKV{"a" -> 1, "b" -> 2, "a" -> 1} = BagKV{"b" -> 2, "a" -> 1, "a" -> 1};"#),
1157            serde_json::Value::Bool(true)
1158        );
1159        assert_eq!(
1160            r(r#"BagKV{"a" -> 1, "b" -> 2, "a" -> 1} = BagKV{"b" -> 2, "a" -> 1};"#),
1161            serde_json::Value::Bool(false)
1162        );
1163    }
1164
1165    #[test]
1166    fn test_bind_option_result_equality_is_pointwise() {
1167        assert_eq!(r(r#"Bind("a", 1) = Bind("a", 1);"#), serde_json::Value::Bool(true));
1168        assert_eq!(r(r#"Some(Null) = None;"#), serde_json::Value::Bool(false));
1169        assert_eq!(r(r#"Ok(1) = Ok(1);"#), serde_json::Value::Bool(true));
1170        assert_eq!(r(r#"Ok(1) = Fail("x", "y");"#), serde_json::Value::Bool(false));
1171    }
1172
1173    #[test]
1174    fn test_function_values_are_not_comparable() {
1175        assert_eq!(
1176            r(r#"let f = x => x; f = f;"#),
1177            serde_json::json!({"$type": "fail", "$code": "t_sda_wrong_shape", "$msg": "wrong shape"})
1178        );
1179
1180        assert_eq!(
1181            r(r#"Set{x => x};"#),
1182            serde_json::json!({"$type": "fail", "$code": "t_sda_wrong_shape", "$msg": "wrong shape"})
1183        );
1184    }
1185
1186    #[test]
1187    fn test_bagkv_optional_missing_returns_none() {
1188        let result = r(r#"BagKV{"k" -> 1}<"missing">?;"#);
1189        assert_eq!(result, serde_json::json!({"$type": "none"}));
1190    }
1191
1192    #[test]
1193    fn test_bagkv_required_missing_returns_fail() {
1194        let result = r(r#"BagKV{"k" -> 1}<"missing">!;"#);
1195        assert_eq!(
1196            result,
1197            serde_json::json!({"$type": "fail", "$code": "t_sda_missing_key", "$msg": "missing key"})
1198        );
1199    }
1200
1201    #[test]
1202    fn test_placeholder_scoping_pipe() {
1203        assert_eq!(r("5 |> _ + 1;"), serde_json::json!(6));
1204    }
1205
1206    #[test]
1207    fn test_required_selector_ok() {
1208        let result = ri(r#"input<"name">!;"#, serde_json::json!({"name": "steve"}));
1209        assert_eq!(result, serde_json::json!({"$type": "ok", "$value": "steve"}));
1210    }
1211
1212    #[test]
1213    fn test_required_selector_missing() {
1214        let result = ri(r#"input<"missing">!;"#, serde_json::json!({"name": "steve"}));
1215        assert_eq!(
1216            result,
1217            serde_json::json!({"$type": "fail", "$code": "t_sda_missing_key", "$msg": "missing key"})
1218        );
1219    }
1220
1221    #[test]
1222    fn test_optional_selector_present() {
1223        let result = ri(r#"input<"name">?;"#, serde_json::json!({"name": "steve"}));
1224        assert_eq!(result, serde_json::json!({"$type": "some", "$value": "steve"}));
1225    }
1226
1227    #[test]
1228    fn test_optional_selector_missing() {
1229        let result = ri(r#"input<"missing">?;"#, serde_json::json!({"name": "steve"}));
1230        assert_eq!(result, serde_json::json!({"$type": "none"}));
1231    }
1232
1233    #[test]
1234    fn test_null_vs_absence_some_null() {
1235        let result = ri(r#"input<"x">?;"#, serde_json::json!({"x": null}));
1236        assert_eq!(result, serde_json::json!({"$type": "some", "$value": null}));
1237    }
1238
1239    #[test]
1240    fn test_null_vs_absence_none() {
1241        let result = ri(r#"input<"x">?;"#, serde_json::json!({}));
1242        assert_eq!(result, serde_json::json!({"$type": "none"}));
1243    }
1244
1245    #[test]
1246    fn test_bagkv_duplicate_optional() {
1247        let result = r(r#"BagKV{"k" -> 1, "k" -> 2}<"k">?;"#);
1248        assert_eq!(result, serde_json::json!({"$type": "none"}));
1249    }
1250
1251    #[test]
1252    fn test_bagkv_duplicate_required() {
1253        let result = r(r#"BagKV{"k" -> 1, "k" -> 2}<"k">!;"#);
1254        assert_eq!(
1255            result,
1256            serde_json::json!({"$type": "fail", "$code": "t_sda_duplicate_key", "$msg": "duplicate key"})
1257        );
1258    }
1259
1260    #[test]
1261    fn test_normalize_unique_ok() {
1262        let result = r(r#"normalizeUnique(BagKV{"a" -> 1, "b" -> 2});"#);
1263        assert_eq!(result, serde_json::json!({"$type": "ok", "$value": {"a": 1, "b": 2}}));
1264    }
1265
1266    #[test]
1267    fn test_normalize_unique_fail() {
1268        let result = r(r#"normalizeUnique(BagKV{"k" -> 1, "k" -> 2});"#);
1269        assert_eq!(
1270            result,
1271            serde_json::json!({"$type": "fail", "$code": "t_sda_duplicate_key", "$msg": "duplicate key"})
1272        );
1273    }
1274
1275    #[test]
1276    fn test_normalize_unique_wrong_shape_returns_fail() {
1277        let result = r(r#"normalizeUnique(Seq[1, 2]);"#);
1278        assert_eq!(
1279            result,
1280            serde_json::json!({"$type": "fail", "$code": "t_sda_wrong_shape", "$msg": "wrong shape"})
1281        );
1282    }
1283
1284    #[test]
1285    fn test_normalize_first() {
1286        let result = r(r#"normalizeFirst(BagKV{"k" -> 1, "k" -> 2});"#);
1287        assert_eq!(result, serde_json::json!({"k": 1}));
1288    }
1289
1290    #[test]
1291    fn test_normalize_first_wrong_shape_returns_fail() {
1292        let result = r(r#"normalizeFirst(Seq[1, 2]);"#);
1293        assert_eq!(
1294            result,
1295            serde_json::json!({"$type": "fail", "$code": "t_sda_wrong_shape", "$msg": "wrong shape"})
1296        );
1297    }
1298
1299    #[test]
1300    fn test_normalize_last() {
1301        let result = r(r#"normalizeLast(BagKV{"k" -> 1, "k" -> 2});"#);
1302        assert_eq!(result, serde_json::json!({"k": 2}));
1303    }
1304
1305    #[test]
1306    fn test_normalize_last_wrong_shape_returns_fail() {
1307        let result = r(r#"normalizeLast(Seq[1, 2]);"#);
1308        assert_eq!(
1309            result,
1310            serde_json::json!({"$type": "fail", "$code": "t_sda_wrong_shape", "$msg": "wrong shape"})
1311        );
1312    }
1313
1314    #[test]
1315    fn test_normalize_unique_non_string_bagkv_key_returns_fail() {
1316        let result = ri(
1317            r#"normalizeUnique(input);"#,
1318            serde_json::json!({
1319                "$type": "bagkv",
1320                "$items": [[1, 2]]
1321            }),
1322        );
1323        assert_eq!(
1324            result,
1325            serde_json::json!({"$type": "fail", "$code": "t_sda_wrong_shape", "$msg": "wrong shape"})
1326        );
1327    }
1328
1329    #[test]
1330    fn test_carrier_preservation_seq() {
1331        let result = ri(r#"{ x | x in input | x > 2 };"#, serde_json::json!([1, 2, 3, 4]));
1332        assert_eq!(result, serde_json::json!([3, 4]));
1333    }
1334
1335    #[test]
1336    fn test_carrier_preservation_set() {
1337        let result = r(r#"{ x | x in Set{1, 2, 3} | x > 1 };"#);
1338        if let serde_json::Value::Object(obj) = &result {
1339            assert_eq!(obj["$type"], serde_json::json!("set"));
1340            if let serde_json::Value::Array(items) = &obj["$items"] {
1341                assert_eq!(items.len(), 2);
1342            } else {
1343                panic!("expected $items array");
1344            }
1345        } else {
1346            panic!("expected set object, got {:?}", result);
1347        }
1348    }
1349
1350    #[test]
1351    fn test_carrier_preservation_bag() {
1352        let result = r(r#"{ x | x in Bag{1, 2, 2, 3} | x > 1 };"#);
1353        if let serde_json::Value::Object(obj) = &result {
1354            assert_eq!(obj["$type"], serde_json::json!("bag"));
1355            if let serde_json::Value::Array(items) = &obj["$items"] {
1356                assert_eq!(items.len(), 3);
1357            } else {
1358                panic!("expected $items array");
1359            }
1360        } else {
1361            panic!("expected bag object, got {:?}", result);
1362        }
1363    }
1364
1365    #[test]
1366    fn test_comprehension_with_yield() {
1367        let result = r("{ yield x * 2 | x in Seq[1, 2, 3] };");
1368        assert_eq!(result, serde_json::json!([2, 4, 6]));
1369    }
1370
1371    #[test]
1372    fn test_comprehension_with_general_shorthand_projection() {
1373        let result = r("{ x + 1 | x in Seq[1, 2, 3] };");
1374        assert_eq!(result, serde_json::json!([2, 3, 4]));
1375    }
1376
1377    #[test]
1378    fn test_bagkv_comprehension_filter_returns_bag_of_bindings() {
1379        let result = r(r#"{ b | b in BagKV{"a" -> 1, "b" -> 2} | b<key> = "b" };"#);
1380        assert_eq!(
1381            result,
1382            serde_json::json!({
1383                "$type": "bag",
1384                "$items": [
1385                    {"$type": "bind", "$key": "b", "$val": 2}
1386                ]
1387            })
1388        );
1389    }
1390
1391    #[test]
1392    fn test_bagkv_comprehension_projection_returns_bag() {
1393        let result = r(r#"{ b<val> | b in BagKV{"a" -> 1, "b" -> 2} };"#);
1394        assert_eq!(
1395            result,
1396            serde_json::json!({
1397                "$type": "bag",
1398                "$items": [1, 2]
1399            })
1400        );
1401    }
1402
1403    #[test]
1404    fn test_title_case_keywords() {
1405        assert_eq!(r("Seq[1, 2, 3];"), serde_json::json!([1, 2, 3]));
1406        assert_eq!(r(r#"Map{"a" -> 1};"#), serde_json::json!({"a": 1}));
1407    }
1408
1409    #[test]
1410    fn test_map_rejects_identifier_keys() {
1411        let err = run("Map{a -> 1};", serde_json::Value::Null).unwrap_err();
1412        assert!(matches!(err, SdaError::Parse(_)));
1413    }
1414
1415    #[test]
1416    fn test_bagkv_accepts_identifier_keys_as_selector_shorthand() {
1417        let result = r(r#"BagKV{content_type -> 1};"#);
1418        assert_eq!(
1419            result,
1420            serde_json::json!({
1421                "$type": "bagkv",
1422                "$items": [
1423                    ["content_type", 1]
1424                ]
1425            })
1426        );
1427    }
1428
1429    #[test]
1430    fn test_bagkv_rejects_non_selector_keys() {
1431        let err = run("BagKV{1 -> 2};", serde_json::Value::Null).unwrap_err();
1432        assert!(matches!(err, SdaError::Parse(_)));
1433    }
1434
1435    #[test]
1436    fn test_static_selector_literal_is_rejected() {
1437        let err = run("{a b};", serde_json::Value::Null).unwrap_err();
1438        assert!(matches!(err, SdaError::Parse(ParseError::SelectorNotStatic)));
1439    }
1440
1441    #[test]
1442    fn test_static_selector_duplicate_label_is_rejected() {
1443        let err = run("{a a};", serde_json::Value::Null).unwrap_err();
1444        assert!(matches!(err, SdaError::Parse(ParseError::DuplicateLabelInSelector)));
1445    }
1446
1447    #[test]
1448    fn test_invalid_comprehension_generator_binding_reports_generator_shape() {
1449        let err = run("{ 1 in Seq[1] };", serde_json::Value::Null).unwrap_err();
1450        match err {
1451            SdaError::Parse(ParseError::Expected(expected, _, _)) => {
1452                assert_eq!(expected, "generator expression `name in collection`");
1453            }
1454            other => panic!("expected generator-shape parse error, got {other:?}"),
1455        }
1456    }
1457
1458    #[test]
1459    fn test_format_source_normalizes_spacing_and_keywords() {
1460        let formatted = format_source(" let x=1+2; values( input ) ").unwrap();
1461        assert_eq!(formatted, "let x = 1 + 2;\nvalues(input);\n");
1462    }
1463
1464    #[test]
1465    fn test_format_source_canonicalizes_selectors_and_bagkv_keys() {
1466        let formatted = format_source(r#"BagKV{"two words" -> 1, key -> input<name>!};"#).unwrap();
1467        assert_eq!(formatted, "BagKV{\"two words\" -> 1, key -> input<\"name\">!};\n");
1468    }
1469
1470    #[test]
1471    fn test_format_source_preserves_precedence_for_lambda_and_pipe() {
1472        let formatted = format_source("1 |> (x => x + 1);").unwrap();
1473        assert_eq!(formatted, "1 |> (x => x + 1);\n");
1474    }
1475
1476    #[test]
1477    fn test_format_source_preserves_precedence_for_lambda_call_and_unary() {
1478        let formatted = format_source("(-(1 + 2)) + ((x => x + 1)(3));").unwrap();
1479        assert_eq!(formatted, "-(1 + 2) + (x => x + 1)(3);\n");
1480    }
1481
1482    #[test]
1483    fn test_format_source_preserves_right_associative_sensitive_grouping() {
1484        let formatted = format_source("1 - (2 - 3);").unwrap();
1485        assert_eq!(formatted, "1 - (2 - 3);\n");
1486    }
1487
1488    #[test]
1489    fn test_format_source_canonicalizes_comprehension_spacing() {
1490        let formatted = format_source("{yield x+1|x in Seq[1,2,3]|x>1 and x<3};").unwrap();
1491        assert_eq!(formatted, "{ yield x + 1 | x in Seq[1, 2, 3] | x > 1 and x < 3 };\n");
1492    }
1493
1494    #[test]
1495    fn test_format_source_canonicalizes_nested_selector_and_call_chains() {
1496        let formatted = format_source("f(input<name>!)(g((1 + 2) * 3));").unwrap();
1497        assert_eq!(formatted, "f(input<\"name\">!)(g((1 + 2) * 3));\n");
1498    }
1499
1500    #[test]
1501    fn test_format_source_canonicalizes_selector_after_call() {
1502        let formatted = format_source("(f(input))<name>?;").unwrap();
1503        assert_eq!(formatted, "f(input)<\"name\">?;\n");
1504    }
1505
1506    #[test]
1507    fn test_unbound_placeholder_outside_pipe() {
1508        use crate::eval::eval_program;
1509        let tokens = lexer::lex("•;").unwrap();
1510        let prog = parser::parse(tokens).unwrap();
1511        let mut env = crate::Env::new();
1512        let result = eval_program(&prog, &mut env).unwrap();
1513        assert_eq!(
1514            result,
1515            Some(Value::Fail_(
1516                "t_sda_unbound_placeholder".to_string(),
1517                "unbound placeholder".to_string(),
1518            ))
1519        );
1520    }
1521
1522    #[test]
1523    fn test_as_bag_kv_from_bag_of_bind() {
1524        use crate::eval::eval_program;
1525        let tokens = lexer::lex(r#"asBagKV(Bag{Bind("a", 1)});"#).unwrap();
1526        let prog = parser::parse(tokens).unwrap();
1527        let mut env = crate::Env::new();
1528        let result = eval_program(&prog, &mut env).unwrap();
1529        assert!(matches!(result, Some(Value::Ok_(_))));
1530    }
1531
1532    #[test]
1533    fn test_as_bag_kv_wrong_shape_not_bag() {
1534        let result = r(r#"asBagKV(Seq[1, 2]);"#);
1535        assert_eq!(
1536            result,
1537            serde_json::json!({"$type": "fail", "$code": "t_sda_wrong_shape", "$msg": "wrong shape"})
1538        );
1539    }
1540
1541    #[test]
1542    fn test_as_bag_kv_wrong_shape_non_bind_element() {
1543        let result = r(r#"asBagKV(Bag{1, 2});"#);
1544        assert_eq!(
1545            result,
1546            serde_json::json!({"$type": "fail", "$code": "t_sda_wrong_shape", "$msg": "wrong shape"})
1547        );
1548    }
1549}
1550
1551fn encode_base16(bytes: &[u8]) -> String {
1552    let mut out = String::with_capacity(bytes.len() * 2);
1553    for byte in bytes {
1554        use std::fmt::Write as _;
1555        write!(&mut out, "{byte:02x}").expect("write to string");
1556    }
1557    out
1558}
1559
1560fn decode_base16(src: &str) -> Result<Vec<u8>, String> {
1561    if src.len() % 2 != 0 {
1562        return Err("expected even-length base16 string".to_string());
1563    }
1564
1565    let mut out = Vec::with_capacity(src.len() / 2);
1566    let mut chars = src.chars();
1567    while let (Some(hi), Some(lo)) = (chars.next(), chars.next()) {
1568        let hi = hi
1569            .to_digit(16)
1570            .ok_or_else(|| "expected base16 digits only".to_string())?;
1571        let lo = lo
1572            .to_digit(16)
1573            .ok_or_else(|| "expected base16 digits only".to_string())?;
1574        out.push(((hi << 4) | lo) as u8);
1575    }
1576
1577    Ok(out)
1578}