Skip to main content

sml/
lib.rs

1//! SML — SNOWARE Markup Language (Rust 实现, crate 名 `sml`)
2//!
3//! 声明式数据/配置格式, JSON/YAML 的替代品。语法与 Soup 生态的
4//! `lib/sml.soup` (Lua) 对齐:
5//!
6//! ```sml
7//! firstName: John
8//! age: 27
9//! address:
10//! {
11//!     streetAddress: "21 2nd Street"
12//!     state: NY
13//! }
14//! phoneNumbers: [ { type: home } { type: office } ]
15//! @base { region: cn-north-1 }
16//! server web { &base port: 8080 }
17//! ```
18//!
19//! 特性:
20//! - 引号可选 (裸词即字符串)
21//! - 块冒号可省 (`address { }` ≡ `address: { }`)
22//! - 数组分隔灵活 (逗号可选)
23//! - 片段继承 (`@name { }` 定义 / `&name` 引用)
24//! - `include "path"` 引入外部文件(见 [`parse_file`])
25//! - `$env.VAR` 环境变量内联
26//! - `#` 行注释
27//! - 类型自识别: true/false -> bool, null -> None, 数字 -> i64/f64, 其余 -> String
28//!
29//! 值模型: `Value` 枚举 (与 JSON 同构, 另加 `__type`/`__name` 裸块元数据)。
30//!
31//! # 纯解析 vs 文件解析
32//!
33//! [`parse`] 是**纯函数**(只吃字符串,不做 IO),因此不含 include 处理。
34//! 需要 include 时用 [`parse_file`],它会先展开指令再交给 `parse`。
35//! 这样设计保证了 `parse` 的可嵌入性(如 WASM / 沙箱内无文件系统)。
36//!
37//! # Cargo features
38//!
39//! - `serde`(默认关闭):为 `Value` 实现 `Serialize`/`Deserialize`,
40//!   可直接与 serde_json / serde_yaml / toml 等后端互操作。
41//!   不启用时本 crate **零依赖**。
42//!
43//! ```toml
44//! sml-rs = { version = "0.2", features = ["serde"] }
45//! ```
46
47use std::collections::BTreeMap;
48use std::fmt;
49use std::path::{Path, PathBuf};
50
51// ---------------------------------------------------------------------------
52// 值模型
53// ---------------------------------------------------------------------------
54
55#[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    /// 对象/块; `__type` / `__name` 裸块元数据以保留字键存放
64    Object(BTreeMap<String, Value>),
65}
66
67impl Value {
68    /// 对象字段按需取 (支持 "." 点路径)
69    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    /// 字符串视图 (字符串直接返回; 其它返回 None)
80    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// ---------------------------------------------------------------------------
95// 契约(Contract)—— 可选的 schema 层
96//
97// SML 本身是纯数据格式(与 JSON/YAML 同层),值模型只有 7 种类型,
98// **不具备**结构体定义、枚举、字段约束等类型系统能力。
99// 契约是在此之上的**可选校验层**,用于给块加上结构与取值约束:
100//
101// ```sml
102// @contract Server {
103//     host: str                      # 必填
104//     port: int default 8080         # 带默认值
105//     tls: bool default true
106//     tags: [str] optional           # 可选
107//     status: enum [ active retired ]
108//     ratio: num min 0 max 1
109// }
110//
111// database {
112//     @is Server                     # 应用契约
113//     host: db1.internal
114//     status: active
115// }
116// ```
117//
118// 语义:
119// - `@contract Name { ... }` 定义契约(不进主树)
120// - `@is Name` 在当前块应用契约:缺失字段用 default 填充;
121//   缺少且无默认值的必填字段、类型不符、枚举值越界、数值越 min/max 均报错
122// - 契约须在 `@is` **之前**定义(顺序依赖,与片段继承一致)
123// - 不使用契约时行为完全不变,因此**向后兼容**
124// ---------------------------------------------------------------------------
125
126/// 契约中的字段类型
127#[derive(Debug, Clone, PartialEq)]
128pub enum TypeSpec {
129    /// 任意类型
130    Any,
131    /// 引用另一个契约(**组合**)——字段值须是块,并递归按被引用契约校验。
132    /// 用组合而非继承:契约之间不共享字段,而是「字段的类型是另一个契约」。
133    /// 语法上复用裸词(写被引用的契约名),因此不引入任何新 token:
134    ///     @contract Address { city: str }
135    ///     @contract Server { address: Address }
136    ContractRef(String),
137    Str,
138    Int,
139    /// 数值:int 或 float 均可
140    Num,
141    Bool,
142    /// 数组,元素须为指定类型
143    Array(Box<TypeSpec>),
144    /// 枚举:取值须在给定列表中
145    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/// 契约中的字段规格
164#[derive(Debug, Clone)]
165pub struct FieldSpec {
166    pub ty: TypeSpec,
167    /// 是否必填(默认 true)
168    pub required: bool,
169    /// 缺失时填充的默认值
170    pub default: Option<Value>,
171    /// 数值下界(含)
172    pub min: Option<f64>,
173    /// 数值上界(含)
174    pub max: Option<f64>,
175}
176
177/// 契约(schema):一组字段规格
178#[derive(Debug, Clone)]
179pub struct Contract {
180    pub name: String,
181    pub fields: BTreeMap<String, FieldSpec>,
182    /// 是否允许契约未声明的字段。
183    /// **默认 false(严格)**:额外字段一律报错,可及早发现拼写错误
184    /// (如 `prot` 误写为 `port`)。确需放宽时须**显式**写 `loose`。
185    pub allow_extra: bool,
186}
187
188/// 校验值是否符合类型规格。
189/// `contracts` 供 `ContractRef`(组合)递归查找被引用契约。
190fn check_type(
191    contract: &str,
192    field: &str,
193    spec: &FieldSpec,
194    v: &Value,
195    contracts: &BTreeMap<String, Contract>,
196) -> Result<(), String> {
197    // 组合:字段值是块,递归按被引用的契约校验(含填默认值)
198    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        // 裸词数字会被 coerce 成 Int/Float,故枚举也接受被 coerce 成标量的情形
232        (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    // 数值区间
255    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
295/// 对块应用契约:填充默认值 + 校验 + 严格性检查。
296///
297/// **严格为默认**:契约未声明的字段会被拒绝,除非契约显式标记 `loose`。
298/// 这样拼错的字段名(如 `prot`)会立即报错,而不是被静默忽略。
299fn apply_contract(
300    c: &Contract,
301    node: &mut BTreeMap<String, Value>,
302    contracts: &BTreeMap<String, Contract>,
303) -> Result<(), String> {
304    // 1) 严格性:未声明字段一律拒绝(组合字段本身已在 fields 声明,其
305    //    内部字段由被引用契约在自己的 apply_contract 中负责校验)
306    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    // 2) 逐字段:填默认值 + 类型/枚举/区间/组合校验
317    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                // 组合会回填子块默认值,故需要可变副本
331                if matches!(spec.ty, TypeSpec::ContractRef(_)) {
332                    // 先按**原值**校验必须是块,否则会退化成
333                    // 「子字段缺失」这类误导性错误
334                    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
350/// 对「组合字段」递归应用被引用契约(会回填子块默认值)
351fn 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    // 先做基础类型校验(值须为块),再递归应用
369    check_type(contract, field, spec, &Value::Object(sub.clone()), contracts)?;
370    apply_contract(target, sub, contracts)
371}
372
373// ---------------------------------------------------------------------------
374// 解析: 词法 + 递归下降
375// ---------------------------------------------------------------------------
376
377#[derive(Debug, Clone, PartialEq)]
378enum Tok {
379    LBrace,  // {
380    RBrace,  // }
381    LBrack,  // [
382    RBrack,  // ]
383    Comma,   // ,
384    Colon,   // :
385    At,      // @
386    Str(String),   // 引号串 (已解码)
387    Word(String),  // 裸词
388}
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                // 注释到行尾
403                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                            // 转义:\n \t \r \0 \" \\ \u{XXXX} \uXXXX
417                            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                                    // 支持 \u{XXXX} 或 \uXXXX
427                                    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                // `@` 仅当位于**词首**时才是片段定义标记(`@base { ... }`)。
484                // 出现在词中间时(典型如邮箱 `a@b.c`)必须作为普通字符保留:
485                // 否则 `a@b.c` 会被切成 `Word("a")` + `At` + `Word("b.c")`,
486                // 后半段在解析时被丢弃,导致邮箱静默损坏为 `a`。
487                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    // $env.VAR 内联
513    if let Some(ev) = w.strip_prefix("$env.") {
514        return Value::Str(std::env::var(ev).unwrap_or_default());
515    }
516    // 片段引用 &name
517    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    // 数字: int / float / 科学计数
524    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    /// 契约表:名 -> 契约。由 `@contract Name { ... }` 填充
538    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    /// 解析契约体:逐条读 `field: <类型> [修饰符...]`
554    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    /// 解析单个字段的类型与修饰符
586    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                // 非内置类型名 -> 视为**契约引用**(组合)。
621                // 这样「字段的类型是另一个契约」复用裸词表达,不引入新 token。
622                // 被引用的契约可在之后定义(校验发生在 @is 时,而非定义时)。
623                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        // 修饰符:required / optional / default <值> / min <数> / max <数>
650        let mut required = true;
651        let mut default = None;
652        let mut min = None;
653        let mut max = None;
654        loop {
655            // 若当前是 `标识符 :` 则视为下一个字段的开始,停止读修饰符
656            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    /// 解析对象/块, 直到遇到 closing (None=顶层)
707    fn parse_block(&mut self, closing: Option<Tok>) -> Result<Value, String> {
708        let mut node: BTreeMap<String, Value> = BTreeMap::new();
709        // 块内若声明了 `@is Name`,在块解析完成后应用契约
710        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                    // 顶层遇右括号也停
725                    break;
726                }
727                Tok::Comma => {
728                    self.next();
729                }
730                Tok::At => {
731                    // @name { ... } 片段定义 (不进主树)
732                    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                    // —— 契约定义:`@contract Name { ... }` ——
741                    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                        // 可选修饰符 `loose`:显式允许契约未声明的字段。
749                        // 严格是默认,放宽必须写出来(复用裸词,不引入新 token)。
750                        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                    // —— 契约应用:`@is Name`(在当前块内)——
769                    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                    // 可选 type [name] 参数
780                    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                    // key
815                    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                    // 同名冲突 -> 提升为数组
825                    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        // 块结束:若声明了 `@is`,应用契约(填默认值 + 校验 + 严格性检查)
840        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    /// 解析一个值 (在 key 之后)
852    fn parse_value(&mut self, key: &str, colon: bool) -> Result<Value, String> {
853        // 无冒号且后继是裸词: 可能是裸块 `type [name] { }`
854        if !colon && matches!(self.peek(), Some(Tok::Word(_))) {
855            // 预扫描: 收集参数直到 { / 结束; 若发现 { 则按裸块处理
856            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                // 裸块: key 为类型, 参数在 { 前
870                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            // 键后无值: `key }` / `key ]` / `key ,` / 行尾 —— key 本身即值 (片段引用/裸词)
923            Some(Tok::RBrace) | Some(Tok::RBrack) | Some(Tok::Comma) | None => {
924                if colon {
925                    // 有冒号但无值: 空值
926                    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/// SML 语法版本
968///
969/// SML 源于 eclog,演进中通过 `@version` 声明文档遵循的语法版本,
970/// 使解析器能在将来引入 v2 不兼容语法时仍正确读取旧文档。
971#[derive(Debug, Clone, Copy, PartialEq, Eq)]
972pub enum Version {
973    /// v1:初始公开版本
974    V1,
975}
976
977impl Version {
978    /// 当前实现支持的最新版本
979    pub const CURRENT: Version = Version::V1;
980
981    /// 解析版本字面量(`v1` / `1`)
982    fn from_word(w: &str) -> Option<Version> {
983        match w {
984            "v1" | "1" => Some(Version::V1),
985            _ => None,
986        }
987    }
988
989    /// 版本名(用于错误信息与序列化回显)
990    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
1003/// 若该行是 `@version` 声明,返回版本字面量;否则返回 None。
1004///
1005/// `version` 是保留字:不允许作为片段名(`@version { }`)使用。
1006fn version_directive(line: &str) -> Result<Option<String>, String> {
1007    let content = strip_line_comment(line).trim();
1008    // 词法失败的行(如未闭合引号)不是版本声明,交由主解析器报更准确的错
1009    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
1023/// 剥离 `@version` 声明行,返回剩余文本与声明的版本(未声明则为 None)。
1024///
1025/// 允许多次声明(include 进来的文件可各自声明),但必须一致;
1026/// 声明了实现不支持的版本时报错,避免静默按错误语法解析。
1027fn 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
1053/// 解析 SML 文本,并返回其声明的语法版本。
1054///
1055/// 未声明版本时按 `Version::CURRENT`(v1)处理,**既有文档不受影响**。
1056pub 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
1061/// 解析 SML 文件:展开 include,并返回其声明的语法版本
1062pub 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
1074/// 解析 SML 文本
1075///
1076/// 会自动识别并剥离 `@version` 声明(需要版本信息时用 [`parse_versioned`])。
1077pub fn parse(text: &str) -> Result<Value, String> {
1078    let (rest, _) = strip_version(text)?;
1079    parse_impl(&rest)
1080}
1081
1082/// 不含版本处理的底层解析
1083fn 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    // 顶层支持三种形态,与 `to_sml` 的输出对称:
1092    //   - `[ ... ]` 数组:to_sml 对非对象走 dump_inline,会输出顶层数组
1093    //     (如「历史记录」这类对象数组)。此前 parse 只认键值块,导致
1094    //     能序列化却读不回("期望键, 得 LBrack"),是不对称缺陷。
1095    //   - `{ ... }` 顶层对象块
1096    //   - 键值块(传统形态)
1097    // 注:顶层**标量**仍不可往返(SML 顶层需为容器),这是格式固有限制。
1098    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
1111// ---------------------------------------------------------------------------
1112// include 指令:把外部 .sml 文件内联进来
1113//
1114// 语法:`include "path.sml"` 或 `@include "path.sml"`(两种等价)
1115// 语义:**文本内联**(类似 C 的 #include),而非对象合并。
1116//   这样 include 可以出现在块内部引入一组字段,例如:
1117//       server web { &base include "common/port.sml" }
1118//   若做成对象合并就无法表达「注入若干字段到当前块」。
1119//
1120// 相对路径按**被包含文件自身所在目录**解析(与 C 预处理器一致),
1121// 而非进程工作目录,因此嵌套 include 时路径行为可预期。
1122// ---------------------------------------------------------------------------
1123
1124/// 嵌套深度上限:既防栈溢出,也让异常深层的引用尽早失败
1125const MAX_INCLUDE_DEPTH: usize = 32;
1126
1127/// 剥离行尾注释,正确跳过引号内的 `#`(如 `key: "a#b"` 中的 # 不是注释起点)
1128fn 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            // 引号内的反斜杠会转义下一个字符,需整体跳过
1136            b'\\' if in_quote => i += 1,
1137            b'#' if !in_quote => return &line[..i],
1138            _ => {}
1139        }
1140        i += 1;
1141    }
1142    line
1143}
1144
1145/// 若该行是 include 指令,返回目标路径;否则返回 None
1146fn 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    // 复用词法器处理路径,使含空格的路径(引号串)能被正确识别
1150    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
1158/// 把 text 中的 include 指令递归展开为不含指令的纯 SML 文本。
1159///
1160/// `base` 为相对路径的解析基准目录(通常是当前文件所在目录)。
1161/// 循环引用与缺失文件都会返回错误,不会静默跳过。
1162pub 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                // stack 是「当前正在展开的文件链」,命中即成环
1186                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
1208/// 解析 SML 文件,并展开其中的 include 指令。
1209///
1210/// 相对路径以**该文件所在目录**为基准。
1211pub 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
1223/// 解析到对象 (失败抛 `ParseError`)
1224pub 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
1239// ---------------------------------------------------------------------------
1240// 序列化
1241// ---------------------------------------------------------------------------
1242
1243fn 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(&quote_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
1319/// 序列化回 SML 文本 (round-trip)
1320pub 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
1337// ---------------------------------------------------------------------------
1338// C-ABI (cdylib, 供 C / 其它语言调用)
1339// ---------------------------------------------------------------------------
1340
1341use 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/// sml_parse(text) -> 返回 JSON 字符串 (调用方 sml_free 释放); 失败返回 NULL
1350#[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/// sml_dump(json) -> 接受 JSON 字符串, 序列化为 SML; 调用方 sml_free
1363#[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/// sml_free(p): 释放由 sml_parse / sml_dump 返回的字符串
1376#[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/// sml_version() -> 版本字符串 (调用方 sml_free)
1384#[no_mangle]
1385pub extern "C" fn sml_version() -> *mut c_char {
1386    cstr(concat!("sml ", env!("CARGO_PKG_VERSION")))
1387}
1388
1389// ---------------------------------------------------------------------------
1390// 内部: JSON <-> Value (供 C-ABI 便捷桥)
1391// ---------------------------------------------------------------------------
1392
1393fn 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// ---------------------------------------------------------------------------
1568// serde 支持(可选 feature:`serde`)
1569//
1570// 手写实现而非 `#[derive]`:derive 会把枚举表示为外部标签形式
1571//   Value::Int(5)  ->  {"Int":5}
1572// 而配置文件的实际用途希望是自然形状  5。
1573// 手写后 SML 的 Value 与 JSON 数据形状一致,可直接喂给
1574// serde_json / serde_yaml / toml 等任意 serde 后端。
1575//
1576// 不启用该 feature 时 crate 保持零依赖。
1577// ---------------------------------------------------------------------------
1578
1579#[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                // Vec<Value> / 逐项委托,递归依赖 Value 自身的 impl
1600                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            // 交给格式自行判断类型(JSON 的数字/字符串/数组/对象都能落到对应变体)
1618            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        // 超出 i64 的大整数退化为 Float,避免直接报错丢失数据
1650        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// ---------------------------------------------------------------------------
1688// 测试
1689// ---------------------------------------------------------------------------
1690
1691#[cfg(test)]
1692mod tests {
1693    use super::*;
1694
1695    // ---------------- version ----------------
1696
1697    #[test]
1698    fn version_defaults_to_current_when_absent() {
1699        // 既有文档没有版本声明,必须仍能解析且默认为当前版本
1700        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        // 若未剥离,`@version v1` 会被当成片段定义而解析异常
1715        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        // v2 尚未定义,优先报「不支持」
1731        assert!(!err.is_empty());
1732        // 两个都支持但不一致时的路径:v1 与 v1 不冲突
1733        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    // ---------------- include ----------------
1761
1762    /// 在临时目录下建文件,返回目录句柄(drop 时自动清理)
1763    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        // 关键:相对路径按「被包含文件自身目录」解析,而非进程工作目录
1796        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        // mid 在根,include sub/mid2;mid2 在 sub 内,include leaf.sml(相对 sub)
1800        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        // 文本内联语义:可在块内注入一组字段
1815        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        // 引号内的 # 不应被当成注释,否则 `include "a#b.sml"` 会被截断
1849        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        // `key: include` 是指令吗?不是——前面有 key 与冒号
1856        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    // ---------------- 邮箱 / 裸词中的 @ ----------------
1863
1864    #[test]
1865    fn email_in_bare_word_survives() {
1866        // 回归:裸词中的 `@` 曾被切成 At token,导致邮箱被截断为 `a`
1867        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        // 词首的 `@` 仍是片段定义标记,不能被上面的修改破坏。
1889        // 注:SML 的片段继承用法是「定义后作为值引用」(`k: &base`);
1890        // 块内裸写 `&base` 会被当作键,不属于本用例覆盖范围。
1891        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    // ---------------- 顶层数组 / 对象(与 to_sml 对称)----------------
1900
1901    #[test]
1902    fn toplevel_array_roundtrips() {
1903        // 回归:to_sml 能输出顶层数组,但 parse 曾只认键值块,
1904        // 导致「能写不能读」("期望键, 得 LBrack")。
1905        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    // ---------------- serde ----------------
1948
1949    #[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        // 自然形状:字符串就是字符串,数字就是数字,而非 {"Int":27}
1955        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        // 回归测试:数组元素是对象、对象里又有数组(如配置的条目列表)。
1979        // dump_inline 曾把嵌套数组缩略成 [..],导致 chunks 丢成 [".."]。
1980        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        // 回归测试:tokenizer 曾按字节 `as char` 逐个处理,
2021        // 把 UTF-8 多字节字符拆成 Latin-1 字符,导致
2022        // `"修复若干问题"` 解析后变成双编码乱码。
2023        let v = parse(r#"note: "修复若干问题""#).unwrap();
2024        assert_eq!(
2025            v.get("note").and_then(|x| x.as_str()),
2026            Some("修复若干问题"),
2027            "引号串中的中文不应被破坏"
2028        );
2029        // 裸词中文同样不能破坏
2030        let v2 = parse("region: 华北").unwrap();
2031        assert_eq!(v2.get("region").and_then(|x| x.as_str()), Some("华北"));
2032        // 转义 \u 序列
2033        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        // &base 展开为字段 (键名 "&base", 值=片段对象), 与 Lua 实现一致
2075        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}