1use std::collections::BTreeMap;
2use std::fmt;
3use std::path::{Path, PathBuf};
4use crate::value::Value;
5#[derive(Debug, Clone, PartialEq)]
39pub enum TypeSpec {
40 Any,
42 ContractRef(String),
48 Str,
49 Int,
50 Num,
52 Bool,
53 Array(Box<TypeSpec>),
55 Enum(Vec<String>),
57}
58
59impl TypeSpec {
60 fn name(&self) -> String {
61 match self {
62 TypeSpec::Any => "any".into(),
63 TypeSpec::Str => "str".into(),
64 TypeSpec::Int => "int".into(),
65 TypeSpec::Num => "num".into(),
66 TypeSpec::Bool => "bool".into(),
67 TypeSpec::Array(inner) => format!("[{}]", inner.name()),
68 TypeSpec::Enum(vals) => format!("enum [{}]", vals.join(" ")),
69 TypeSpec::ContractRef(name) => name.clone(),
70 }
71 }
72}
73
74#[derive(Debug, Clone)]
76pub struct FieldSpec {
77 pub ty: TypeSpec,
78 pub required: bool,
80 pub default: Option<Value>,
82 pub min: Option<f64>,
84 pub max: Option<f64>,
86}
87
88#[derive(Debug, Clone)]
90pub struct Contract {
91 pub name: String,
92 pub fields: BTreeMap<String, FieldSpec>,
93 pub allow_extra: bool,
97}
98
99fn check_type(
102 contract: &str,
103 field: &str,
104 spec: &FieldSpec,
105 v: &Value,
106 contracts: &BTreeMap<String, Contract>,
107) -> Result<(), String> {
108 if let TypeSpec::ContractRef(ref_name) = &spec.ty {
110 return match v {
111 Value::Object(_) => {
112 let mut sub = match v {
113 Value::Object(m) => m.clone(),
114 _ => unreachable!(),
115 };
116 let target = contracts.get(ref_name).ok_or_else(|| {
117 format!(
118 "sml: 字段 `{}` 引用了未定义的契约 `{}`(契约 `{}`)",
119 field, ref_name, contract
120 )
121 })?;
122 apply_contract(target, &mut sub, contracts)?;
123 Ok(())
124 }
125 _ => Err(format!(
126 "sml: 字段 `{}` 应为块并按契约 `{}` 校验,实际为 {}(契约 `{}`)",
127 field,
128 ref_name,
129 value_kind(v),
130 contract
131 )),
132 };
133 }
134
135 let ok = match (&spec.ty, v) {
136 (TypeSpec::Any, _) => true,
137 (TypeSpec::Str, Value::Str(_)) => true,
138 (TypeSpec::Int, Value::Int(_)) => true,
139 (TypeSpec::Num, Value::Int(_)) | (TypeSpec::Num, Value::Float(_)) => true,
140 (TypeSpec::Bool, Value::Bool(_)) => true,
141 (TypeSpec::Enum(vals), Value::Str(s)) => vals.iter().any(|x| x == s),
142 (TypeSpec::Enum(vals), Value::Int(i)) => vals.iter().any(|x| x == &i.to_string()),
144 (TypeSpec::Array(inner), Value::Array(items)) => items.iter().all(|it| {
145 check_type(
146 contract,
147 field,
148 &FieldSpec { ty: (**inner).clone(), required: true, default: None, min: None, max: None },
149 it,
150 contracts,
151 )
152 .is_ok()
153 }),
154 _ => false,
155 };
156 if !ok {
157 return Err(format!(
158 "sml: 字段 `{}` 类型应为 {},实际为 {}(契约 `{}`)",
159 field,
160 spec.ty.name(),
161 value_kind(v),
162 contract
163 ));
164 }
165 if spec.min.is_some() || spec.max.is_some() {
167 let n = match v {
168 Value::Int(i) => Some(*i as f64),
169 Value::Float(f) => Some(*f),
170 _ => None,
171 };
172 if let Some(n) = n {
173 if let Some(lo) = spec.min {
174 if n < lo {
175 return Err(format!(
176 "sml: 字段 `{}` 值 {} 小于下界 {}(契约 `{}`)",
177 field, n, lo, contract
178 ));
179 }
180 }
181 if let Some(hi) = spec.max {
182 if n > hi {
183 return Err(format!(
184 "sml: 字段 `{}` 值 {} 大于上界 {}(契约 `{}`)",
185 field, n, hi, contract
186 ));
187 }
188 }
189 }
190 }
191 Ok(())
192}
193
194fn value_kind(v: &Value) -> &'static str {
195 match v {
196 Value::Null => "null",
197 Value::Bool(_) => "bool",
198 Value::Int(_) => "int",
199 Value::Float(_) => "float",
200 Value::Str(_) => "str",
201 Value::Array(_) => "array",
202 Value::Object(_) => "object",
203 }
204}
205
206fn apply_contract(
211 c: &Contract,
212 node: &mut BTreeMap<String, Value>,
213 contracts: &BTreeMap<String, Contract>,
214) -> Result<(), String> {
215 if !c.allow_extra {
218 for k in node.keys() {
219 if !c.fields.contains_key(k) {
220 return Err(format!(
221 "sml: 字段 `{}` 未在契约 `{}` 中声明(严格模式;如需允许额外字段请在契约名后写 `loose`)",
222 k, c.name
223 ));
224 }
225 }
226 }
227 for (k, spec) in &c.fields {
229 match node.get(k) {
230 None => {
231 if let Some(d) = &spec.default {
232 node.insert(k.clone(), d.clone());
233 } else if spec.required {
234 return Err(format!(
235 "sml: 字段 `{}` 必填但缺失(契约 `{}`)",
236 k, c.name
237 ));
238 }
239 }
240 Some(v) => {
241 if matches!(spec.ty, TypeSpec::ContractRef(_)) {
243 check_type(&c.name, k, spec, v, contracts)?;
246 let mut sub = match v {
247 Value::Object(m) => m.clone(),
248 _ => unreachable!("check_type 已保证为块"),
249 };
250 check_type_contract_ref(&c.name, k, spec, &mut sub, contracts)?;
251 node.insert(k.clone(), Value::Object(sub));
252 } else {
253 check_type(&c.name, k, spec, v, contracts)?;
254 }
255 }
256 }
257 }
258 Ok(())
259}
260
261fn check_type_contract_ref(
263 contract: &str,
264 field: &str,
265 spec: &FieldSpec,
266 sub: &mut BTreeMap<String, Value>,
267 contracts: &BTreeMap<String, Contract>,
268) -> Result<(), String> {
269 let ref_name = match &spec.ty {
270 TypeSpec::ContractRef(n) => n.clone(),
271 _ => return Ok(()),
272 };
273 let target = contracts.get(&ref_name).ok_or_else(|| {
274 format!(
275 "sml: 字段 `{}` 引用了未定义的契约 `{}`(契约 `{}`)",
276 field, ref_name, contract
277 )
278 })?;
279 check_type(contract, field, spec, &Value::Object(sub.clone()), contracts)?;
281 apply_contract(target, sub, contracts)
282}
283
284#[derive(Debug, Clone, PartialEq)]
289enum Tok {
290 LBrace, RBrace, LBrack, RBrack, Comma, Colon, At, Str(String), Word(String), }
300
301fn tokenize(text: &str) -> Result<Vec<Tok>, String> {
302 let mut toks = Vec::new();
303 let mut chars = text.chars().peekable();
304 let mut buf = String::new();
305 let mut flush = |buf: &mut String, toks: &mut Vec<Tok>| {
306 if !buf.is_empty() {
307 toks.push(Tok::Word(std::mem::take(buf)));
308 }
309 };
310 while let Some(c) = chars.next() {
311 match c {
312 '#' => {
313 for c2 in chars.by_ref() {
315 if c2 == '\n' {
316 break;
317 }
318 }
319 }
320 '-' => {
321 if chars.peek() == Some(&'-') {
323 chars.next(); for c2 in chars.by_ref() {
325 if c2 == '\n' {
326 break;
327 }
328 }
329 } else {
330 buf.push(c);
331 }
332 }
333 '/' => {
334 match chars.peek() {
335 Some('/') => {
337 chars.next(); for c2 in chars.by_ref() {
339 if c2 == '\n' {
340 break;
341 }
342 }
343 }
344 Some('*') => {
346 chars.next(); loop {
348 match chars.next() {
349 Some('*') => {
350 if chars.peek() == Some(&'/') {
351 chars.next();
352 break;
353 }
354 }
355 Some(_) => {}
356 None => break,
357 }
358 }
359 }
360 _ => buf.push(c),
362 }
363 }
364 '_' => {
365 if chars.peek() == Some(&'*') {
367 chars.next(); loop {
369 match chars.next() {
370 Some('*') => {
371 if chars.peek() == Some(&'_') {
372 chars.next();
373 break;
374 }
375 }
376 Some(_) => {}
377 None => break,
378 }
379 }
380 } else {
381 buf.push(c);
382 }
383 }
384 '"' => {
385 flush(&mut buf, &mut toks);
386 let mut s = String::new();
387 loop {
388 match chars.next() {
389 Some('"') => break,
390 Some('\\') => {
391 match chars.next() {
393 Some('n') => s.push('\n'),
394 Some('t') => s.push('\t'),
395 Some('r') => s.push('\r'),
396 Some('0') => s.push('\0'),
397 Some('"') => s.push('"'),
398 Some('\\') => s.push('\\'),
399 Some('u') => {
400 let mut hex = String::new();
401 if chars.peek() == Some(&'{') {
403 chars.next();
404 for c2 in chars.by_ref() {
405 if c2 == '}' {
406 break;
407 }
408 hex.push(c2);
409 }
410 } else {
411 for _ in 0..4 {
412 if let Some(c2) = chars.next() {
413 hex.push(c2);
414 } else {
415 return Err(format!(
420 "sml: 字符串转义 \\u 缺少足够的十六进制数字(期望 4 位,得 {hex:?})"
421 ));
422 }
423 }
424 }
425 if hex.is_empty() {
428 return Err("sml: 字符串转义 \\u 后缺少十六进制数字".to_string());
429 }
430 let cp = u32::from_str_radix(&hex, 16).map_err(|_| {
431 format!("sml: 字符串转义 \\u 含非十六进制数字:{hex:?}")
432 })?;
433 let ch = char::from_u32(cp).ok_or_else(|| {
434 format!("sml: 字符串转义 \\u 得到非法 Unicode 码点:U+{cp:04X}")
435 })?;
436 s.push(ch);
437 }
438 Some(other) => s.push(other),
439 None => {
441 return Err(
442 "sml: 字符串中的转义符 \\ 后遇到文件结束".to_string()
443 )
444 }
445 }
446 }
447 Some(other) => s.push(other),
448 None => return Err("sml: 字符串未闭合(缺少结束引号 \")".to_string()),
451 }
452 }
453 toks.push(Tok::Str(s));
454 }
455 '{' => {
456 flush(&mut buf, &mut toks);
457 toks.push(Tok::LBrace);
458 }
459 '}' => {
460 flush(&mut buf, &mut toks);
461 toks.push(Tok::RBrace);
462 }
463 '[' => {
464 flush(&mut buf, &mut toks);
465 toks.push(Tok::LBrack);
466 }
467 ']' => {
468 flush(&mut buf, &mut toks);
469 toks.push(Tok::RBrack);
470 }
471 ',' => {
472 flush(&mut buf, &mut toks);
473 toks.push(Tok::Comma);
474 }
475 ':' => {
476 flush(&mut buf, &mut toks);
477 toks.push(Tok::Colon);
478 }
479 '@' => {
480 if buf.is_empty() {
485 toks.push(Tok::At);
486 } else {
487 buf.push(c);
488 }
489 }
490 ' ' | '\t' | '\n' | '\r' => {
491 flush(&mut buf, &mut toks);
492 }
493 _ => {
494 buf.push(c);
495 }
496 }
497 }
498 flush(&mut buf, &mut toks);
499 Ok(toks)
500}
501
502fn coerce_word(
508 w: &str,
509 fragments: &BTreeMap<String, Value>,
510 features: FeatureSet,
511 ns_prefix: &str,
512) -> Result<Value, String> {
513 match w {
514 "true" => return Ok(Value::Bool(true)),
515 "false" => return Ok(Value::Bool(false)),
516 "null" => return Ok(Value::Null),
517 _ => {}
518 }
519 if let Some(ev) = w.strip_prefix("$env.") {
521 if !features.has(Feature::Env) {
522 return Err(format!("sml: 当前特性集禁用了 `$env`(env),裸词 `{}` 无法解析", w));
523 }
524 return Ok(Value::Str(std::env::var(ev).unwrap_or_default()));
525 }
526 if let Some(name) = w.strip_prefix('&') {
528 if !features.has(Feature::Fragment) {
529 return Err(format!("sml: 当前特性集禁用了片段引用(fragment),`{}` 无法解析", w));
530 }
531 if let Some(v) = fragments.get(name) {
532 return Ok(v.clone());
533 }
534 if !ns_prefix.is_empty() {
536 let mut probe = ns_prefix.to_string();
537 loop {
538 let full = format!("{probe}.{name}");
539 if let Some(v) = fragments.get(&full) {
540 return Ok(v.clone());
541 }
542 match probe.rfind('.') {
543 Some(idx) => probe.truncate(idx),
544 None => break,
545 }
546 }
547 }
548 return Ok(Value::Str(w.to_string()));
549 }
550 if let Ok(i) = w.parse::<i64>() {
552 return Ok(Value::Int(i));
553 }
554 let looks_int = !w.contains(['.', 'e', 'E']) && w.chars().all(|c| c.is_ascii_digit() || c == '+');
559 if looks_int {
560 if let Ok(u) = w.parse::<u64>() {
561 if u > i64::MAX as u64 {
563 return Ok(Value::Str(w.to_string()));
564 }
565 }
566 return Ok(Value::Str(w.to_string()));
567 }
568 if let Ok(f) = w.parse::<f64>() {
569 return Ok(Value::Float(f));
570 }
571 if !features.has(Feature::BarewordStr) {
572 return Err(format!(
573 "sml: 字符串必须加引号,裸词 `{}` 应写作 `\"{}\"`(特性 bareword-string 已禁用)",
574 w, w
575 ));
576 }
577 Ok(Value::Str(w.to_string()))
578}
579
580struct Parser {
581 toks: Vec<Tok>,
582 i: usize,
583 fragments: BTreeMap<String, Value>,
584 contracts: BTreeMap<String, Contract>,
586 features: FeatureSet,
588 depth: usize,
591 ns_stack: Vec<String>,
595}
596
597impl Parser {
598 fn ns_prefix(&self) -> String {
600 if self.ns_stack.is_empty() {
601 String::new()
602 } else {
603 self.ns_stack.join(".")
604 }
605 }
606
607 fn qualify(&self, name: &str) -> String {
609 let p = self.ns_prefix();
610 if p.is_empty() {
611 name.to_string()
612 } else {
613 format!("{p}.{name}")
614 }
615 }
616
617 fn peek(&self) -> Option<&Tok> {
618 self.toks.get(self.i)
619 }
620 fn next(&mut self) -> Option<Tok> {
621 let t = self.toks.get(self.i).cloned();
622 if t.is_some() {
623 self.i += 1;
624 }
625 t
626 }
627
628 fn parse_contract_body(&mut self) -> Result<BTreeMap<String, FieldSpec>, String> {
630 let mut fields: BTreeMap<String, FieldSpec> = BTreeMap::new();
631 loop {
632 match self.peek().cloned() {
633 None | Some(Tok::RBrace) => {
634 self.next();
635 break;
636 }
637 Some(Tok::Comma) => {
638 self.next();
639 }
640 _ => {
641 let key = match self.next() {
642 Some(Tok::Word(s)) | Some(Tok::Str(s)) => s,
643 other => {
644 return Err(format!("sml: 契约字段期望键, 得 {:?}", other))
645 }
646 };
647 if self.peek() == Some(&Tok::Colon) {
648 self.next();
649 } else {
650 return Err(format!("sml: 契约字段 `{}` 后须有冒号", key));
651 }
652 let spec = self.parse_field_spec()?;
653 fields.insert(key, spec);
654 }
655 }
656 }
657 Ok(fields)
658 }
659
660 fn parse_field_spec(&mut self) -> Result<FieldSpec, String> {
662 let ty = match self.next() {
663 Some(Tok::Word(w)) => match w.as_str() {
664 "str" => TypeSpec::Str,
665 "int" => TypeSpec::Int,
666 "num" => TypeSpec::Num,
667 "bool" => TypeSpec::Bool,
668 "any" => TypeSpec::Any,
669 "enum" => {
670 if self.peek() != Some(&Tok::LBrack) {
671 return Err("sml: `enum` 后须为 [ ... ]".into());
672 }
673 self.next();
674 let mut vals = Vec::new();
675 loop {
676 match self.peek().cloned() {
677 None | Some(Tok::RBrack) => {
678 self.next();
679 break;
680 }
681 Some(Tok::Comma) => {
682 self.next();
683 }
684 Some(Tok::Word(s)) | Some(Tok::Str(s)) => {
685 vals.push(s);
686 self.next();
687 }
688 _ => {
689 self.next();
690 }
691 }
692 }
693 TypeSpec::Enum(vals)
694 }
695 other => TypeSpec::ContractRef(other.to_string()),
699 },
700 Some(Tok::LBrack) => {
701 let inner = match self.next() {
702 Some(Tok::Word(w)) => match w.as_str() {
703 "str" => TypeSpec::Str,
704 "int" => TypeSpec::Int,
705 "num" => TypeSpec::Num,
706 "bool" => TypeSpec::Bool,
707 "any" => TypeSpec::Any,
708 other => {
709 return Err(format!("sml: 未知数组元素类型 `{}`", other))
710 }
711 },
712 other => {
713 return Err(format!("sml: 数组元素类型期望标识符, 得 {:?}", other))
714 }
715 };
716 if self.peek() == Some(&Tok::RBrack) {
717 self.next();
718 }
719 TypeSpec::Array(Box::new(inner))
720 }
721 other => return Err(format!("sml: 字段类型期望标识符, 得 {:?}", other)),
722 };
723
724 let mut required = true;
726 let mut default = None;
727 let mut min = None;
728 let mut max = None;
729 loop {
730 let is_next_field = matches!(self.peek(), Some(Tok::Word(_)))
732 && matches!(self.toks.get(self.i + 1), Some(Tok::Colon));
733 if is_next_field {
734 break;
735 }
736 match self.peek().cloned() {
737 Some(Tok::Word(w)) => match w.as_str() {
738 "optional" => {
739 required = false;
740 self.next();
741 }
742 "required" => {
743 required = true;
744 self.next();
745 }
746 "default" => {
747 self.next();
748 default = Some(match self.next() {
749 Some(Tok::Word(w2)) => coerce_word(&w2, &self.fragments, self.features, &self.ns_prefix())?,
750 Some(Tok::Str(s)) => Value::Str(s),
751 other => {
752 return Err(format!("sml: default 期望值, 得 {:?}", other))
753 }
754 });
755 }
756 "min" => {
757 self.next();
758 min = Some(self.parse_spec_number()?);
759 }
760 "max" => {
761 self.next();
762 max = Some(self.parse_spec_number()?);
763 }
764 _ => break,
765 },
766 _ => break,
767 }
768 }
769 Ok(FieldSpec { ty, required, default, min, max })
770 }
771
772 fn parse_spec_number(&mut self) -> Result<f64, String> {
773 match self.next() {
774 Some(Tok::Word(w)) => {
775 w.parse::<f64>().map_err(|_| format!("sml: 期望数字, 得 `{}`", w))
776 }
777 other => Err(format!("sml: 期望数字, 得 {:?}", other)),
778 }
779 }
780
781 fn parse_block(&mut self, closing: Option<Tok>) -> Result<Value, String> {
785 if self.depth >= MAX_VALUE_DEPTH {
786 return Err(format!(
787 "sml: 嵌套过深(超过 {} 层),疑似递归或恶意输入",
788 MAX_VALUE_DEPTH
789 ));
790 }
791 self.depth += 1;
792 let r = self.parse_block_inner(closing);
793 self.depth -= 1;
794 r
795 }
796
797 fn parse_block_inner(&mut self, closing: Option<Tok>) -> Result<Value, String> {
798 let mut node: BTreeMap<String, Value> = BTreeMap::new();
799 let mut applied_contract: Option<String> = None;
801 loop {
802 let tok = match self.peek().cloned() {
803 None => break,
804 Some(t) => t,
805 };
806 match tok {
807 Tok::RBrace | Tok::RBrack => {
808 if let Some(cl) = &closing {
809 if *cl == tok {
810 self.next();
811 break;
812 }
813 }
814 break;
816 }
817 Tok::Comma => {
818 self.next();
819 }
820 Tok::At => {
821 self.next();
823 let fname = match self.next() {
824 Some(Tok::Word(s)) | Some(Tok::Str(s)) => s,
825 _ => return Err("sml: @ 后需片段名".into()),
826 };
827 if self.peek() == Some(&Tok::Colon) {
828 self.next();
829 }
830 if fname == "contract" {
832 if !self.features.has(Feature::Contract) {
833 return Err("@contract 需要特性 `contract`,但当前特性集已禁用".into());
834 }
835 let cname = match self.next() {
836 Some(Tok::Word(s)) | Some(Tok::Str(s)) => s,
837 other => {
838 return Err(format!("sml: @contract 后须契约名, 得 {:?}", other))
839 }
840 };
841 let mut allow_extra = false;
844 if let Some(Tok::Word(w)) = self.peek().cloned() {
845 if w == "loose" {
846 allow_extra = true;
847 self.next();
848 }
849 }
850 if self.peek() != Some(&Tok::LBrace) {
851 return Err(format!("sml: @contract {} 后须 {{ ... }}", cname));
852 }
853 self.next();
854 let fields = self.parse_contract_body()?;
855 self.contracts.insert(
857 self.qualify(&cname),
858 Contract {
859 name: self.qualify(&cname),
860 fields,
861 allow_extra,
862 },
863 );
864 continue;
865 }
866 if fname == "is" {
868 if !self.features.has(Feature::Contract) {
869 return Err("@is 需要特性 `contract`,但当前特性集已禁用".into());
870 }
871 let cname = match self.next() {
872 Some(Tok::Word(s)) | Some(Tok::Str(s)) => s,
873 other => {
874 return Err(format!("sml: @is 后须契约名, 得 {:?}", other))
875 }
876 };
877 let resolved = if self.contracts.contains_key(&cname) {
879 cname.clone()
880 } else {
881 self.qualify(&cname)
882 };
883 applied_contract = Some(resolved);
884 continue;
885 }
886 let mut ftype: Option<String> = None;
888 let mut farg: Option<String> = None;
889 if let Some(Tok::Word(s)) = self.peek().cloned() {
890 if *self.peek().unwrap() != Tok::LBrace {
891 self.next();
892 ftype = Some(s);
893 if let Some(Tok::Word(s2)) = self.peek().cloned() {
894 if *self.peek().unwrap() != Tok::LBrace {
895 self.next();
896 farg = Some(s2);
897 }
898 }
899 }
900 }
901 if self.peek() == Some(&Tok::LBrace) {
902 self.next();
903 let mut sub = match self.parse_block(Some(Tok::RBrace))? {
904 Value::Object(m) => m,
905 other => {
906 let mut m = BTreeMap::new();
907 m.insert("_value".into(), other);
908 m
909 }
910 };
911 if let Some(t) = ftype {
912 sub.insert("__type".into(), Value::Str(t));
913 }
914 if let Some(a) = farg {
915 sub.insert("__name".into(), Value::Str(a));
916 }
917 if !self.features.has(Feature::Fragment) {
918 return Err(format!(
919 "sml: 片段定义 `@{}` 需要特性 `fragment`,但当前特性集已禁用",
920 fname
921 ));
922 }
923 self.fragments.insert(self.qualify(&fname), Value::Object(sub));
925 }
926 }
927 _ => {
928 let key = match self.next() {
930 Some(Tok::Word(s)) | Some(Tok::Str(s)) => s,
931 other => return Err(format!("sml: 期望键, 得 {:?}", other)),
932 };
933 let colon = self.peek() == Some(&Tok::Colon);
934 if colon {
935 self.next();
936 }
937 let val = self.parse_value(&key, colon)?;
938 if let Some(existing) = node.get_mut(&key) {
940 match existing {
941 Value::Array(a) => a.push(val),
942 _ => {
943 let old = node.remove(&key).unwrap();
944 node.insert(key, Value::Array(vec![old, val]));
945 }
946 }
947 } else {
948 node.insert(key, val);
949 }
950 }
951 }
952 }
953 if let Some(cname) = applied_contract {
955 let c = self
956 .contracts
957 .get(&cname)
958 .cloned()
959 .ok_or_else(|| format!("sml: 未定义的契约 `{}`", cname))?;
960 apply_contract(&c, &mut node, &self.contracts)?;
961 }
962 Ok(Value::Object(node))
963 }
964
965 fn parse_value(&mut self, key: &str, colon: bool) -> Result<Value, String> {
967 if !colon && matches!(self.peek(), Some(Tok::Word(_))) {
969 let mut probe = self.i;
971 let mut found_block = false;
972 while probe < self.toks.len() {
973 match &self.toks[probe] {
974 Tok::Word(_) | Tok::Str(_) => probe += 1,
975 Tok::LBrace => {
976 found_block = true;
977 break;
978 }
979 _ => break,
980 }
981 }
982 if found_block {
983 let mut args: Vec<Value> = Vec::new();
985 while let Some(t) = self.peek().cloned() {
986 match t {
987 Tok::Word(w) => {
988 args.push(coerce_word(&w, &self.fragments, self.features, &self.ns_prefix())?);
989 self.next();
990 }
991 Tok::Str(_) => {
992 if let Some(Tok::Str(s)) = self.next() {
993 args.push(Value::Str(s));
994 }
995 }
996 _ => break,
997 }
998 }
999 if self.peek() == Some(&Tok::LBrace) {
1000 self.next();
1001 self.ns_stack.push(key.to_string());
1003 let mut sub = self.parse_block(Some(Tok::RBrace))?;
1004 self.ns_stack.pop();
1005 if let Value::Object(m) = &mut sub {
1006 m.insert("__type".into(), Value::Str(key.to_string()));
1007 if args.len() == 1 {
1008 m.insert("__name".into(), args.remove(0));
1009 }
1010 }
1011 return Ok(sub);
1012 }
1013 }
1014 }
1015 match self.peek().cloned() {
1016 Some(Tok::LBrace) => {
1017 self.next();
1018 self.parse_block(Some(Tok::RBrace))
1019 }
1020 Some(Tok::LBrack) => {
1021 self.next();
1022 self.parse_array()
1023 }
1024 Some(tok @ (Tok::Word(_) | Tok::Str(_))) => {
1025 let v = match tok {
1026 Tok::Word(w) => coerce_word(&w, &self.fragments, self.features, &self.ns_prefix())?,
1027 Tok::Str(s) => {
1028 let ev = s.strip_prefix("$env.");
1029 match ev {
1030 Some(name) => Value::Str(std::env::var(name).unwrap_or_default()),
1031 None => Value::Str(s),
1032 }
1033 }
1034 _ => unreachable!(),
1035 };
1036 self.next();
1037 Ok(v)
1038 }
1039 Some(Tok::RBrace) | Some(Tok::RBrack) | Some(Tok::Comma) | None => {
1041 if colon {
1042 Ok(Value::Null)
1044 } else {
1045 Ok(coerce_word(key, &self.fragments, self.features, &self.ns_prefix())?)
1046 }
1047 }
1048 _ => Err("sml: 语法错误".into()),
1049 }
1050 }
1051
1052 fn parse_array(&mut self) -> Result<Value, String> {
1055 if self.depth >= MAX_VALUE_DEPTH {
1056 return Err(format!(
1057 "sml: 嵌套过深(超过 {} 层),疑似递归或恶意输入",
1058 MAX_VALUE_DEPTH
1059 ));
1060 }
1061 self.depth += 1;
1062 let r = self.parse_array_inner();
1063 self.depth -= 1;
1064 r
1065 }
1066
1067 fn parse_array_inner(&mut self) -> Result<Value, String> {
1068 let mut arr = Vec::new();
1069 loop {
1070 match self.peek().cloned() {
1071 None => break,
1072 Some(Tok::RBrack) => {
1073 self.next();
1074 break;
1075 }
1076 Some(Tok::Comma) => {
1077 self.next();
1078 }
1079 Some(Tok::LBrace) => {
1080 self.next();
1081 arr.push(self.parse_block(Some(Tok::RBrace))?);
1082 }
1083 Some(Tok::LBrack) => {
1084 self.next();
1085 arr.push(self.parse_array_inner()?);
1086 }
1087 Some(Tok::Word(w)) => {
1088 arr.push(coerce_word(&w, &self.fragments, self.features, &self.ns_prefix())?);
1089 self.next();
1090 }
1091 Some(Tok::Str(_)) => {
1092 if let Some(Tok::Str(s)) = self.next() {
1093 arr.push(Value::Str(s));
1094 }
1095 }
1096 _ => break,
1097 }
1098 }
1099 Ok(Value::Array(arr))
1100 }
1101}
1102
1103#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
1108pub enum Version {
1109 V1,
1111 V2,
1113 V3,
1116}
1117
1118impl Version {
1119 pub const CURRENT: Version = Version::V3;
1121
1122 pub fn strict_strings(self) -> bool {
1124 self >= Version::V2
1125 }
1126
1127 pub(crate) fn from_word(w: &str) -> Option<Version> {
1129 match w {
1130 "v1" | "1" => Some(Version::V1),
1131 "v2" | "2" => Some(Version::V2),
1132 "v3" | "3" => Some(Version::V3),
1133 _ => None,
1134 }
1135 }
1136
1137 pub fn name(self) -> &'static str {
1139 match self {
1140 Version::V1 => "v1",
1141 Version::V2 => "v2",
1142 Version::V3 => "v3",
1143 }
1144 }
1145}
1146
1147impl fmt::Display for Version {
1148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1149 f.write_str(self.name())
1150 }
1151}
1152
1153#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1167pub enum Feature {
1168 BarewordStr,
1170 Include,
1172 Env,
1174 Contract,
1176 Fragment,
1178 TopArray,
1180 Namespace,
1182 ImplicitNs,
1184 MultiInclude,
1186 GlobInclude,
1188 RegexInclude,
1190 ExtRewrite,
1192}
1193
1194pub fn feature_names() -> Vec<&'static str> {
1198 FEATURES.iter().map(|(n, _)| *n).collect()
1199}
1200
1201pub static FEATURES: &[(&str, Feature)] = &[
1203 ("bareword-string", Feature::BarewordStr),
1204 ("include", Feature::Include),
1205 ("env", Feature::Env),
1206 ("contract", Feature::Contract),
1207 ("fragment", Feature::Fragment),
1208 ("top-level-array", Feature::TopArray),
1209 ("namespace", Feature::Namespace),
1210 ("implicit-ns", Feature::ImplicitNs),
1211 ("multi-include", Feature::MultiInclude),
1212 ("glob-include", Feature::GlobInclude),
1213 ("regex-include", Feature::RegexInclude),
1214 ("ext-rewrite", Feature::ExtRewrite),
1215];
1216
1217impl Feature {
1218 pub fn from_name(name: &str) -> Option<Feature> {
1220 FEATURES.iter().find(|(n, _)| *n == name).map(|(_, f)| *f)
1221 }
1222
1223 pub fn name(self) -> &'static str {
1225 FEATURES
1226 .iter()
1227 .find(|(_, f)| *f == self)
1228 .map(|(n, _)| *n)
1229 .unwrap_or("<unknown>")
1230 }
1231}
1232
1233#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1239pub struct FeatureSet(u64);
1240
1241impl FeatureSet {
1242 pub fn all() -> FeatureSet {
1245 let mut m = 0u64;
1246 for (_, f) in FEATURES {
1247 m |= 1 << (*f as u8);
1248 }
1249 FeatureSet(m)
1250 }
1251
1252 pub fn baseline() -> FeatureSet {
1256 FeatureSet::none()
1257 .with(Feature::BarewordStr)
1258 .with(Feature::Include)
1259 .with(Feature::Env)
1260 .with(Feature::Contract)
1261 .with(Feature::Fragment)
1262 .with(Feature::TopArray)
1263 .with(Feature::Namespace)
1264 .with(Feature::ImplicitNs)
1265 }
1266
1267 pub fn none() -> FeatureSet {
1269 FeatureSet(0)
1270 }
1271
1272 pub fn for_version(v: Version) -> FeatureSet {
1276 let mut s = FeatureSet::baseline();
1277 if v.strict_strings() {
1280 s = s.without(Feature::BarewordStr);
1281 } else {
1282 s = s.with(Feature::BarewordStr);
1283 }
1284 s
1285 }
1286
1287 pub fn has(self, f: Feature) -> bool {
1289 (self.0 & (1 << (f as u8))) != 0
1290 }
1291
1292 pub fn with(self, f: Feature) -> FeatureSet {
1294 FeatureSet(self.0 | (1 << (f as u8)))
1295 }
1296
1297 pub fn without(self, f: Feature) -> FeatureSet {
1299 FeatureSet(self.0 & !(1 << (f as u8)))
1300 }
1301
1302 pub fn intersection(self, other: FeatureSet) -> FeatureSet {
1304 FeatureSet(self.0 & other.0)
1305 }
1306
1307 pub fn is_empty(self) -> bool {
1309 self.0 == 0
1310 }
1311}
1312
1313impl fmt::Display for FeatureSet {
1314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1315 let mut first = true;
1316 for (n, feat) in FEATURES {
1317 if self.has(*feat) {
1318 if !first {
1319 f.write_str(",")?;
1320 }
1321 f.write_str(n)?;
1322 first = false;
1323 }
1324 }
1325 if first {
1326 f.write_str("<none>")?;
1327 }
1328 Ok(())
1329 }
1330}
1331
1332#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1334enum FeatureMode {
1335 Default,
1336 Whitelist,
1337 Blacklist,
1338}
1339
1340fn tok_word(t: &Tok) -> String {
1342 match t {
1343 Tok::Word(s) | Tok::Str(s) => s.clone(),
1344 _ => String::new(),
1345 }
1346}
1347
1348fn apply_feature_directive(
1361 line: &str,
1362 feats: &mut FeatureSet,
1363 mode: &mut FeatureMode,
1364 base: &mut Option<Version>,
1365) -> Result<bool, String> {
1366 let content = strip_line_comment(line).trim();
1367 let toks = match tokenize(content) {
1368 Ok(t) => t,
1369 Err(_) => return Ok(false),
1370 };
1371 if toks.is_empty() || toks[0] != Tok::At {
1372 return Ok(false);
1373 }
1374 let words: Vec<String> = toks
1375 .iter()
1376 .map(|t| match t {
1377 Tok::At => "@".to_string(),
1378 other => tok_word(other),
1379 })
1380 .collect();
1381 let head = format!("{}{}", words.first().map(|s| s.as_str()).unwrap_or(""), words.get(1).map(|s| s.as_str()).unwrap_or(""));
1383 if head != "@feature" {
1384 return Ok(false);
1385 }
1386 let words: Vec<String> = words[1..].to_vec();
1388 if words.len() < 2 {
1389 return Err("@feature 指令缺少参数".into());
1390 }
1391 let arg = words[1].as_str();
1392 let names = |from: usize| -> Vec<String> {
1394 words[from..]
1395 .join(",")
1396 .split(',')
1397 .map(|s| s.trim().to_string())
1398 .filter(|s| !s.is_empty())
1399 .collect()
1400 };
1401 match arg {
1402 "base" => {
1403 let v = Version::from_word(words.get(2).map(|s| s.as_str()).unwrap_or(""))
1404 .ok_or_else(|| {
1405 format!(
1406 "@feature base 需要 v1/v2/v3,收到 `{}`",
1407 words.get(2).cloned().unwrap_or_default()
1408 )
1409 })?;
1410 *feats = FeatureSet::for_version(v);
1411 *base = Some(v);
1412 Ok(true)
1413 }
1414 "mode" => {
1415 let m = words.get(2).map(|s| s.as_str()).unwrap_or("");
1416 *mode = match m {
1417 "whitelist" => FeatureMode::Whitelist,
1418 "blacklist" => FeatureMode::Blacklist,
1419 _ => return Err(format!("@feature mode 需要 whitelist/blacklist,收到 `{m}`")),
1420 };
1421 if *mode == FeatureMode::Whitelist {
1422 *feats = FeatureSet::none();
1424 }
1425 Ok(true)
1426 }
1427 "enable" => {
1428 for n in names(2) {
1433 let f = Feature::from_name(&n).ok_or_else(|| {
1434 format!(
1435 "未知特性 `{n}`,可用:{}",
1436 FEATURES.iter().map(|(n, _)| *n).collect::<Vec<_>>().join(", ")
1437 )
1438 })?;
1439 *feats = feats.with(f);
1440 }
1441 Ok(true)
1442 }
1443 "disable" => {
1444 for n in names(2) {
1445 let f = Feature::from_name(&n).ok_or_else(|| {
1446 format!(
1447 "未知特性 `{n}`,可用:{}",
1448 FEATURES.iter().map(|(n, _)| *n).collect::<Vec<_>>().join(", ")
1449 )
1450 })?;
1451 *feats = feats.without(f);
1452 }
1453 Ok(true)
1454 }
1455 "whitelist" => {
1456 *mode = FeatureMode::Whitelist;
1457 let mut s = FeatureSet::none();
1458 for n in names(2) {
1459 let f = Feature::from_name(&n).ok_or_else(|| {
1460 format!(
1461 "未知特性 `{n}`,可用:{}",
1462 FEATURES.iter().map(|(n, _)| *n).collect::<Vec<_>>().join(", ")
1463 )
1464 })?;
1465 s = s.with(f);
1466 }
1467 *feats = s;
1468 Ok(true)
1469 }
1470 "blacklist" => {
1471 let mut s = FeatureSet::all();
1472 for n in names(2) {
1473 let f = Feature::from_name(&n).ok_or_else(|| {
1474 format!(
1475 "未知特性 `{n}`,可用:{}",
1476 FEATURES.iter().map(|(n, _)| *n).collect::<Vec<_>>().join(", ")
1477 )
1478 })?;
1479 s = s.without(f);
1480 }
1481 *feats = s;
1482 Ok(true)
1483 }
1484 _ => Err(format!("未知 @feature 子命令 `{arg}`,可用 base/mode/enable/disable")),
1485 }
1486}
1487
1488fn strip_features(text: &str) -> Result<(String, FeatureSet, Option<Version>, bool), String> {
1498 let mut out = String::new();
1499 let mut feats = FeatureSet::all();
1500 let mut mode = FeatureMode::Default;
1501 let mut base: Option<Version> = None;
1502 let mut had_feature = false;
1503
1504 for line in text.lines() {
1505 match apply_feature_directive(line, &mut feats, &mut mode, &mut base) {
1506 Ok(true) => {
1507 had_feature = true;
1508 continue; }
1510 Ok(false) => {}
1511 Err(e) => return Err(e), }
1513 out.push_str(line);
1514 out.push('\n');
1515 }
1516 Ok((out, feats, base, had_feature))
1517}
1518
1519fn version_directive(line: &str) -> Result<Option<String>, String> {
1523 let content = strip_line_comment(line).trim();
1524 let toks = match tokenize(content) {
1526 Ok(t) => t,
1527 Err(_) => return Ok(None),
1528 };
1529 match toks.as_slice() {
1530 [Tok::At, Tok::Word(w), Tok::Word(v)] if w == "version" => Ok(Some(v.clone())),
1531 [Tok::At, Tok::Word(w), Tok::Str(v)] if w == "version" => Ok(Some(v.clone())),
1532 [Tok::At, Tok::Word(w), ..] if w == "version" => Err(
1533 "`@version` 是版本声明指令,须写作 `@version v1`;`version` 不可作为片段名".into(),
1534 ),
1535 _ => Ok(None),
1536 }
1537}
1538
1539pub(crate) fn strip_version(text: &str) -> Result<(String, Option<Version>), String> {
1544 let mut declared: Option<Version> = None;
1545 let mut rest = String::new();
1546 for line in text.lines() {
1547 if let Some(lit) = version_directive(line)? {
1548 let v = Version::from_word(&lit).ok_or_else(|| {
1549 format!(
1550 "不支持的 SML 版本 `{lit}`(本实现支持 {})",
1551 Version::CURRENT.name()
1552 )
1553 })?;
1554 match declared {
1555 None => declared = Some(v),
1556 Some(prev) if prev != v => {
1557 return Err(format!("@version 冲突:{} 与 {}", prev.name(), v.name()))
1558 }
1559 Some(_) => {}
1560 }
1561 continue;
1562 }
1563 rest.push_str(line);
1564 rest.push('\n');
1565 }
1566 Ok((rest, declared))
1567}
1568
1569fn features_for(v: Version, feats: FeatureSet, had_feature: bool) -> FeatureSet {
1577 if had_feature {
1578 feats
1579 } else {
1580 FeatureSet::for_version(v)
1581 }
1582}
1583
1584pub fn parse_versioned(text: &str) -> Result<(Value, Version), String> {
1589 let (rest, declared) = strip_version(text)?;
1590 let (rest, feats, base, had) = strip_features(&rest)?;
1591 let v = declared.or(base).unwrap_or(Version::V1);
1593 let feats = features_for(v, feats, had);
1594 Ok((parse_impl(&rest, v, feats)?, v))
1595}
1596
1597pub fn parse_file_versioned(path: impl AsRef<Path>) -> Result<(Value, Version), String> {
1599 let path = path.as_ref();
1600 let text =
1601 std::fs::read_to_string(path).map_err(|e| format!("读取失败 {}: {e}", path.display()))?;
1602 let base = path
1603 .parent()
1604 .map(|p| p.to_path_buf())
1605 .unwrap_or_else(|| PathBuf::from("."));
1606 let (rest, declared) = strip_version(&text)?;
1607 let (rest, feats, base_ver, had) = strip_features(&rest)?;
1608 let allowed = FeatureSet::all().intersection(feats);
1609 let v = declared.or(base_ver).unwrap_or(Version::V1);
1610 let feats = features_for(v, allowed, had);
1611 let toks = resolve_includes(&rest, &base, allowed)?;
1612 let val = parse_impl_tokens(toks, v, feats)?;
1613 Ok((val, v))
1614}
1615
1616pub fn parse(text: &str) -> Result<Value, String> {
1624 let (rest, declared) = strip_version(text)?;
1625 let (rest, feats, base, had) = strip_features(&rest)?;
1626 let v = declared.or(base).unwrap_or(Version::V1);
1627 let feats = features_for(v, feats, had);
1628 parse_impl(&rest, v, feats)
1629}
1630
1631pub fn parse_allowed(
1639 text: &str,
1640 allowed: &[Version],
1641) -> Result<Value, String> {
1642 let (rest, declared) = strip_version(text)?;
1643 let (rest, feats, base, had) = strip_features(&rest)?;
1644 let v = declared.or(base).unwrap_or(Version::V1);
1645 if !allowed.contains(&v) {
1646 return Err(format!(
1647 "sml: 文档声明版本 {} 不在本库接受的版本范围 {{{}}} 内",
1648 v.name(),
1649 allowed
1650 .iter()
1651 .map(|x| x.name())
1652 .collect::<Vec<_>>()
1653 .join(", ")
1654 ));
1655 }
1656 let feats = features_for(v, feats, had);
1657 parse_impl(&rest, v, feats)
1658}
1659
1660pub fn parse_with_features(
1669 text: &str,
1670 allowed: FeatureSet,
1671) -> Result<(Value, FeatureSet), String> {
1672 let (rest, declared) = strip_version(text)?;
1673 let (rest, feats, base, had) = strip_features(&rest)?;
1674 let v = declared.or(base).unwrap_or(Version::V1);
1675 let feats = features_for(v, feats, had);
1676 let effective = feats.intersection(allowed);
1677 if effective.is_empty() {
1678 return Err(format!(
1679 "sml: 文档请求的特性 {feats} 与调用方允许的特性 {allowed} 无交集"
1680 ));
1681 }
1682 let val = parse_impl(&rest, v, effective)?;
1683 Ok((val, effective))
1684}
1685
1686fn parse_impl(text: &str, version: Version, features: FeatureSet) -> Result<Value, String> {
1688 let toks = tokenize(text)?;
1689 parse_impl_tokens(toks, version, features)
1690}
1691
1692fn parse_impl_tokens(
1694 toks: Vec<Tok>,
1695 version: Version,
1696 features: FeatureSet,
1697) -> Result<Value, String> {
1698 let mut p = Parser {
1699 toks,
1700 i: 0,
1701 fragments: BTreeMap::new(),
1702 contracts: BTreeMap::new(),
1703 features,
1704 depth: 0,
1705 ns_stack: Vec::new(),
1706 };
1707 match p.peek() {
1715 Some(Tok::LBrack) => {
1716 if !p.features.has(Feature::TopArray) {
1717 return Err("sml: 顶层数组需要特性 `top-level-array`,但当前特性集已禁用".into());
1718 }
1719 p.next();
1720 p.parse_array()
1721 }
1722 Some(Tok::LBrace) => {
1723 p.next();
1724 p.parse_block(Some(Tok::RBrace))
1725 }
1726 _ => p.parse_block(None),
1727 }
1728}
1729
1730const MAX_VALUE_DEPTH: usize = 128;
1752
1753const MAX_INCLUDE_DEPTH: usize = 32;
1755
1756pub(crate) fn strip_line_comment(line: &str) -> &str {
1758 let bytes = line.as_bytes();
1759 let mut i = 0;
1760 let mut in_quote = false;
1761 while i < bytes.len() {
1762 match bytes[i] {
1763 b'"' => in_quote = !in_quote,
1764 b'\\' if in_quote => i += 1,
1766 b'#' if !in_quote => return &line[..i],
1767 _ => {}
1768 }
1769 i += 1;
1770 }
1771 line
1772}
1773
1774#[derive(Debug, Clone, PartialEq, Eq)]
1776pub struct IncludeTarget {
1777 pub raw: String,
1779 pub namespace: Option<String>,
1782 pub via_import: bool,
1784 pub keys: Option<Vec<String>>,
1787}
1788
1789pub(crate) fn parse_include_line(line: &str, features: FeatureSet) -> Result<Option<Vec<IncludeTarget>>, String> {
1810 let content = strip_line_comment(line).trim();
1811 let content = content.strip_prefix('@').unwrap_or(content).trim_start();
1812 let (via_import, rest) = if let Some(r) = content.strip_prefix("include ") {
1815 (false, r.trim_start())
1816 } else if let Some(r) = content.strip_prefix("import ") {
1817 (true, r.trim_start())
1818 } else {
1819 return Ok(None);
1820 };
1821 if !features.has(Feature::Include) {
1822 return Ok(None);
1823 }
1824 let mut targets: Vec<IncludeTarget> = Vec::new();
1825 let mut rest = rest;
1826 loop {
1827 let (raw, ns, keys, tail) = if rest.trim_start().starts_with('{') {
1832 let (keys, after) = parse_key_list(rest.trim_start())?;
1834 let after = after.trim_start();
1835 let (ns, after) = if let Some(stripped) = after.strip_prefix("as ") {
1837 let (n, t) = match next_token(stripped.trim_start()) {
1838 Some((n, t)) => (Some(n), t.trim_start()),
1839 None => return Ok(None),
1840 };
1841 (n, t)
1842 } else {
1843 (None, after)
1844 };
1845 let after = after.trim_start();
1847 let after = match after.strip_prefix("in ") {
1848 Some(a) => a.trim_start(),
1849 None => {
1850 return Err(
1851 "sml: `import { keys } ...` 必须接 `in \"file\"` 指定目标文件".into(),
1852 )
1853 }
1854 };
1855 let (path, t) = match next_token(after) {
1856 Some((p, t)) => (p, t),
1857 None => return Ok(None),
1858 };
1859 (path, ns, Some(keys), t)
1860 } else {
1861 let (path, tail0) = match next_token(rest) {
1863 Some((p, t)) => (p, t),
1864 None => {
1865 if targets.is_empty() && rest.trim().is_empty() {
1866 return Ok(None);
1867 } else {
1868 break;
1869 }
1870 }
1871 };
1872 let mut r = tail0.trim_start();
1873 let mut ns: Option<String> = None;
1875 if let Some(stripped) = r.strip_prefix("as ") {
1876 let (n, t) = match next_token(stripped.trim_start()) {
1877 Some((n, t)) => (n, t),
1878 None => return Ok(None),
1879 };
1880 ns = Some(n);
1881 r = t.trim_start();
1882 }
1883 let keys = if r.starts_with('{') {
1885 let (k, after) = parse_key_list(r)?;
1886 r = after.trim_start();
1887 Some(k)
1888 } else {
1889 None
1890 };
1891 (path, ns, keys, r)
1892 };
1893 targets.push(finalize_target(
1894 raw,
1895 ns,
1896 via_import,
1897 features,
1898 keys,
1899 ));
1900 if let Some(stripped) = tail.strip_prefix(',') {
1902 if !features.has(Feature::MultiInclude) {
1903 return Ok(None);
1904 }
1905 rest = stripped.trim_start();
1906 continue;
1907 } else {
1908 rest = tail;
1909 break;
1910 }
1911 }
1912 if targets.is_empty() {
1913 return Ok(None);
1914 }
1915 for t in &targets {
1917 if t.keys.is_some() && (t.raw.contains('*') || t.raw.starts_with("re:")) {
1919 return Err(
1920 "sml: 部分引用 `{ keys }` 不能配合 glob/regex 通配(请指定单个文件)".into(),
1921 );
1922 }
1923 if t.raw.starts_with("re:") {
1925 if !features.has(Feature::RegexInclude) {
1926 return Err("sml: 正则 include 需要特性 `regex-include`(请 @feature enable regex-include)".into());
1927 }
1928 continue;
1929 }
1930 if t.raw.contains('*') && !features.has(Feature::GlobInclude) {
1931 return Err("sml: 通配 include 需要特性 `glob-include`(请 @feature enable glob-include)".into());
1932 }
1933 }
1934 Ok(Some(targets))
1935}
1936
1937fn next_token(s: &str) -> Option<(String, &str)> {
1940 let s = s.trim_start();
1941 if s.is_empty() {
1942 return None;
1943 }
1944 if s.starts_with('"') {
1945 let bytes = s.as_bytes();
1947 let mut i = 1;
1948 let mut out = String::new();
1949 while i < bytes.len() {
1950 if bytes[i] == b'"' {
1951 i += 1;
1952 break;
1953 }
1954 if bytes[i] == b'\\' && i + 1 < bytes.len() {
1955 i += 1;
1957 out.push(bytes[i] as char);
1958 i += 1;
1959 } else {
1960 out.push(bytes[i] as char);
1961 i += 1;
1962 }
1963 }
1964 Some((out, &s[i..]))
1965 } else {
1966 let end = s
1968 .find(|c: char| c.is_whitespace() || c == ',')
1969 .unwrap_or(s.len());
1970 let (tok, tail) = s.split_at(end);
1971 Some((tok.trim().to_string(), tail))
1972 }
1973}
1974
1975fn parse_key_list(s: &str) -> Result<(Vec<String>, &str), String> {
1978 let s = s.trim_start();
1979 let Some(body) = s.strip_prefix('{') else {
1980 return Err("sml: 期望 `{ key1, key2, ... }` 键列表".into());
1981 };
1982 let close = body.find('}').ok_or("sml: 键列表缺少闭合 `}`")?;
1983 let inner = &body[..close];
1984 let mut keys: Vec<String> = Vec::new();
1985 for part in inner.split(',') {
1986 let part = part.trim();
1987 if part.is_empty() {
1988 continue;
1989 }
1990 if let Some(q) = part.strip_prefix('"') {
1992 let q = q.strip_suffix('"').unwrap_or(q);
1993 keys.push(q.to_string());
1994 } else {
1995 keys.push(part.to_string());
1996 }
1997 }
1998 if keys.is_empty() {
1999 return Err("sml: 键列表不能为空(至少指定一个键)".into());
2000 }
2001 Ok((keys, &body[close + 1..]))
2002}
2003
2004fn finalize_target(
2006 raw: String,
2007 ns: Option<String>,
2008 via_import: bool,
2009 features: FeatureSet,
2010 keys: Option<Vec<String>>,
2011) -> IncludeTarget {
2012 let namespace = match ns {
2013 Some(n) => Some(n),
2014 None => {
2015 if keys.is_some() {
2018 None
2019 } else if via_import || (features.has(Feature::ImplicitNs) && !raw.contains('.')) {
2020 Some(raw.clone())
2023 } else {
2024 None
2025 }
2026 }
2027 };
2028 IncludeTarget {
2029 raw,
2030 namespace,
2031 via_import,
2032 keys,
2033 }
2034}
2035
2036fn resolve_target_paths(
2044 t: &IncludeTarget,
2045 base: &Path,
2046 features: FeatureSet,
2047) -> Result<Vec<PathBuf>, String> {
2048 if let Some(pat) = t.raw.strip_prefix("re:") {
2050 if !features.has(Feature::RegexInclude) {
2051 return Err("sml: 正则 include 需要特性 `regex-include`(请 @feature enable regex-include)".into());
2052 }
2053 let pat = pat.trim_matches('"');
2054 let pat = pat.replace('/', std::path::MAIN_SEPARATOR_STR);
2056 let (dir, pat) = split_dir(&pat);
2057 return glob_or_regex_dir(&base.join(dir), pat, Some(pat), features);
2058 }
2059 if t.raw.contains('*') {
2061 if !features.has(Feature::GlobInclude) {
2062 return Err("sml: 通配 include 需要特性 `glob-include`(请 @feature enable glob-include)".into());
2063 }
2064 let normalized = t.raw.replace('/', std::path::MAIN_SEPARATOR_STR);
2065 let (dir, pat) = split_dir(&normalized);
2066 return glob_or_regex_dir(&base.join(dir), pat, None, features);
2067 }
2068 let path = if t.via_import {
2070 if t.raw.contains(std::path::MAIN_SEPARATOR) || t.raw.ends_with(".sml") {
2074 base.join(&t.raw)
2075 } else {
2076 let rel = t
2077 .raw
2078 .split('.')
2079 .collect::<Vec<_>>()
2080 .join(std::path::MAIN_SEPARATOR_STR);
2081 base.join(rel).with_extension("sml")
2082 }
2083 } else if t.raw.contains('.') {
2084 let _ = features.has(Feature::ExtRewrite);
2088 base.join(&t.raw)
2089 } else {
2090 base.join(format!("{}.sml", t.raw))
2091 };
2092 Ok(vec![path])
2093}
2094
2095fn split_dir(pat: &str) -> (&str, &str) {
2098 match pat.rfind(std::path::MAIN_SEPARATOR) {
2099 Some(idx) => (&pat[..idx], &pat[idx + 1..]),
2100 None => ("", pat),
2101 }
2102}
2103
2104fn glob_or_regex_dir(
2106 base: &Path,
2107 pattern: &str,
2108 regex: Option<&str>,
2109 _features: FeatureSet,
2110) -> Result<Vec<PathBuf>, String> {
2111 let mut hits: Vec<PathBuf> = Vec::new();
2112 let entries = std::fs::read_dir(base)
2113 .map_err(|e| format!("include 目录读取失败 {}: {e}", base.display()))?;
2114 let re = regex.map(|r| compile_regex(r));
2116 for ent in entries {
2117 let ent = ent.map_err(|e| format!("include 目录遍历失败: {e}"))?;
2118 let p = ent.path();
2119 if p.is_dir() {
2120 continue; }
2122 let name = match p.file_name().and_then(|n| n.to_str()) {
2123 Some(n) => n,
2124 None => continue,
2125 };
2126 let matched = if let Some(re) = &re {
2127 regex_matches(re, name)
2128 } else {
2129 let pat_file = pattern.rsplit(std::path::MAIN_SEPARATOR).next().unwrap_or(pattern);
2131 glob_matches(pat_file, name)
2132 };
2133 if matched {
2134 hits.push(p);
2135 }
2136 }
2137 hits.sort();
2139 Ok(hits)
2140}
2141
2142fn glob_matches(pattern: &str, text: &str) -> bool {
2144 let segs: Vec<&str> = pattern.split('*').collect();
2146 if segs.is_empty() {
2147 return text.is_empty();
2148 }
2149 let mut pos = 0usize;
2150 if !pattern.starts_with('*') {
2152 if !text[pos..].starts_with(segs[0]) {
2153 return false;
2154 }
2155 pos += segs[0].len();
2156 }
2157 for seg in &segs[if pattern.starts_with('*') { 0 } else { 1 }..] {
2158 if seg.is_empty() {
2159 continue;
2160 }
2161 match text[pos..].find(seg) {
2162 Some(idx) => pos += idx + seg.len(),
2163 None => return false,
2164 }
2165 }
2166 if !pattern.ends_with('*') {
2168 if pos != text.len() {
2169 return false;
2170 }
2171 }
2172 true
2173}
2174
2175struct MiniRegex {
2178 pattern: String,
2179}
2180
2181fn compile_regex(pat: &str) -> MiniRegex {
2182 MiniRegex {
2184 pattern: pat.to_string(),
2185 }
2186}
2187
2188fn regex_matches(re: &MiniRegex, text: &str) -> bool {
2190 let pat = &re.pattern;
2191 let anchored_start = pat.starts_with('^');
2192 let anchored_end = pat.ends_with('$');
2193 let p = if anchored_start { &pat[1..] } else { pat };
2194 let p = if anchored_end { &p[..p.len().saturating_sub(1)] } else { p };
2195 if anchored_start {
2197 backtrack_match(p, text, 0).is_some()
2198 } else {
2199 for start in 0..=text.len() {
2200 if backtrack_match(p, text, start).is_some() {
2201 if !anchored_end {
2202 return true;
2203 }
2204 if backtrack_match(p, text, start) == Some(text.len()) {
2206 return true;
2207 }
2208 }
2209 }
2210 false
2211 }
2212}
2213
2214fn backtrack_match(pat: &str, text: &str, ti: usize) -> Option<usize> {
2216 let pchars: Vec<char> = pat.chars().collect();
2218 let tchars: Vec<char> = text.chars().collect();
2219 fn go(pchars: &[char], tchars: &[char], pi: usize, ti: usize) -> Option<usize> {
2220 let mut pi = pi;
2221 let mut ti = ti;
2222 while pi < pchars.len() {
2223 match pchars[pi] {
2224 '\\' => {
2225 if pi + 1 >= pchars.len() {
2227 return None;
2228 }
2229 let pc = pchars[pi + 1];
2230 if ti >= tchars.len() || tchars[ti] != pc {
2231 return None;
2232 }
2233 pi += 2;
2234 ti += 1;
2235 }
2236 '.' => {
2237 if ti >= tchars.len() {
2238 return None;
2239 }
2240 pi += 1;
2241 ti += 1;
2242 }
2243 '*' => {
2244 let prev = if pi >= 1 { Some(pchars[pi - 1]) } else { None };
2247 if ti < tchars.len() {
2250 let mut end = ti;
2252 match prev {
2253 Some('.') => {
2254 while end < tchars.len() {
2255 end += 1;
2256 }
2257 }
2258 Some(c) if c != '\\' => {
2259 while end < tchars.len() && tchars[end] == c {
2260 end += 1;
2261 }
2262 }
2263 _ => {}
2264 }
2265 let mut e = end;
2267 while e >= ti {
2268 if let Some(r) = go(pchars, tchars, pi + 1, e) {
2269 return Some(r);
2270 }
2271 if e == ti {
2272 break;
2273 }
2274 e -= 1;
2275 }
2276 }
2277 return go(pchars, tchars, pi + 1, ti);
2279 }
2280 '+' => {
2281 if ti >= tchars.len() {
2282 return None;
2283 }
2284 let prev = pchars.get(pi.wrapping_sub(1)).copied();
2285 let mut consumed = 0;
2286 match prev {
2287 Some('.') => {
2288 if ti >= tchars.len() {
2289 return None;
2290 }
2291 consumed = 1;
2292 }
2293 Some(c) if c != '\\' => {
2294 if tchars[ti] != c {
2295 return None;
2296 }
2297 consumed = 1;
2298 while ti + consumed < tchars.len()
2299 && tchars[ti + consumed] == c
2300 {
2301 consumed += 1;
2302 }
2303 }
2304 _ => return None,
2305 }
2306 pi += 1;
2307 ti += consumed;
2308 }
2309 '?' => {
2310 let prev = pchars.get(pi.wrapping_sub(1)).copied();
2312 if ti < tchars.len() {
2313 match prev {
2314 Some('.') => {
2315 pi += 1;
2316 ti += 1;
2317 }
2318 Some(c) if c != '\\' => {
2319 if tchars[ti] == c {
2320 pi += 1;
2321 ti += 1;
2322 } else {
2323 pi += 1; }
2325 }
2326 _ => {
2327 pi += 1; }
2329 }
2330 } else {
2331 pi += 1;
2332 }
2333 }
2334 '[' => {
2335 let mut j = pi + 1;
2337 let negate = if j < pchars.len() && pchars[j] == '^' {
2338 j += 1;
2339 true
2340 } else {
2341 false
2342 };
2343 let mut cls = Vec::new();
2344 while j < pchars.len() && pchars[j] != ']' {
2345 if j + 2 < pchars.len()
2346 && pchars[j + 1] == '-'
2347 && pchars[j + 2] != ']'
2348 {
2349 let lo = pchars[j];
2350 let hi = pchars[j + 2];
2351 cls.push((lo, hi));
2352 j += 3;
2353 } else {
2354 cls.push((pchars[j], pchars[j]));
2355 j += 1;
2356 }
2357 }
2358 if j >= pchars.len() {
2359 return None; }
2361 if ti >= tchars.len() {
2362 return None;
2363 }
2364 let c = tchars[ti];
2365 let in_cls = cls.iter().any(|(lo, hi)| c >= *lo && c <= *hi);
2366 let ok = if negate { !in_cls } else { in_cls };
2367 if !ok {
2368 return None;
2369 }
2370 pi = j + 1;
2371 ti += 1;
2372 }
2373 c => {
2374 if ti >= tchars.len() || tchars[ti] != c {
2375 return None;
2376 }
2377 pi += 1;
2378 ti += 1;
2379 }
2380 }
2381 }
2382 Some(ti)
2383 }
2384 go(&pchars, &tchars, 0, ti)
2385}
2386
2387pub fn resolve_includes(
2399 text: &str,
2400 base: &Path,
2401 features: FeatureSet,
2402) -> Result<Vec<Tok>, String> {
2403 let mut stack: Vec<PathBuf> = Vec::new();
2404 let mut toks: Vec<Tok> = Vec::new();
2405 expand_includes(text, base, &mut stack, features, &mut toks)?;
2406 Ok(toks)
2407}
2408
2409fn expand_includes(
2411 text: &str,
2412 base: &Path,
2413 stack: &mut Vec<PathBuf>,
2414 features: FeatureSet,
2415 out: &mut Vec<Tok>,
2416) -> Result<(), String> {
2417 if stack.len() >= MAX_INCLUDE_DEPTH {
2418 return Err(format!("include 嵌套超过 {MAX_INCLUDE_DEPTH} 层"));
2419 }
2420 for line in text.lines() {
2421 match parse_include_line(line, features)? {
2422 Some(targets) => {
2423 if !features.has(Feature::Include) {
2424 return Err("sml: 当前特性集禁用了 include(include 特性)".into());
2425 }
2426 for t in targets {
2427 if t.namespace.is_some() && !features.has(Feature::Namespace) {
2428 return Err(
2429 "sml: 当前特性集禁用了命名空间包含(namespace 特性)".into(),
2430 );
2431 }
2432 let paths = resolve_target_paths(&t, base, features)?;
2434 for path in paths {
2435 let canon = path.canonicalize().map_err(|e| {
2436 format!("include 无法定位 {}: {e}", path.display())
2437 })?;
2438 if stack.iter().any(|p| p == &canon) {
2440 return Err(format!("include 循环引用: {}", canon.display()));
2441 }
2442 let content = std::fs::read_to_string(&canon)
2443 .map_err(|e| format!("include 读取失败 {}: {e}", canon.display()))?;
2444 let child_base = canon
2445 .parent()
2446 .map(|p| p.to_path_buf())
2447 .unwrap_or_else(|| PathBuf::from("."));
2448 stack.push(canon.clone());
2449 let mut inner =
2451 expand_file_tokens(&content, &child_base, stack, features)?;
2452 if let Some(keys) = &t.keys {
2454 inner = filter_top_level_keys(inner, keys);
2455 }
2456 if let Some(ns) = &t.namespace {
2458 for seg in ns.split('.') {
2459 out.push(Tok::Word(seg.to_string()));
2460 out.push(Tok::LBrace);
2461 }
2462 out.extend(inner);
2463 for _ in ns.split('.') {
2464 out.push(Tok::RBrace);
2465 }
2466 } else {
2467 out.extend(inner);
2468 }
2469 stack.pop();
2470 }
2471 }
2472 }
2473 None => {
2474 let line_toks = tokenize(line).map_err(|e| {
2476 format!("include 预处理词法错误:{e}(于行:{line})")
2477 })?;
2478 out.extend(line_toks);
2479 }
2480 }
2481 }
2482 Ok(())
2483}
2484
2485fn filter_top_level_keys(toks: Vec<Tok>, keys: &[String]) -> Vec<Tok> {
2491 let key_set: std::collections::HashSet<&str> = keys.iter().map(|s| s.as_str()).collect();
2492 let mut out: Vec<Tok> = Vec::with_capacity(toks.len());
2493 let mut i = 0;
2494 let n = toks.len();
2495 while i < n {
2496 if !matches!(toks[i], Tok::Word(_) | Tok::Str(_)) {
2498 out.push(toks[i].clone());
2499 i += 1;
2500 continue;
2501 }
2502 let key_name = match &toks[i] {
2503 Tok::Word(w) => w.clone(),
2504 Tok::Str(s) => s.clone(),
2505 _ => unreachable!(),
2506 };
2507 let j = if i + 1 < n {
2509 match &toks[i + 1] {
2510 Tok::Colon => {
2512 if i + 2 < n {
2513 match &toks[i + 2] {
2514 Tok::LBrace | Tok::LBrack => {
2516 let mut depth = 1i32;
2517 let mut k = i + 3;
2518 while k < n {
2519 match &toks[k] {
2520 Tok::LBrace | Tok::LBrack => depth += 1,
2521 Tok::RBrace | Tok::RBrack => {
2522 depth -= 1;
2523 if depth == 0 {
2524 break;
2525 }
2526 }
2527 _ => {}
2528 }
2529 k += 1;
2530 }
2531 (k + 1).min(n)
2532 }
2533 _ => i + 3,
2535 }
2536 } else {
2537 i + 2
2538 }
2539 }
2540 Tok::LBrace | Tok::LBrack => {
2542 let mut depth = 1i32;
2543 let mut k = i + 2;
2544 while k < n {
2545 match &toks[k] {
2546 Tok::LBrace | Tok::LBrack => depth += 1,
2547 Tok::RBrace | Tok::RBrack => {
2548 depth -= 1;
2549 if depth == 0 {
2550 break;
2551 }
2552 }
2553 _ => {}
2554 }
2555 k += 1;
2556 }
2557 (k + 1).min(n)
2558 }
2559 _ => i + 1,
2561 }
2562 } else {
2563 i + 1
2564 };
2565 if key_set.contains(key_name.as_str()) {
2566 for t in &toks[i..j] {
2567 out.push(t.clone());
2568 }
2569 }
2570 i = j;
2571 }
2572 out
2573}
2574
2575fn expand_file_tokens(
2576 content: &str,
2577 base: &Path,
2578 stack: &mut Vec<PathBuf>,
2579 features: FeatureSet,
2580) -> Result<Vec<Tok>, String> {
2581 let cleaned: String = content
2583 .lines()
2584 .filter(|l| {
2585 let t = strip_line_comment(l).trim();
2586 let t = t.strip_prefix('@').unwrap_or(t).trim_start();
2587 !(t.starts_with("version") || t.starts_with("feature"))
2588 })
2589 .collect::<Vec<_>>()
2590 .join("\n");
2591 let mut toks = Vec::new();
2592 expand_includes(&cleaned, base, stack, features, &mut toks)?;
2593 Ok(toks)
2594}
2595
2596pub fn parse_file(path: impl AsRef<Path>) -> Result<Value, String> {
2601 let path = path.as_ref();
2602 let text = std::fs::read_to_string(path)
2603 .map_err(|e| format!("读取失败 {}: {e}", path.display()))?;
2604 let base = path
2605 .parent()
2606 .map(|p| p.to_path_buf())
2607 .unwrap_or_else(|| PathBuf::from("."));
2608 let (rest, declared) = strip_version(&text)?;
2612 let (rest, feats, base_ver, had) = strip_features(&rest)?;
2613 let v = declared.or(base_ver).unwrap_or(Version::V1);
2614 let feats = features_for(v, feats, had);
2615 let allowed = FeatureSet::all().intersection(feats);
2616 let toks = resolve_includes(&rest, &base, allowed)?;
2617 parse_impl_tokens(toks, v, allowed)
2618}
2619
2620pub fn loads(text: &str) -> Result<Value, ParseError> {
2622 parse(text).map_err(ParseError)
2623}
2624
2625#[derive(Debug)]
2626pub struct ParseError(pub String);
2627
2628impl fmt::Display for ParseError {
2629 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2630 write!(f, "sml parse error: {}", self.0)
2631 }
2632}
2633
2634impl std::error::Error for ParseError {}
2635
2636fn needs_quote(s: &str) -> bool {
2654 if s.is_empty() {
2655 return true;
2656 }
2657 match s {
2658 "true" | "false" | "null" | "inf" | "nan" => return true,
2659 _ => {}
2660 }
2661 if s.parse::<i64>().is_ok() || s.parse::<f64>().is_ok() {
2663 return true;
2664 }
2665 if s.starts_with("--")
2667 || s.starts_with("//")
2668 || s.starts_with("/*")
2669 || s.starts_with("*/")
2670 || s.starts_with("*")
2671 || s.starts_with("_*")
2672 {
2673 return true;
2674 }
2675 if s.contains([' ', '\t', '\n', '\r', ':', '#', '{', '}', ',', '[', ']', '"', '\\', '/', '*'])
2677 {
2678 return true;
2679 }
2680 match s.chars().next() {
2682 Some(c) if c.is_alphabetic() || c == '_' => {}
2683 _ => return true,
2684 }
2685 !s.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-' || c == '.')
2688}
2689
2690fn quote_if_needed(s: &str) -> String {
2692 if needs_quote(s) {
2693 format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\""))
2694 } else {
2695 s.to_string()
2696 }
2697}
2698
2699fn dump_block(m: &BTreeMap<String, Value>, indent: usize, out: &mut String) {
2703 if m.is_empty() {
2704 out.push_str("{}");
2705 return;
2706 }
2707 out.push_str(&format!("\n{}{{", " ".repeat(indent)));
2708 for (k, val) in m {
2709 out.push_str(&format!(
2710 "\n{}{}: ",
2711 " ".repeat(indent + 1),
2712 quote_if_needed(k)
2713 ));
2714 dump_value(val, indent + 1, out);
2715 }
2716 out.push_str(&format!("\n{}}}", " ".repeat(indent)));
2717}
2718
2719fn dump_value(v: &Value, indent: usize, out: &mut String) {
2720 let pad = " ".repeat(indent);
2721 match v {
2722 Value::Null => out.push_str("null"),
2723 Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
2724 Value::Int(i) => out.push_str(&i.to_string()),
2725 Value::Float(f) => {
2726 if f.fract() == 0.0 {
2728 out.push_str(&format!("{:.1}", f));
2729 } else {
2730 out.push_str(&format!("{}", f));
2731 }
2732 }
2733 Value::Str(s) => out.push_str("e_if_needed(s)),
2734 Value::Array(a) => {
2735 if a.is_empty() {
2736 out.push_str("[]");
2737 } else {
2738 out.push('[');
2739 for e in a {
2740 out.push('\n');
2741 out.push_str(&format!("{}{}", " ".repeat(indent + 1), dump_inline(e)));
2742 }
2743 out.push_str(&format!("\n{}]", pad));
2744 }
2745 }
2746 Value::Object(m) => dump_block(m, indent, out),
2747 }
2748}
2749
2750fn dump_scalar(v: &Value) -> String {
2751 match v {
2752 Value::Null => "null".into(),
2753 Value::Bool(b) => b.to_string(),
2754 Value::Int(i) => i.to_string(),
2755 Value::Float(f) => f.to_string(),
2756 Value::Str(s) => quote_if_needed(s),
2757 _ => "".into(),
2758 }
2759}
2760
2761fn dump_inline(v: &Value) -> String {
2762 match v {
2763 Value::Object(m) => {
2764 let parts: Vec<String> = m
2766 .iter()
2767 .map(|(k, val)| format!("{}: {}", k, dump_inline(val)))
2768 .collect();
2769 format!("{{ {} }}", parts.join(", "))
2770 }
2771 Value::Array(a) => {
2772 let parts: Vec<String> = a.iter().map(dump_inline).collect();
2773 format!("[ {} ]", parts.join(", "))
2774 }
2775 other => dump_scalar(other),
2776 }
2777}
2778
2779pub fn to_sml(v: &Value) -> String {
2784 let mut out = String::new();
2785 if let Value::Object(m) = v {
2786 if m.contains_key("__type") {
2787 dump_block(m, 0, &mut out);
2788 } else {
2789 for (k, val) in m {
2790 out.push_str(&format!("{}: ", quote_if_needed(k)));
2791 dump_value(val, 0, &mut out);
2792 out.push('\n');
2793 }
2794 }
2795 } else {
2796 out.push_str(&dump_inline(v));
2797 }
2798 out
2799}