1use std::collections::BTreeMap;
48use std::fmt;
49use std::path::{Path, PathBuf};
50
51#[derive(Debug, Clone, PartialEq)]
56pub enum Value {
57 Null,
58 Bool(bool),
59 Int(i64),
60 Float(f64),
61 Str(String),
62 Array(Vec<Value>),
63 Object(BTreeMap<String, Value>),
65}
66
67impl Value {
68 pub fn get(&self, path: &str) -> Option<&Value> {
70 let mut cur = self;
71 for seg in path.split('.') {
72 match cur {
73 Value::Object(m) => cur = m.get(seg)?,
74 _ => return None,
75 }
76 }
77 Some(cur)
78 }
79 pub fn as_str(&self) -> Option<&str> {
81 match self {
82 Value::Str(s) => Some(s),
83 _ => None,
84 }
85 }
86}
87
88impl fmt::Display for Value {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 write!(f, "{}", to_sml(self))
91 }
92}
93
94#[derive(Debug, Clone, PartialEq)]
128pub enum TypeSpec {
129 Any,
131 ContractRef(String),
137 Str,
138 Int,
139 Num,
141 Bool,
142 Array(Box<TypeSpec>),
144 Enum(Vec<String>),
146}
147
148impl TypeSpec {
149 fn name(&self) -> String {
150 match self {
151 TypeSpec::Any => "any".into(),
152 TypeSpec::Str => "str".into(),
153 TypeSpec::Int => "int".into(),
154 TypeSpec::Num => "num".into(),
155 TypeSpec::Bool => "bool".into(),
156 TypeSpec::Array(inner) => format!("[{}]", inner.name()),
157 TypeSpec::Enum(vals) => format!("enum [{}]", vals.join(" ")),
158 TypeSpec::ContractRef(name) => name.clone(),
159 }
160 }
161}
162
163#[derive(Debug, Clone)]
165pub struct FieldSpec {
166 pub ty: TypeSpec,
167 pub required: bool,
169 pub default: Option<Value>,
171 pub min: Option<f64>,
173 pub max: Option<f64>,
175}
176
177#[derive(Debug, Clone)]
179pub struct Contract {
180 pub name: String,
181 pub fields: BTreeMap<String, FieldSpec>,
182 pub allow_extra: bool,
186}
187
188fn check_type(
191 contract: &str,
192 field: &str,
193 spec: &FieldSpec,
194 v: &Value,
195 contracts: &BTreeMap<String, Contract>,
196) -> Result<(), String> {
197 if let TypeSpec::ContractRef(ref_name) = &spec.ty {
199 return match v {
200 Value::Object(_) => {
201 let mut sub = match v {
202 Value::Object(m) => m.clone(),
203 _ => unreachable!(),
204 };
205 let target = contracts.get(ref_name).ok_or_else(|| {
206 format!(
207 "sml: 字段 `{}` 引用了未定义的契约 `{}`(契约 `{}`)",
208 field, ref_name, contract
209 )
210 })?;
211 apply_contract(target, &mut sub, contracts)?;
212 Ok(())
213 }
214 _ => Err(format!(
215 "sml: 字段 `{}` 应为块并按契约 `{}` 校验,实际为 {}(契约 `{}`)",
216 field,
217 ref_name,
218 value_kind(v),
219 contract
220 )),
221 };
222 }
223
224 let ok = match (&spec.ty, v) {
225 (TypeSpec::Any, _) => true,
226 (TypeSpec::Str, Value::Str(_)) => true,
227 (TypeSpec::Int, Value::Int(_)) => true,
228 (TypeSpec::Num, Value::Int(_)) | (TypeSpec::Num, Value::Float(_)) => true,
229 (TypeSpec::Bool, Value::Bool(_)) => true,
230 (TypeSpec::Enum(vals), Value::Str(s)) => vals.iter().any(|x| x == s),
231 (TypeSpec::Enum(vals), Value::Int(i)) => vals.iter().any(|x| x == &i.to_string()),
233 (TypeSpec::Array(inner), Value::Array(items)) => items.iter().all(|it| {
234 check_type(
235 contract,
236 field,
237 &FieldSpec { ty: (**inner).clone(), required: true, default: None, min: None, max: None },
238 it,
239 contracts,
240 )
241 .is_ok()
242 }),
243 _ => false,
244 };
245 if !ok {
246 return Err(format!(
247 "sml: 字段 `{}` 类型应为 {},实际为 {}(契约 `{}`)",
248 field,
249 spec.ty.name(),
250 value_kind(v),
251 contract
252 ));
253 }
254 if spec.min.is_some() || spec.max.is_some() {
256 let n = match v {
257 Value::Int(i) => Some(*i as f64),
258 Value::Float(f) => Some(*f),
259 _ => None,
260 };
261 if let Some(n) = n {
262 if let Some(lo) = spec.min {
263 if n < lo {
264 return Err(format!(
265 "sml: 字段 `{}` 值 {} 小于下界 {}(契约 `{}`)",
266 field, n, lo, contract
267 ));
268 }
269 }
270 if let Some(hi) = spec.max {
271 if n > hi {
272 return Err(format!(
273 "sml: 字段 `{}` 值 {} 大于上界 {}(契约 `{}`)",
274 field, n, hi, contract
275 ));
276 }
277 }
278 }
279 }
280 Ok(())
281}
282
283fn value_kind(v: &Value) -> &'static str {
284 match v {
285 Value::Null => "null",
286 Value::Bool(_) => "bool",
287 Value::Int(_) => "int",
288 Value::Float(_) => "float",
289 Value::Str(_) => "str",
290 Value::Array(_) => "array",
291 Value::Object(_) => "object",
292 }
293}
294
295fn apply_contract(
300 c: &Contract,
301 node: &mut BTreeMap<String, Value>,
302 contracts: &BTreeMap<String, Contract>,
303) -> Result<(), String> {
304 if !c.allow_extra {
307 for k in node.keys() {
308 if !c.fields.contains_key(k) {
309 return Err(format!(
310 "sml: 字段 `{}` 未在契约 `{}` 中声明(严格模式;如需允许额外字段请在契约名后写 `loose`)",
311 k, c.name
312 ));
313 }
314 }
315 }
316 for (k, spec) in &c.fields {
318 match node.get(k) {
319 None => {
320 if let Some(d) = &spec.default {
321 node.insert(k.clone(), d.clone());
322 } else if spec.required {
323 return Err(format!(
324 "sml: 字段 `{}` 必填但缺失(契约 `{}`)",
325 k, c.name
326 ));
327 }
328 }
329 Some(v) => {
330 if matches!(spec.ty, TypeSpec::ContractRef(_)) {
332 check_type(&c.name, k, spec, v, contracts)?;
335 let mut sub = match v {
336 Value::Object(m) => m.clone(),
337 _ => unreachable!("check_type 已保证为块"),
338 };
339 check_type_contract_ref(&c.name, k, spec, &mut sub, contracts)?;
340 node.insert(k.clone(), Value::Object(sub));
341 } else {
342 check_type(&c.name, k, spec, v, contracts)?;
343 }
344 }
345 }
346 }
347 Ok(())
348}
349
350fn check_type_contract_ref(
352 contract: &str,
353 field: &str,
354 spec: &FieldSpec,
355 sub: &mut BTreeMap<String, Value>,
356 contracts: &BTreeMap<String, Contract>,
357) -> Result<(), String> {
358 let ref_name = match &spec.ty {
359 TypeSpec::ContractRef(n) => n.clone(),
360 _ => return Ok(()),
361 };
362 let target = contracts.get(&ref_name).ok_or_else(|| {
363 format!(
364 "sml: 字段 `{}` 引用了未定义的契约 `{}`(契约 `{}`)",
365 field, ref_name, contract
366 )
367 })?;
368 check_type(contract, field, spec, &Value::Object(sub.clone()), contracts)?;
370 apply_contract(target, sub, contracts)
371}
372
373#[derive(Debug, Clone, PartialEq)]
378enum Tok {
379 LBrace, RBrace, LBrack, RBrack, Comma, Colon, At, Str(String), Word(String), }
389
390fn tokenize(text: &str) -> Result<Vec<Tok>, String> {
391 let mut toks = Vec::new();
392 let mut chars = text.chars().peekable();
393 let mut buf = String::new();
394 let mut flush = |buf: &mut String, toks: &mut Vec<Tok>| {
395 if !buf.is_empty() {
396 toks.push(Tok::Word(std::mem::take(buf)));
397 }
398 };
399 while let Some(c) = chars.next() {
400 match c {
401 '#' => {
402 for c2 in chars.by_ref() {
404 if c2 == '\n' {
405 break;
406 }
407 }
408 }
409 '"' => {
410 flush(&mut buf, &mut toks);
411 let mut s = String::new();
412 loop {
413 match chars.next() {
414 Some('"') => break,
415 Some('\\') => {
416 match chars.next() {
418 Some('n') => s.push('\n'),
419 Some('t') => s.push('\t'),
420 Some('r') => s.push('\r'),
421 Some('0') => s.push('\0'),
422 Some('"') => s.push('"'),
423 Some('\\') => s.push('\\'),
424 Some('u') => {
425 let mut hex = String::new();
426 if chars.peek() == Some(&'{') {
428 chars.next();
429 for c2 in chars.by_ref() {
430 if c2 == '}' {
431 break;
432 }
433 hex.push(c2);
434 }
435 } else {
436 for _ in 0..4 {
437 if let Some(c2) = chars.next() {
438 hex.push(c2);
439 }
440 }
441 }
442 if let Ok(cp) = u32::from_str_radix(&hex, 16) {
443 if let Some(ch) = char::from_u32(cp) {
444 s.push(ch);
445 }
446 }
447 }
448 Some(other) => s.push(other),
449 None => break,
450 }
451 }
452 Some(other) => s.push(other),
453 None => break,
454 }
455 }
456 toks.push(Tok::Str(s));
457 }
458 '{' => {
459 flush(&mut buf, &mut toks);
460 toks.push(Tok::LBrace);
461 }
462 '}' => {
463 flush(&mut buf, &mut toks);
464 toks.push(Tok::RBrace);
465 }
466 '[' => {
467 flush(&mut buf, &mut toks);
468 toks.push(Tok::LBrack);
469 }
470 ']' => {
471 flush(&mut buf, &mut toks);
472 toks.push(Tok::RBrack);
473 }
474 ',' => {
475 flush(&mut buf, &mut toks);
476 toks.push(Tok::Comma);
477 }
478 ':' => {
479 flush(&mut buf, &mut toks);
480 toks.push(Tok::Colon);
481 }
482 '@' => {
483 if buf.is_empty() {
488 toks.push(Tok::At);
489 } else {
490 buf.push(c);
491 }
492 }
493 ' ' | '\t' | '\n' | '\r' => {
494 flush(&mut buf, &mut toks);
495 }
496 _ => {
497 buf.push(c);
498 }
499 }
500 }
501 flush(&mut buf, &mut toks);
502 Ok(toks)
503}
504
505fn coerce_word(w: &str, fragments: &BTreeMap<String, Value>) -> Value {
506 match w {
507 "true" => return Value::Bool(true),
508 "false" => return Value::Bool(false),
509 "null" => return Value::Null,
510 _ => {}
511 }
512 if let Some(ev) = w.strip_prefix("$env.") {
514 return Value::Str(std::env::var(ev).unwrap_or_default());
515 }
516 if let Some(name) = w.strip_prefix('&') {
518 if let Some(v) = fragments.get(name) {
519 return v.clone();
520 }
521 return Value::Str(w.to_string());
522 }
523 if let Ok(i) = w.parse::<i64>() {
525 return Value::Int(i);
526 }
527 if let Ok(f) = w.parse::<f64>() {
528 return Value::Float(f);
529 }
530 Value::Str(w.to_string())
531}
532
533struct Parser {
534 toks: Vec<Tok>,
535 i: usize,
536 fragments: BTreeMap<String, Value>,
537 contracts: BTreeMap<String, Contract>,
539}
540
541impl Parser {
542 fn peek(&self) -> Option<&Tok> {
543 self.toks.get(self.i)
544 }
545 fn next(&mut self) -> Option<Tok> {
546 let t = self.toks.get(self.i).cloned();
547 if t.is_some() {
548 self.i += 1;
549 }
550 t
551 }
552
553 fn parse_contract_body(&mut self) -> Result<BTreeMap<String, FieldSpec>, String> {
555 let mut fields: BTreeMap<String, FieldSpec> = BTreeMap::new();
556 loop {
557 match self.peek().cloned() {
558 None | Some(Tok::RBrace) => {
559 self.next();
560 break;
561 }
562 Some(Tok::Comma) => {
563 self.next();
564 }
565 _ => {
566 let key = match self.next() {
567 Some(Tok::Word(s)) | Some(Tok::Str(s)) => s,
568 other => {
569 return Err(format!("sml: 契约字段期望键, 得 {:?}", other))
570 }
571 };
572 if self.peek() == Some(&Tok::Colon) {
573 self.next();
574 } else {
575 return Err(format!("sml: 契约字段 `{}` 后须有冒号", key));
576 }
577 let spec = self.parse_field_spec()?;
578 fields.insert(key, spec);
579 }
580 }
581 }
582 Ok(fields)
583 }
584
585 fn parse_field_spec(&mut self) -> Result<FieldSpec, String> {
587 let ty = match self.next() {
588 Some(Tok::Word(w)) => match w.as_str() {
589 "str" => TypeSpec::Str,
590 "int" => TypeSpec::Int,
591 "num" => TypeSpec::Num,
592 "bool" => TypeSpec::Bool,
593 "any" => TypeSpec::Any,
594 "enum" => {
595 if self.peek() != Some(&Tok::LBrack) {
596 return Err("sml: `enum` 后须为 [ ... ]".into());
597 }
598 self.next();
599 let mut vals = Vec::new();
600 loop {
601 match self.peek().cloned() {
602 None | Some(Tok::RBrack) => {
603 self.next();
604 break;
605 }
606 Some(Tok::Comma) => {
607 self.next();
608 }
609 Some(Tok::Word(s)) | Some(Tok::Str(s)) => {
610 vals.push(s);
611 self.next();
612 }
613 _ => {
614 self.next();
615 }
616 }
617 }
618 TypeSpec::Enum(vals)
619 }
620 other => TypeSpec::ContractRef(other.to_string()),
624 },
625 Some(Tok::LBrack) => {
626 let inner = match self.next() {
627 Some(Tok::Word(w)) => match w.as_str() {
628 "str" => TypeSpec::Str,
629 "int" => TypeSpec::Int,
630 "num" => TypeSpec::Num,
631 "bool" => TypeSpec::Bool,
632 "any" => TypeSpec::Any,
633 other => {
634 return Err(format!("sml: 未知数组元素类型 `{}`", other))
635 }
636 },
637 other => {
638 return Err(format!("sml: 数组元素类型期望标识符, 得 {:?}", other))
639 }
640 };
641 if self.peek() == Some(&Tok::RBrack) {
642 self.next();
643 }
644 TypeSpec::Array(Box::new(inner))
645 }
646 other => return Err(format!("sml: 字段类型期望标识符, 得 {:?}", other)),
647 };
648
649 let mut required = true;
651 let mut default = None;
652 let mut min = None;
653 let mut max = None;
654 loop {
655 let is_next_field = matches!(self.peek(), Some(Tok::Word(_)))
657 && matches!(self.toks.get(self.i + 1), Some(Tok::Colon));
658 if is_next_field {
659 break;
660 }
661 match self.peek().cloned() {
662 Some(Tok::Word(w)) => match w.as_str() {
663 "optional" => {
664 required = false;
665 self.next();
666 }
667 "required" => {
668 required = true;
669 self.next();
670 }
671 "default" => {
672 self.next();
673 default = Some(match self.next() {
674 Some(Tok::Word(w2)) => coerce_word(&w2, &self.fragments),
675 Some(Tok::Str(s)) => Value::Str(s),
676 other => {
677 return Err(format!("sml: default 期望值, 得 {:?}", other))
678 }
679 });
680 }
681 "min" => {
682 self.next();
683 min = Some(self.parse_spec_number()?);
684 }
685 "max" => {
686 self.next();
687 max = Some(self.parse_spec_number()?);
688 }
689 _ => break,
690 },
691 _ => break,
692 }
693 }
694 Ok(FieldSpec { ty, required, default, min, max })
695 }
696
697 fn parse_spec_number(&mut self) -> Result<f64, String> {
698 match self.next() {
699 Some(Tok::Word(w)) => {
700 w.parse::<f64>().map_err(|_| format!("sml: 期望数字, 得 `{}`", w))
701 }
702 other => Err(format!("sml: 期望数字, 得 {:?}", other)),
703 }
704 }
705
706 fn parse_block(&mut self, closing: Option<Tok>) -> Result<Value, String> {
708 let mut node: BTreeMap<String, Value> = BTreeMap::new();
709 let mut applied_contract: Option<String> = None;
711 loop {
712 let tok = match self.peek().cloned() {
713 None => break,
714 Some(t) => t,
715 };
716 match tok {
717 Tok::RBrace | Tok::RBrack => {
718 if let Some(cl) = &closing {
719 if *cl == tok {
720 self.next();
721 break;
722 }
723 }
724 break;
726 }
727 Tok::Comma => {
728 self.next();
729 }
730 Tok::At => {
731 self.next();
733 let fname = match self.next() {
734 Some(Tok::Word(s)) | Some(Tok::Str(s)) => s,
735 _ => return Err("sml: @ 后需片段名".into()),
736 };
737 if self.peek() == Some(&Tok::Colon) {
738 self.next();
739 }
740 if fname == "contract" {
742 let cname = match self.next() {
743 Some(Tok::Word(s)) | Some(Tok::Str(s)) => s,
744 other => {
745 return Err(format!("sml: @contract 后须契约名, 得 {:?}", other))
746 }
747 };
748 let mut allow_extra = false;
751 if let Some(Tok::Word(w)) = self.peek().cloned() {
752 if w == "loose" {
753 allow_extra = true;
754 self.next();
755 }
756 }
757 if self.peek() != Some(&Tok::LBrace) {
758 return Err(format!("sml: @contract {} 后须 {{ ... }}", cname));
759 }
760 self.next();
761 let fields = self.parse_contract_body()?;
762 self.contracts.insert(
763 cname.clone(),
764 Contract { name: cname, fields, allow_extra },
765 );
766 continue;
767 }
768 if fname == "is" {
770 let cname = match self.next() {
771 Some(Tok::Word(s)) | Some(Tok::Str(s)) => s,
772 other => {
773 return Err(format!("sml: @is 后须契约名, 得 {:?}", other))
774 }
775 };
776 applied_contract = Some(cname);
777 continue;
778 }
779 let mut ftype: Option<String> = None;
781 let mut farg: Option<String> = None;
782 if let Some(Tok::Word(s)) = self.peek().cloned() {
783 if *self.peek().unwrap() != Tok::LBrace {
784 self.next();
785 ftype = Some(s);
786 if let Some(Tok::Word(s2)) = self.peek().cloned() {
787 if *self.peek().unwrap() != Tok::LBrace {
788 self.next();
789 farg = Some(s2);
790 }
791 }
792 }
793 }
794 if self.peek() == Some(&Tok::LBrace) {
795 self.next();
796 let mut sub = match self.parse_block(Some(Tok::RBrace))? {
797 Value::Object(m) => m,
798 other => {
799 let mut m = BTreeMap::new();
800 m.insert("_value".into(), other);
801 m
802 }
803 };
804 if let Some(t) = ftype {
805 sub.insert("__type".into(), Value::Str(t));
806 }
807 if let Some(a) = farg {
808 sub.insert("__name".into(), Value::Str(a));
809 }
810 self.fragments.insert(fname, Value::Object(sub));
811 }
812 }
813 _ => {
814 let key = match self.next() {
816 Some(Tok::Word(s)) | Some(Tok::Str(s)) => s,
817 other => return Err(format!("sml: 期望键, 得 {:?}", other)),
818 };
819 let colon = self.peek() == Some(&Tok::Colon);
820 if colon {
821 self.next();
822 }
823 let val = self.parse_value(&key, colon)?;
824 if let Some(existing) = node.get_mut(&key) {
826 match existing {
827 Value::Array(a) => a.push(val),
828 _ => {
829 let old = node.remove(&key).unwrap();
830 node.insert(key, Value::Array(vec![old, val]));
831 }
832 }
833 } else {
834 node.insert(key, val);
835 }
836 }
837 }
838 }
839 if let Some(cname) = applied_contract {
841 let c = self
842 .contracts
843 .get(&cname)
844 .cloned()
845 .ok_or_else(|| format!("sml: 未定义的契约 `{}`", cname))?;
846 apply_contract(&c, &mut node, &self.contracts)?;
847 }
848 Ok(Value::Object(node))
849 }
850
851 fn parse_value(&mut self, key: &str, colon: bool) -> Result<Value, String> {
853 if !colon && matches!(self.peek(), Some(Tok::Word(_))) {
855 let mut probe = self.i;
857 let mut found_block = false;
858 while probe < self.toks.len() {
859 match &self.toks[probe] {
860 Tok::Word(_) | Tok::Str(_) => probe += 1,
861 Tok::LBrace => {
862 found_block = true;
863 break;
864 }
865 _ => break,
866 }
867 }
868 if found_block {
869 let mut args: Vec<Value> = Vec::new();
871 while let Some(t) = self.peek().cloned() {
872 match t {
873 Tok::Word(w) => {
874 args.push(coerce_word(&w, &self.fragments));
875 self.next();
876 }
877 Tok::Str(_) => {
878 if let Some(Tok::Str(s)) = self.next() {
879 args.push(Value::Str(s));
880 }
881 }
882 _ => break,
883 }
884 }
885 if self.peek() == Some(&Tok::LBrace) {
886 self.next();
887 let mut sub = self.parse_block(Some(Tok::RBrace))?;
888 if let Value::Object(m) = &mut sub {
889 m.insert("__type".into(), Value::Str(key.to_string()));
890 if args.len() == 1 {
891 m.insert("__name".into(), args.remove(0));
892 }
893 }
894 return Ok(sub);
895 }
896 }
897 }
898 match self.peek().cloned() {
899 Some(Tok::LBrace) => {
900 self.next();
901 self.parse_block(Some(Tok::RBrace))
902 }
903 Some(Tok::LBrack) => {
904 self.next();
905 self.parse_array()
906 }
907 Some(tok @ (Tok::Word(_) | Tok::Str(_))) => {
908 let v = match tok {
909 Tok::Word(w) => coerce_word(&w, &self.fragments),
910 Tok::Str(s) => {
911 let ev = s.strip_prefix("$env.");
912 match ev {
913 Some(name) => Value::Str(std::env::var(name).unwrap_or_default()),
914 None => Value::Str(s),
915 }
916 }
917 _ => unreachable!(),
918 };
919 self.next();
920 Ok(v)
921 }
922 Some(Tok::RBrace) | Some(Tok::RBrack) | Some(Tok::Comma) | None => {
924 if colon {
925 Ok(Value::Null)
927 } else {
928 Ok(coerce_word(key, &self.fragments))
929 }
930 }
931 _ => Err("sml: 语法错误".into()),
932 }
933 }
934
935 fn parse_array(&mut self) -> Result<Value, String> {
936 let mut arr = Vec::new();
937 loop {
938 match self.peek().cloned() {
939 None => break,
940 Some(Tok::RBrack) => {
941 self.next();
942 break;
943 }
944 Some(Tok::Comma) => {
945 self.next();
946 }
947 Some(Tok::LBrace) => {
948 self.next();
949 arr.push(self.parse_block(Some(Tok::RBrace))?);
950 }
951 Some(Tok::Word(w)) => {
952 arr.push(coerce_word(&w, &self.fragments));
953 self.next();
954 }
955 Some(Tok::Str(_)) => {
956 if let Some(Tok::Str(s)) = self.next() {
957 arr.push(Value::Str(s));
958 }
959 }
960 _ => break,
961 }
962 }
963 Ok(Value::Array(arr))
964 }
965}
966
967#[derive(Debug, Clone, Copy, PartialEq, Eq)]
972pub enum Version {
973 V1,
975}
976
977impl Version {
978 pub const CURRENT: Version = Version::V1;
980
981 fn from_word(w: &str) -> Option<Version> {
983 match w {
984 "v1" | "1" => Some(Version::V1),
985 _ => None,
986 }
987 }
988
989 pub fn name(self) -> &'static str {
991 match self {
992 Version::V1 => "v1",
993 }
994 }
995}
996
997impl fmt::Display for Version {
998 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
999 f.write_str(self.name())
1000 }
1001}
1002
1003fn version_directive(line: &str) -> Result<Option<String>, String> {
1007 let content = strip_line_comment(line).trim();
1008 let toks = match tokenize(content) {
1010 Ok(t) => t,
1011 Err(_) => return Ok(None),
1012 };
1013 match toks.as_slice() {
1014 [Tok::At, Tok::Word(w), Tok::Word(v)] if w == "version" => Ok(Some(v.clone())),
1015 [Tok::At, Tok::Word(w), Tok::Str(v)] if w == "version" => Ok(Some(v.clone())),
1016 [Tok::At, Tok::Word(w), ..] if w == "version" => Err(
1017 "`@version` 是版本声明指令,须写作 `@version v1`;`version` 不可作为片段名".into(),
1018 ),
1019 _ => Ok(None),
1020 }
1021}
1022
1023fn strip_version(text: &str) -> Result<(String, Option<Version>), String> {
1028 let mut declared: Option<Version> = None;
1029 let mut rest = String::new();
1030 for line in text.lines() {
1031 if let Some(lit) = version_directive(line)? {
1032 let v = Version::from_word(&lit).ok_or_else(|| {
1033 format!(
1034 "不支持的 SML 版本 `{lit}`(本实现支持 {})",
1035 Version::CURRENT.name()
1036 )
1037 })?;
1038 match declared {
1039 None => declared = Some(v),
1040 Some(prev) if prev != v => {
1041 return Err(format!("@version 冲突:{} 与 {}", prev.name(), v.name()))
1042 }
1043 Some(_) => {}
1044 }
1045 continue;
1046 }
1047 rest.push_str(line);
1048 rest.push('\n');
1049 }
1050 Ok((rest, declared))
1051}
1052
1053pub fn parse_versioned(text: &str) -> Result<(Value, Version), String> {
1057 let (rest, declared) = strip_version(text)?;
1058 Ok((parse_impl(&rest)?, declared.unwrap_or(Version::CURRENT)))
1059}
1060
1061pub fn parse_file_versioned(path: impl AsRef<Path>) -> Result<(Value, Version), String> {
1063 let path = path.as_ref();
1064 let text =
1065 std::fs::read_to_string(path).map_err(|e| format!("读取失败 {}: {e}", path.display()))?;
1066 let base = path
1067 .parent()
1068 .map(|p| p.to_path_buf())
1069 .unwrap_or_else(|| PathBuf::from("."));
1070 let expanded = resolve_includes(&text, &base)?;
1071 parse_versioned(&expanded)
1072}
1073
1074pub fn parse(text: &str) -> Result<Value, String> {
1078 let (rest, _) = strip_version(text)?;
1079 parse_impl(&rest)
1080}
1081
1082fn parse_impl(text: &str) -> Result<Value, String> {
1084 let toks = tokenize(text)?;
1085 let mut p = Parser {
1086 toks,
1087 i: 0,
1088 fragments: BTreeMap::new(),
1089 contracts: BTreeMap::new(),
1090 };
1091 match p.peek() {
1099 Some(Tok::LBrack) => {
1100 p.next();
1101 p.parse_array()
1102 }
1103 Some(Tok::LBrace) => {
1104 p.next();
1105 p.parse_block(Some(Tok::RBrace))
1106 }
1107 _ => p.parse_block(None),
1108 }
1109}
1110
1111const MAX_INCLUDE_DEPTH: usize = 32;
1126
1127fn strip_line_comment(line: &str) -> &str {
1129 let bytes = line.as_bytes();
1130 let mut i = 0;
1131 let mut in_quote = false;
1132 while i < bytes.len() {
1133 match bytes[i] {
1134 b'"' => in_quote = !in_quote,
1135 b'\\' if in_quote => i += 1,
1137 b'#' if !in_quote => return &line[..i],
1138 _ => {}
1139 }
1140 i += 1;
1141 }
1142 line
1143}
1144
1145fn include_target(line: &str) -> Option<String> {
1147 let content = strip_line_comment(line).trim();
1148 let content = content.strip_prefix('@').unwrap_or(content).trim_start();
1149 let toks = tokenize(content).ok()?;
1151 match toks.as_slice() {
1152 [Tok::Word(w), Tok::Str(p)] if w == "include" => Some(p.clone()),
1153 [Tok::Word(w), Tok::Word(p)] if w == "include" => Some(p.clone()),
1154 _ => None,
1155 }
1156}
1157
1158pub fn resolve_includes(text: &str, base: &Path) -> Result<String, String> {
1163 let mut out = String::new();
1164 let mut stack: Vec<PathBuf> = Vec::new();
1165 expand_includes(text, base, &mut out, &mut stack)?;
1166 Ok(out)
1167}
1168
1169fn expand_includes(
1170 text: &str,
1171 base: &Path,
1172 out: &mut String,
1173 stack: &mut Vec<PathBuf>,
1174) -> Result<(), String> {
1175 if stack.len() >= MAX_INCLUDE_DEPTH {
1176 return Err(format!("include 嵌套超过 {MAX_INCLUDE_DEPTH} 层"));
1177 }
1178 for line in text.lines() {
1179 match include_target(line) {
1180 Some(rel) => {
1181 let path = base.join(&rel);
1182 let canon = path
1183 .canonicalize()
1184 .map_err(|e| format!("include 无法定位 {}: {e}", path.display()))?;
1185 if stack.iter().any(|p| p == &canon) {
1187 return Err(format!("include 循环引用: {}", canon.display()));
1188 }
1189 let content = std::fs::read_to_string(&canon)
1190 .map_err(|e| format!("include 读取失败 {}: {e}", canon.display()))?;
1191 let child_base = canon
1192 .parent()
1193 .map(|p| p.to_path_buf())
1194 .unwrap_or_else(|| PathBuf::from("."));
1195 stack.push(canon);
1196 expand_includes(&content, &child_base, out, stack)?;
1197 stack.pop();
1198 }
1199 None => {
1200 out.push_str(line);
1201 out.push('\n');
1202 }
1203 }
1204 }
1205 Ok(())
1206}
1207
1208pub fn parse_file(path: impl AsRef<Path>) -> Result<Value, String> {
1212 let path = path.as_ref();
1213 let text = std::fs::read_to_string(path)
1214 .map_err(|e| format!("读取失败 {}: {e}", path.display()))?;
1215 let base = path
1216 .parent()
1217 .map(|p| p.to_path_buf())
1218 .unwrap_or_else(|| PathBuf::from("."));
1219 let expanded = resolve_includes(&text, &base)?;
1220 parse(&expanded)
1221}
1222
1223pub fn loads(text: &str) -> Result<Value, ParseError> {
1225 parse(text).map_err(ParseError)
1226}
1227
1228#[derive(Debug)]
1229pub struct ParseError(pub String);
1230
1231impl fmt::Display for ParseError {
1232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1233 write!(f, "sml parse error: {}", self.0)
1234 }
1235}
1236
1237impl std::error::Error for ParseError {}
1238
1239fn quote_if_needed(s: &str) -> String {
1244 if s.is_empty() || s.contains([' ', '\t', '\n', '\r', ':', '#', '{', '}']) {
1245 format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\""))
1246 } else {
1247 s.to_string()
1248 }
1249}
1250
1251fn dump_value(v: &Value, indent: usize, out: &mut String) {
1252 let pad = " ".repeat(indent);
1253 match v {
1254 Value::Null => out.push_str("null"),
1255 Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
1256 Value::Int(i) => out.push_str(&i.to_string()),
1257 Value::Float(f) => out.push_str(&format!("{}", f)),
1258 Value::Str(s) => out.push_str("e_if_needed(s)),
1259 Value::Array(a) => {
1260 if a.is_empty() {
1261 out.push_str("[]");
1262 } else {
1263 out.push('[');
1264 for e in a {
1265 out.push('\n');
1266 out.push_str(&format!("{}{}", " ".repeat(indent + 1), dump_inline(e)));
1267 }
1268 out.push_str(&format!("\n{}]", pad));
1269 }
1270 }
1271 Value::Object(m) => {
1272 let has_body = m.iter().any(|(k, _)| k != "__type" && k != "__name");
1273 if !has_body {
1274 out.push_str("{}");
1275 return;
1276 }
1277 out.push_str(&format!("\n{}{{", pad));
1278 for (k, val) in m {
1279 if k == "__type" || k == "__name" {
1280 continue;
1281 }
1282 out.push_str(&format!("\n{}{}: ", " ".repeat(indent + 1), k));
1283 dump_value(val, indent + 1, out);
1284 }
1285 out.push_str(&format!("\n{}}}", pad));
1286 }
1287 }
1288}
1289
1290fn dump_scalar(v: &Value) -> String {
1291 match v {
1292 Value::Null => "null".into(),
1293 Value::Bool(b) => b.to_string(),
1294 Value::Int(i) => i.to_string(),
1295 Value::Float(f) => f.to_string(),
1296 Value::Str(s) => quote_if_needed(s),
1297 _ => "".into(),
1298 }
1299}
1300
1301fn dump_inline(v: &Value) -> String {
1302 match v {
1303 Value::Object(m) => {
1304 let parts: Vec<String> = m
1305 .iter()
1306 .filter(|(k, _)| k.as_str() != "__type" && k.as_str() != "__name")
1307 .map(|(k, val)| format!("{}: {}", k, dump_inline(val)))
1308 .collect();
1309 format!("{{ {} }}", parts.join(", "))
1310 }
1311 Value::Array(a) => {
1312 let parts: Vec<String> = a.iter().map(dump_inline).collect();
1313 format!("[ {} ]", parts.join(", "))
1314 }
1315 other => dump_scalar(other),
1316 }
1317}
1318
1319pub fn to_sml(v: &Value) -> String {
1321 let mut out = String::new();
1322 if let Value::Object(m) = v {
1323 for (k, val) in m {
1324 if k == "__type" || k == "__name" {
1325 continue;
1326 }
1327 out.push_str(&format!("{}: ", k));
1328 dump_value(val, 0, &mut out);
1329 out.push('\n');
1330 }
1331 } else {
1332 out.push_str(&dump_inline(v));
1333 }
1334 out
1335}
1336
1337use std::os::raw::{c_char, c_int};
1342use std::ptr;
1343
1344fn cstr(s: &str) -> *mut c_char {
1345 let c = std::ffi::CString::new(s).unwrap_or_default();
1346 c.into_raw()
1347}
1348
1349#[no_mangle]
1351pub extern "C" fn sml_parse(text: *const c_char) -> *mut c_char {
1352 if text.is_null() {
1353 return ptr::null_mut();
1354 }
1355 let t = unsafe { std::ffi::CStr::from_ptr(text) }.to_string_lossy().into_owned();
1356 match parse(&t) {
1357 Ok(v) => cstr(&jsonify(&v)),
1358 Err(_) => ptr::null_mut(),
1359 }
1360}
1361
1362#[no_mangle]
1364pub extern "C" fn sml_dump(json: *const c_char) -> *mut c_char {
1365 if json.is_null() {
1366 return ptr::null_mut();
1367 }
1368 let j = unsafe { std::ffi::CStr::from_ptr(json) }.to_string_lossy().into_owned();
1369 match json_to_value(&j) {
1370 Some(v) => cstr(&to_sml(&v)),
1371 None => ptr::null_mut(),
1372 }
1373}
1374
1375#[no_mangle]
1377pub unsafe extern "C" fn sml_free(p: *mut c_char) {
1378 if !p.is_null() {
1379 drop(unsafe { std::ffi::CString::from_raw(p) });
1380 }
1381}
1382
1383#[no_mangle]
1385pub extern "C" fn sml_version() -> *mut c_char {
1386 cstr(concat!("sml ", env!("CARGO_PKG_VERSION")))
1387}
1388
1389fn jsonify(v: &Value) -> String {
1394 fn esc(s: &str) -> String {
1395 s.replace('\\', "\\\\").replace('"', "\\\"")
1396 }
1397 match v {
1398 Value::Null => "null".into(),
1399 Value::Bool(b) => b.to_string(),
1400 Value::Int(i) => i.to_string(),
1401 Value::Float(f) => f.to_string(),
1402 Value::Str(s) => format!("\"{}\"", esc(s)),
1403 Value::Array(a) => {
1404 let parts: Vec<String> = a.iter().map(jsonify).collect();
1405 format!("[{}]", parts.join(","))
1406 }
1407 Value::Object(m) => {
1408 let parts: Vec<String> = m
1409 .iter()
1410 .map(|(k, val)| format!("\"{}\":{}", esc(k), jsonify(val)))
1411 .collect();
1412 format!("{{{}}}", parts.join(","))
1413 }
1414 }
1415}
1416
1417fn json_to_value(s: &str) -> Option<Value> {
1418 let bytes = s.as_bytes();
1419 let mut i = 0;
1420 let _n = bytes.len();
1421 let mut skip_ws = |b: &[u8], i: &mut usize| {
1422 while *i < b.len() && matches!(b[*i], b' ' | b'\t' | b'\n' | b'\r') {
1423 *i += 1;
1424 }
1425 };
1426 let mut parse_str = |b: &[u8], i: &mut usize| -> Option<String> {
1427 skip_ws(b, i);
1428 if *i >= b.len() || b[*i] != b'"' {
1429 return None;
1430 }
1431 *i += 1;
1432 let mut out = String::new();
1433 while *i < b.len() {
1434 let c = b[*i];
1435 if c == b'"' {
1436 *i += 1;
1437 return Some(out);
1438 }
1439 if c == b'\\' && *i + 1 < b.len() {
1440 *i += 1;
1441 let e = b[*i];
1442 out.push(match e {
1443 b'n' => '\n',
1444 b't' => '\t',
1445 b'r' => '\r',
1446 b'"' => '"',
1447 b'\\' => '\\',
1448 _ => e as char,
1449 });
1450 } else {
1451 out.push(c as char);
1452 }
1453 *i += 1;
1454 }
1455 None
1456 };
1457 fn parse_val_impl(
1458 b: &[u8],
1459 i: &mut usize,
1460 s: &str,
1461 parse_str: &dyn Fn(&[u8], &mut usize) -> Option<String>,
1462 ) -> Option<Value> {
1463 let mut skip_ws = |b: &[u8], i: &mut usize| {
1464 while *i < b.len() && matches!(b[*i], b' ' | b'\t' | b'\n' | b'\r') {
1465 *i += 1;
1466 }
1467 };
1468 skip_ws(b, i);
1469 if *i >= b.len() {
1470 return None;
1471 }
1472 match b[*i] {
1473 b'{' => {
1474 *i += 1;
1475 let mut m = BTreeMap::new();
1476 skip_ws(b, i);
1477 if *i < b.len() && b[*i] == b'}' {
1478 *i += 1;
1479 return Some(Value::Object(m));
1480 }
1481 loop {
1482 skip_ws(b, i);
1483 let k = parse_str(b, i)?;
1484 skip_ws(b, i);
1485 if *i < b.len() && b[*i] == b':' {
1486 *i += 1;
1487 }
1488 let v = parse_val_impl(b, i, s, parse_str)?;
1489 m.insert(k, v);
1490 skip_ws(b, i);
1491 if *i < b.len() && b[*i] == b',' {
1492 *i += 1;
1493 } else if *i < b.len() && b[*i] == b'}' {
1494 *i += 1;
1495 break;
1496 }
1497 }
1498 Some(Value::Object(m))
1499 }
1500 b'[' => {
1501 *i += 1;
1502 let mut a = Vec::new();
1503 skip_ws(b, i);
1504 if *i < b.len() && b[*i] == b']' {
1505 *i += 1;
1506 return Some(Value::Array(a));
1507 }
1508 loop {
1509 a.push(parse_val_impl(b, i, s, parse_str)?);
1510 skip_ws(b, i);
1511 if *i < b.len() && b[*i] == b',' {
1512 *i += 1;
1513 } else if *i < b.len() && b[*i] == b']' {
1514 *i += 1;
1515 break;
1516 }
1517 }
1518 Some(Value::Array(a))
1519 }
1520 b'"' => parse_str(b, i).map(Value::Str),
1521 b't' => {
1522 if s[*i..].starts_with("true") {
1523 *i += 4;
1524 Some(Value::Bool(true))
1525 } else {
1526 None
1527 }
1528 }
1529 b'f' => {
1530 if s[*i..].starts_with("false") {
1531 *i += 5;
1532 Some(Value::Bool(false))
1533 } else {
1534 None
1535 }
1536 }
1537 b'n' => {
1538 if s[*i..].starts_with("null") {
1539 *i += 4;
1540 Some(Value::Null)
1541 } else {
1542 None
1543 }
1544 }
1545 _ => {
1546 let start = *i;
1547 while *i < b.len()
1548 && (b[*i].is_ascii_digit()
1549 || matches!(b[*i], b'-' | b'+' | b'.' | b'e' | b'E'))
1550 {
1551 *i += 1;
1552 }
1553 let tok = s[start..*i].to_string();
1554 if let Ok(iv) = tok.parse::<i64>() {
1555 Some(Value::Int(iv))
1556 } else if let Ok(fv) = tok.parse::<f64>() {
1557 Some(Value::Float(fv))
1558 } else {
1559 None
1560 }
1561 }
1562 }
1563 }
1564 parse_val_impl(bytes, &mut i, s, &parse_str)
1565}
1566
1567#[cfg(feature = "serde")]
1580mod serde_impl {
1581 use super::Value;
1582 use serde::de::{self, MapAccess, SeqAccess, Visitor};
1583 use serde::ser::SerializeMap;
1584 use serde::{Deserialize, Deserializer, Serialize, Serializer};
1585 use std::collections::BTreeMap;
1586 use std::fmt;
1587
1588 impl Serialize for Value {
1589 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1590 where
1591 S: Serializer,
1592 {
1593 match self {
1594 Value::Null => serializer.serialize_unit(),
1595 Value::Bool(b) => serializer.serialize_bool(*b),
1596 Value::Int(i) => serializer.serialize_i64(*i),
1597 Value::Float(f) => serializer.serialize_f64(*f),
1598 Value::Str(s) => serializer.serialize_str(s),
1599 Value::Array(a) => a.serialize(serializer),
1601 Value::Object(m) => {
1602 let mut map = serializer.serialize_map(Some(m.len()))?;
1603 for (k, v) in m {
1604 map.serialize_entry(k, v)?;
1605 }
1606 map.end()
1607 }
1608 }
1609 }
1610 }
1611
1612 impl<'de> Deserialize<'de> for Value {
1613 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1614 where
1615 D: Deserializer<'de>,
1616 {
1617 deserializer.deserialize_any(ValueVisitor)
1619 }
1620 }
1621
1622 struct ValueVisitor;
1623
1624 impl<'de> Visitor<'de> for ValueVisitor {
1625 type Value = Value;
1626
1627 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
1628 f.write_str("any valid SML/JSON value")
1629 }
1630
1631 fn visit_unit<E: de::Error>(self) -> Result<Value, E> {
1632 Ok(Value::Null)
1633 }
1634 fn visit_none<E: de::Error>(self) -> Result<Value, E> {
1635 Ok(Value::Null)
1636 }
1637 fn visit_some<D>(self, d: D) -> Result<Value, D::Error>
1638 where
1639 D: Deserializer<'de>,
1640 {
1641 Deserialize::deserialize(d)
1642 }
1643 fn visit_bool<E: de::Error>(self, v: bool) -> Result<Value, E> {
1644 Ok(Value::Bool(v))
1645 }
1646 fn visit_i64<E: de::Error>(self, v: i64) -> Result<Value, E> {
1647 Ok(Value::Int(v))
1648 }
1649 fn visit_u64<E: de::Error>(self, v: u64) -> Result<Value, E> {
1651 Ok(i64::try_from(v)
1652 .map(Value::Int)
1653 .unwrap_or_else(|_| Value::Float(v as f64)))
1654 }
1655 fn visit_f64<E: de::Error>(self, v: f64) -> Result<Value, E> {
1656 Ok(Value::Float(v))
1657 }
1658 fn visit_str<E: de::Error>(self, v: &str) -> Result<Value, E> {
1659 Ok(Value::Str(v.to_string()))
1660 }
1661 fn visit_string<E: de::Error>(self, v: String) -> Result<Value, E> {
1662 Ok(Value::Str(v))
1663 }
1664 fn visit_seq<A>(self, mut seq: A) -> Result<Value, A::Error>
1665 where
1666 A: SeqAccess<'de>,
1667 {
1668 let mut v = Vec::new();
1669 while let Some(x) = seq.next_element()? {
1670 v.push(x);
1671 }
1672 Ok(Value::Array(v))
1673 }
1674 fn visit_map<A>(self, mut map: A) -> Result<Value, A::Error>
1675 where
1676 A: MapAccess<'de>,
1677 {
1678 let mut m = BTreeMap::new();
1679 while let Some((k, v)) = map.next_entry::<String, Value>()? {
1680 m.insert(k, v);
1681 }
1682 Ok(Value::Object(m))
1683 }
1684 }
1685}
1686
1687#[cfg(test)]
1692mod tests {
1693 use super::*;
1694
1695 #[test]
1698 fn version_defaults_to_current_when_absent() {
1699 let (v, ver) = parse_versioned("a: 1\n").unwrap();
1701 assert_eq!(ver, Version::CURRENT);
1702 assert_eq!(v.get("a"), Some(&Value::Int(1)));
1703 }
1704
1705 #[test]
1706 fn version_declared_as_v1() {
1707 let (v, ver) = parse_versioned("@version v1\na: 1\n").unwrap();
1708 assert_eq!(ver, Version::V1);
1709 assert_eq!(v.get("a"), Some(&Value::Int(1)));
1710 }
1711
1712 #[test]
1713 fn version_declaration_is_stripped_not_parsed_as_content() {
1714 let v = parse("@version v1\na: 1\n").unwrap();
1716 assert_eq!(v.get("a"), Some(&Value::Int(1)));
1717 assert!(v.get("version").is_none(), "@version 不应进入数据");
1718 }
1719
1720 #[test]
1721 fn unsupported_version_is_rejected() {
1722 let err = parse_versioned("@version v99\na: 1\n").unwrap_err();
1723 assert!(err.contains("不支持"), "应拒绝不支持的版本,got: {err}");
1724 assert!(err.contains("v99"), "错误应含版本号,got: {err}");
1725 }
1726
1727 #[test]
1728 fn conflicting_version_is_rejected() {
1729 let err = parse_versioned("@version v1\n@version v2\n").unwrap_err();
1730 assert!(!err.is_empty());
1732 let (_, ver) = parse_versioned("@version v1\n@version v1\n").unwrap();
1734 assert_eq!(ver, Version::V1, "重复但一致的声明应被接受");
1735 }
1736
1737 #[test]
1738 fn version_is_reserved_as_fragment_name() {
1739 let err = parse("@version { x: 1 }\n").unwrap_err();
1740 assert!(err.contains("保留") || err.contains("版本声明"), "got: {err}");
1741 }
1742
1743 #[test]
1744 fn version_works_with_include() {
1745 let d = tmpdir("version");
1746 std::fs::write(d.join("p.sml"), "@version v1\nb: 2\n").unwrap();
1747 std::fs::write(d.join("main.sml"), "@version v1\ninclude \"p.sml\"\n").unwrap();
1748 let (v, ver) = parse_file_versioned(d.join("main.sml")).unwrap();
1749 assert_eq!(ver, Version::V1);
1750 assert_eq!(v.get("b"), Some(&Value::Int(2)), "版本与 include 应协同");
1751 let _ = std::fs::remove_dir_all(&d);
1752 }
1753
1754 #[test]
1755 fn version_display_matches_name() {
1756 assert_eq!(Version::V1.name(), "v1");
1757 assert_eq!(format!("{}", Version::V1), "v1");
1758 }
1759
1760 fn tmpdir(tag: &str) -> std::path::PathBuf {
1764 let mut d = std::env::temp_dir();
1765 d.push(format!("sml_test_{tag}_{}", std::process::id()));
1766 let _ = std::fs::remove_dir_all(&d);
1767 std::fs::create_dir_all(&d).expect("create tmpdir");
1768 d
1769 }
1770
1771 #[test]
1772 fn include_inlines_external_file() {
1773 let d = tmpdir("inline");
1774 std::fs::write(d.join("part.sml"), "port: 8080\n").unwrap();
1775 std::fs::write(d.join("main.sml"), "host: local\ninclude \"part.sml\"\n").unwrap();
1776
1777 let v = parse_file(d.join("main.sml")).unwrap();
1778 assert_eq!(v.get("host").unwrap().as_str(), Some("local"));
1779 assert_eq!(v.get("port"), Some(&Value::Int(8080)));
1780 let _ = std::fs::remove_dir_all(&d);
1781 }
1782
1783 #[test]
1784 fn include_at_prefix_is_equivalent() {
1785 let d = tmpdir("at");
1786 std::fs::write(d.join("p.sml"), "b: 2\n").unwrap();
1787 std::fs::write(d.join("m.sml"), "@include \"p.sml\"\n").unwrap();
1788 let v = parse_file(d.join("m.sml")).unwrap();
1789 assert_eq!(v.get("b"), Some(&Value::Int(2)));
1790 let _ = std::fs::remove_dir_all(&d);
1791 }
1792
1793 #[test]
1794 fn include_resolves_relative_to_including_file() {
1795 let d = tmpdir("nested");
1797 std::fs::create_dir_all(d.join("sub")).unwrap();
1798 std::fs::write(d.join("sub/leaf.sml"), "leaf: yes\n").unwrap();
1799 std::fs::write(d.join("sub/mid2.sml"), "include \"leaf.sml\"\n").unwrap();
1801 std::fs::write(d.join("main.sml"), "include \"sub/mid2.sml\"\n").unwrap();
1802
1803 let v = parse_file(d.join("main.sml")).unwrap();
1804 assert_eq!(
1805 v.get("leaf").unwrap().as_str(),
1806 Some("yes"),
1807 "嵌套 include 的路径应相对各自所在目录解析"
1808 );
1809 let _ = std::fs::remove_dir_all(&d);
1810 }
1811
1812 #[test]
1813 fn include_inside_block_injects_fields() {
1814 let d = tmpdir("block");
1816 std::fs::write(d.join("fields.sml"), "region: cn-north-1\nzone: a\n").unwrap();
1817 std::fs::write(d.join("main.sml"), "server web {\ninclude \"fields.sml\"\nport: 8080\n}\n").unwrap();
1818
1819 let v = parse_file(d.join("main.sml")).unwrap();
1820 let server = v.get("server").expect("应有 server 块");
1821 assert_eq!(server.get("region").unwrap().as_str(), Some("cn-north-1"));
1822 assert_eq!(server.get("zone").unwrap().as_str(), Some("a"));
1823 assert_eq!(server.get("port"), Some(&Value::Int(8080)));
1824 let _ = std::fs::remove_dir_all(&d);
1825 }
1826
1827 #[test]
1828 fn include_detects_cycles() {
1829 let d = tmpdir("cycle");
1830 std::fs::write(d.join("a.sml"), "include \"b.sml\"\n").unwrap();
1831 std::fs::write(d.join("b.sml"), "include \"a.sml\"\n").unwrap();
1832 let err = parse_file(d.join("a.sml")).unwrap_err();
1833 assert!(err.contains("循环引用"), "应报循环引用,got: {err}");
1834 let _ = std::fs::remove_dir_all(&d);
1835 }
1836
1837 #[test]
1838 fn include_missing_file_is_error() {
1839 let d = tmpdir("missing");
1840 std::fs::write(d.join("m.sml"), "include \"nope.sml\"\n").unwrap();
1841 let err = parse_file(d.join("m.sml")).unwrap_err();
1842 assert!(err.contains("nope.sml"), "错误应含缺失文件名,got: {err}");
1843 let _ = std::fs::remove_dir_all(&d);
1844 }
1845
1846 #[test]
1847 fn hash_in_quoted_string_is_not_a_comment() {
1848 assert_eq!(strip_line_comment("k: \"a#b\""), "k: \"a#b\"");
1850 assert_eq!(strip_line_comment("k: v # comment"), "k: v ");
1851 }
1852
1853 #[test]
1854 fn include_line_is_not_confused_with_key_named_include() {
1855 assert_eq!(include_target("key: include"), None);
1857 assert_eq!(include_target("include \"a.sml\""), Some("a.sml".into()));
1858 assert_eq!(include_target("@include \"a.sml\""), Some("a.sml".into()));
1859 assert_eq!(include_target("# include \"a.sml\""), None, "注释行不生效");
1860 }
1861
1862 #[test]
1865 fn email_in_bare_word_survives() {
1866 let v = parse("to: a@b.c\nfrom: \"sal <sal@mail.swebase.cn>\"\n").unwrap();
1868 assert_eq!(v.get("to").unwrap().as_str(), Some("a@b.c"), "got: {v:?}");
1869 assert_eq!(
1870 v.get("from").unwrap().as_str(),
1871 Some("sal <sal@mail.swebase.cn>"),
1872 "got: {v:?}"
1873 );
1874 }
1875
1876 #[test]
1877 fn email_roundtrips_through_to_sml() {
1878 let v = Value::Object(BTreeMap::from([(
1879 "to".to_string(),
1880 Value::Str("SALflake@qq.com".into()),
1881 )]));
1882 let back = parse(&to_sml(&v)).unwrap();
1883 assert_eq!(back, v, "邮箱必须能往返,got:\n{}", to_sml(&v));
1884 }
1885
1886 #[test]
1887 fn fragment_definition_still_works() {
1888 let v = parse("@base { region: cn }\nregion: &base\n").unwrap();
1892 assert_eq!(
1893 v.get("region").unwrap().get("region").unwrap().as_str(),
1894 Some("cn"),
1895 "片段引用应展开为定义的内容,got: {v:?}"
1896 );
1897 }
1898
1899 #[test]
1902 fn toplevel_array_roundtrips() {
1903 let v = Value::Array(vec![
1906 Value::Object(BTreeMap::from([
1907 ("ts".to_string(), Value::Str("2026-01-01".into())),
1908 ("to".to_string(), Value::Str("a@b.c".into())),
1909 ])),
1910 Value::Object(BTreeMap::from([
1911 ("ts".to_string(), Value::Str("2026-01-02".into())),
1912 ("to".to_string(), Value::Str("x@y.z".into())),
1913 ])),
1914 ]);
1915 let text = to_sml(&v);
1916 let back = parse(&text).unwrap();
1917 assert_eq!(back, v, "顶层对象数组必须能往返,got text:\n{text}");
1918 }
1919
1920 #[test]
1921 fn toplevel_array_of_scalars_roundtrips() {
1922 let v = Value::Array(vec![
1923 Value::Int(1),
1924 Value::Str("two".into()),
1925 Value::Bool(true),
1926 ]);
1927 let back = parse(&to_sml(&v)).unwrap();
1928 assert_eq!(back, v, "顶层标量数组必须能往返");
1929 }
1930
1931 #[test]
1932 fn toplevel_object_block_roundtrips() {
1933 let mut m = BTreeMap::new();
1934 m.insert("k".to_string(), Value::Int(1));
1935 let v = Value::Object(m);
1936 let back = parse(&to_sml(&v)).unwrap();
1937 assert_eq!(back, v, "顶层对象块必须能往返");
1938 }
1939
1940 #[test]
1941 fn toplevel_empty_array_roundtrips() {
1942 let v = Value::Array(vec![]);
1943 let back = parse(&to_sml(&v)).unwrap();
1944 assert_eq!(back, v, "空数组必须能往返");
1945 }
1946
1947 #[cfg(feature = "serde")]
1950 #[test]
1951 fn serde_roundtrip_preserves_shape() {
1952 let v = parse("name: John\nage: 27\ntags: [a b]\nnested { k: v }\n").unwrap();
1953 let json = serde_json::to_string(&v).unwrap();
1954 assert!(json.contains("\"name\":\"John\""), "got: {json}");
1956 assert!(json.contains("\"age\":27"), "got: {json}");
1957 assert!(json.contains("\"tags\":[\"a\",\"b\"]"), "got: {json}");
1958 assert!(json.contains("\"nested\":{\"k\":\"v\"}"), "got: {json}");
1959
1960 let back: Value = serde_json::from_str(&json).unwrap();
1961 assert_eq!(back, v, "serde 往返应还原原值");
1962 }
1963
1964 #[cfg(feature = "serde")]
1965 #[test]
1966 fn serde_deserializes_json_into_value() {
1967 let v: Value = serde_json::from_str(r#"{"s":"x","i":5,"f":1.5,"b":true,"n":null,"a":[1,2]}"#).unwrap();
1968 assert_eq!(v.get("s").unwrap().as_str(), Some("x"));
1969 assert_eq!(v.get("i"), Some(&Value::Int(5)));
1970 assert_eq!(v.get("f"), Some(&Value::Float(1.5)));
1971 assert_eq!(v.get("b"), Some(&Value::Bool(true)));
1972 assert_eq!(v.get("n"), Some(&Value::Null));
1973 assert!(matches!(v.get("a"), Some(Value::Array(a)) if a.len() == 2));
1974 }
1975
1976 #[test]
1977 fn nested_array_inside_object_inside_array_survives_roundtrip() {
1978 let mut item = BTreeMap::new();
1981 item.insert("path".to_string(), Value::Str("a.txt".into()));
1982 item.insert(
1983 "chunks".to_string(),
1984 Value::Array(vec![
1985 Value::Str("c1".into()),
1986 Value::Str("c2".into()),
1987 ]),
1988 );
1989 let mut root = BTreeMap::new();
1990 root.insert(
1991 "entries".to_string(),
1992 Value::Array(vec![Value::Object(item)]),
1993 );
1994 let text = to_sml(&Value::Object(root));
1995 assert!(!text.contains("[..]"), "嵌套数组不得被缩略: {text}");
1996
1997 let back = parse(&text).unwrap();
1998 let chunks = back.get("entries").and_then(|e| match e {
1999 Value::Array(a) => a.first(),
2000 _ => None,
2001 });
2002 let chunks = match chunks {
2003 Some(Value::Object(m)) => m.get("chunks"),
2004 _ => None,
2005 };
2006 match chunks {
2007 Some(Value::Array(a)) => {
2008 assert_eq!(a.len(), 2, "两个块都应保留: {text}");
2009 assert_eq!(
2010 a.iter().filter_map(|c| c.as_str()).collect::<Vec<_>>(),
2011 vec!["c1", "c2"]
2012 );
2013 }
2014 other => panic!("chunks 应解析为数组,实际 {other:?}"),
2015 }
2016 }
2017
2018 #[test]
2019 fn utf8_in_quoted_string_survives_roundtrip() {
2020 let v = parse(r#"note: "修复若干问题""#).unwrap();
2024 assert_eq!(
2025 v.get("note").and_then(|x| x.as_str()),
2026 Some("修复若干问题"),
2027 "引号串中的中文不应被破坏"
2028 );
2029 let v2 = parse("region: 华北").unwrap();
2031 assert_eq!(v2.get("region").and_then(|x| x.as_str()), Some("华北"));
2032 let v3 = parse(r#"k: "\u{4fee}\u{590d}""#).unwrap();
2034 assert_eq!(v3.get("k").and_then(|x| x.as_str()), Some("修复"));
2035 }
2036
2037 #[test]
2038 fn parse_basic() {
2039 let text = "firstName: John\nage: 27\nisAlive: true\nspouse: null\n";
2040 let v = parse(text).unwrap();
2041 assert_eq!(v.get("firstName"), Some(&Value::Str("John".into())));
2042 assert_eq!(v.get("age"), Some(&Value::Int(27)));
2043 assert_eq!(v.get("isAlive"), Some(&Value::Bool(true)));
2044 assert_eq!(v.get("spouse"), Some(&Value::Null));
2045 }
2046
2047 #[test]
2048 fn parse_nested() {
2049 let text = "address:\n{\n streetAddress: \"21 2nd Street\"\n state: NY\n}\n";
2050 let v = parse(text).unwrap();
2051 assert_eq!(
2052 v.get("address.streetAddress"),
2053 Some(&Value::Str("21 2nd Street".into()))
2054 );
2055 assert_eq!(v.get("address.state"), Some(&Value::Str("NY".into())));
2056 }
2057
2058 #[test]
2059 fn parse_array() {
2060 let text = "phoneNumbers:\n[\n { type: home }\n { type: office }\n]\n";
2061 let v = parse(text).unwrap();
2062 if let Some(Value::Array(a)) = v.get("phoneNumbers") {
2063 assert_eq!(a.len(), 2);
2064 assert_eq!(a[0].get("type"), Some(&Value::Str("home".into())));
2065 } else {
2066 panic!("not array");
2067 }
2068 }
2069
2070 #[test]
2071 fn parse_fragment() {
2072 let text = "@base { region: cn-north-1 }\nserver web { &base }\n";
2073 let v = parse(text).unwrap();
2074 assert_eq!(
2076 v.get("server.&base.region"),
2077 Some(&Value::Str("cn-north-1".into()))
2078 );
2079 assert_eq!(v.get("server.__type"), Some(&Value::Str("server".into())));
2080 assert_eq!(v.get("server.__name"), Some(&Value::Str("web".into())));
2081 }
2082
2083 #[test]
2084 fn roundtrip() {
2085 let text = "name: myapp\nport: 8080\nflags: [ a b c ]\n";
2086 let v = parse(text).unwrap();
2087 let out = to_sml(&v);
2088 let v2 = parse(&out).unwrap();
2089 assert_eq!(v, v2);
2090 }
2091
2092 #[test]
2093 fn env_inline() {
2094 std::env::set_var("SML_TEST_VAR", "hello");
2095 let text = "greeting: $env.SML_TEST_VAR\n";
2096 let v = parse(text).unwrap();
2097 assert_eq!(v.get("greeting"), Some(&Value::Str("hello".into())));
2098 }
2099
2100 #[test]
2101 fn c_abi_json_bridge() {
2102 let text = "name: John\nage: 27\n";
2103 let v = parse(text).unwrap();
2104 let j = jsonify(&v);
2105 assert!(j.contains("\"name\":\"John\""));
2106 let back = json_to_value(&j).unwrap();
2107 assert_eq!(back, v);
2108 }
2109}