1mod 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}