Skip to main content

sml/
core.rs

1use std::collections::BTreeMap;
2use std::fmt;
3use std::path::{Path, PathBuf};
4use crate::value::Value;
5// ---------------------------------------------------------------------------
6// 契约(Contract)—— 可选的 schema 层
7//
8// SML 本身是纯数据格式(与 JSON/YAML 同层),值模型只有 7 种类型,
9// **不具备**结构体定义、枚举、字段约束等类型系统能力。
10// 契约是在此之上的**可选校验层**,用于给块加上结构与取值约束:
11//
12// ```sml
13// @contract Server {
14//     host: str                      # 必填
15//     port: int default 8080         # 带默认值
16//     tls: bool default true
17//     tags: [str] optional           # 可选
18//     status: enum [ active retired ]
19//     ratio: num min 0 max 1
20// }
21//
22// database {
23//     @is Server                     # 应用契约
24//     host: db1.internal
25//     status: active
26// }
27// ```
28//
29// 语义:
30// - `@contract Name { ... }` 定义契约(不进主树)
31// - `@is Name` 在当前块应用契约:缺失字段用 default 填充;
32//   缺少且无默认值的必填字段、类型不符、枚举值越界、数值越 min/max 均报错
33// - 契约须在 `@is` **之前**定义(顺序依赖,与片段继承一致)
34// - 不使用契约时行为完全不变,因此**向后兼容**
35// ---------------------------------------------------------------------------
36
37/// 契约中的字段类型
38#[derive(Debug, Clone, PartialEq)]
39pub enum TypeSpec {
40    /// 任意类型
41    Any,
42    /// 引用另一个契约(**组合**)——字段值须是块,并递归按被引用契约校验。
43    /// 用组合而非继承:契约之间不共享字段,而是「字段的类型是另一个契约」。
44    /// 语法上复用裸词(写被引用的契约名),因此不引入任何新 token:
45    ///     @contract Address { city: str }
46    ///     @contract Server { address: Address }
47    ContractRef(String),
48    Str,
49    Int,
50    /// 数值:int 或 float 均可
51    Num,
52    Bool,
53    /// 数组,元素须为指定类型
54    Array(Box<TypeSpec>),
55    /// 枚举:取值须在给定列表中
56    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/// 契约中的字段规格
75#[derive(Debug, Clone)]
76pub struct FieldSpec {
77    pub ty: TypeSpec,
78    /// 是否必填(默认 true)
79    pub required: bool,
80    /// 缺失时填充的默认值
81    pub default: Option<Value>,
82    /// 数值下界(含)
83    pub min: Option<f64>,
84    /// 数值上界(含)
85    pub max: Option<f64>,
86}
87
88/// 契约(schema):一组字段规格
89#[derive(Debug, Clone)]
90pub struct Contract {
91    pub name: String,
92    pub fields: BTreeMap<String, FieldSpec>,
93    /// 是否允许契约未声明的字段。
94    /// **默认 false(严格)**:额外字段一律报错,可及早发现拼写错误
95    /// (如 `prot` 误写为 `port`)。确需放宽时须**显式**写 `loose`。
96    pub allow_extra: bool,
97}
98
99/// 校验值是否符合类型规格。
100/// `contracts` 供 `ContractRef`(组合)递归查找被引用契约。
101fn check_type(
102    contract: &str,
103    field: &str,
104    spec: &FieldSpec,
105    v: &Value,
106    contracts: &BTreeMap<String, Contract>,
107) -> Result<(), String> {
108    // 组合:字段值是块,递归按被引用的契约校验(含填默认值)
109    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        // 裸词数字会被 coerce 成 Int/Float,故枚举也接受被 coerce 成标量的情形
143        (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    // 数值区间
166    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
206/// 对块应用契约:填充默认值 + 校验 + 严格性检查。
207///
208/// **严格为默认**:契约未声明的字段会被拒绝,除非契约显式标记 `loose`。
209/// 这样拼错的字段名(如 `prot`)会立即报错,而不是被静默忽略。
210fn apply_contract(
211    c: &Contract,
212    node: &mut BTreeMap<String, Value>,
213    contracts: &BTreeMap<String, Contract>,
214) -> Result<(), String> {
215    // 1) 严格性:未声明字段一律拒绝(组合字段本身已在 fields 声明,其
216    //    内部字段由被引用契约在自己的 apply_contract 中负责校验)
217    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    // 2) 逐字段:填默认值 + 类型/枚举/区间/组合校验
228    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                // 组合会回填子块默认值,故需要可变副本
242                if matches!(spec.ty, TypeSpec::ContractRef(_)) {
243                    // 先按**原值**校验必须是块,否则会退化成
244                    // 「子字段缺失」这类误导性错误
245                    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
261/// 对「组合字段」递归应用被引用契约(会回填子块默认值)
262fn 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    // 先做基础类型校验(值须为块),再递归应用
280    check_type(contract, field, spec, &Value::Object(sub.clone()), contracts)?;
281    apply_contract(target, sub, contracts)
282}
283
284// ---------------------------------------------------------------------------
285// 解析: 词法 + 递归下降
286// ---------------------------------------------------------------------------
287
288#[derive(Debug, Clone, PartialEq)]
289enum Tok {
290    LBrace,  // {
291    RBrace,  // }
292    LBrack,  // [
293    RBrack,  // ]
294    Comma,   // ,
295    Colon,   // :
296    At,      // @
297    Str(String),   // 引号串 (已解码)
298    Word(String),  // 裸词
299}
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                // 单行注释到行尾
314                for c2 in chars.by_ref() {
315                    if c2 == '\n' {
316                        break;
317                    }
318                }
319            }
320            '-' => {
321                // `--` 单行注释到行尾;否则作为普通字符
322                if chars.peek() == Some(&'-') {
323                    chars.next(); // 吃掉第二个 -
324                    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                    // `//` 单行注释到行尾
336                    Some('/') => {
337                        chars.next(); // 吃掉第二个 /
338                        for c2 in chars.by_ref() {
339                            if c2 == '\n' {
340                                break;
341                            }
342                        }
343                    }
344                    // `/*` 多行注释,直到 `*/`
345                    Some('*') => {
346                        chars.next(); // 吃掉 *
347                        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                    // 否则作为普通字符(如路径 a/b/c)
361                    _ => buf.push(c),
362                }
363            }
364            '_' => {
365                // `_*` 多行注释,直到 `*_`;否则作为普通字符
366                if chars.peek() == Some(&'*') {
367                    chars.next(); // 吃掉 *
368                    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                            // 转义:\n \t \r \0 \" \\ \u{XXXX} \uXXXX
392                            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                                    // 支持 \u{XXXX} 或 \uXXXX
402                                    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                                                // B8:\uXXXX 定长读 4 字符,
416                                                // 不足 4 位说明输入截断/非法,
417                                                // 必须把已读的 hex 当作失败处理
418                                                // 而非静默丢弃(否则会吃掉后续引号)。
419                                                return Err(format!(
420                                                    "sml: 字符串转义 \\u 缺少足够的十六进制数字(期望 4 位,得 {hex:?})"
421                                                ));
422                                            }
423                                        }
424                                    }
425                                    // B8:非法码点(如代理区 \uD800、空 hex、非 hex)
426                                    // 必须报错,不能静默丢弃并吞掉闭合引号。
427                                    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                                // B9:转义符后遇 EOF,未闭合的反斜杠报错
440                                None => {
441                                    return Err(
442                                        "sml: 字符串中的转义符 \\ 后遇到文件结束".to_string()
443                                    )
444                                }
445                            }
446                        }
447                        Some(other) => s.push(other),
448                        // B9:未闭合字符串(EOF 前没有闭合引号)必须报错,
449                        // 否则后续整行/整个文件会被静默吞并。
450                        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                // `@` 仅当位于**词首**时才是片段定义标记(`@base { ... }`)。
481                // 出现在词中间时(典型如邮箱 `a@b.c`)必须作为普通字符保留:
482                // 否则 `a@b.c` 会被切成 `Word("a")` + `At` + `Word("b.c")`,
483                // 后半段在解析时被丢弃,导致邮箱静默损坏为 `a`。
484                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
502/// 把裸词 `w` 转为 Value。
503///
504/// 受 `features` 控制:关闭 `BarewordStr` 后纯字符串裸词(如 `John`)被拒绝,
505/// 必须写作 `"John"`;仍允许的非字符串裸词:bool / null / 数字 /
506/// 片段引用 `&x`(需 `fragment`)/ 环境变量 `$env.X`(需 `env`)。
507fn 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    // $env.VAR 内联(需 env 特性)
520    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    // 片段引用 &name(需 fragment 特性)。命名空间隔离:先查裸名,再逐级查 ns 前缀。
527    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        // 逐级回退:ui.form.foo → form.foo → foo
535        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    // 数字: int / float / 科学计数
551    if let Ok(i) = w.parse::<i64>() {
552        return Ok(Value::Int(i));
553    }
554    // B10:整数超 i64 范围时,不能静默降级为 Float(会丢精度,
555    // 如 9223372036854775808 这类 uint64 上界 ID / 纳秒时间戳)。
556    // 若为纯整数形态则保留为字符串(round-trip 安全、零精度损失);
557    // 带小数点/科学计数符的才走 f64。
558    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            // 超过 i64 但属合法 uint64:保持整值语义,序列化为字符串不丢精度
562            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    /// 契约表:名 -> 契约。由 `@contract Name { ... }` 填充
585    contracts: BTreeMap<String, Contract>,
586    /// 生效特性集(已与调用方允许范围交集)
587    features: FeatureSet,
588    /// 当前值嵌套深度(块 / 数组的递归层数)。
589    /// 由 `parse_block` / `parse_array` 的 wrapper 维护,用于防止栈溢出。
590    depth: usize,
591    /// 命名空间栈:每个块(含 include `as ns` 产生的块)的名字依次入栈。
592    /// 宏/契约注册与引用时,按栈路径加前缀(如 `ui.form.Button`),
593    /// 使命名空间真正隔离宏,而非仅隔离数据键值。
594    ns_stack: Vec<String>,
595}
596
597impl Parser {
598    /// 当前命名空间前缀(栈路径用 "." 连接,空栈返回空串)
599    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    /// 把裸名套上当前命名空间前缀(若栈非空)
608    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    /// 解析契约体:逐条读 `field: <类型> [修饰符...]`
629    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    /// 解析单个字段的类型与修饰符
661    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                // 非内置类型名 -> 视为**契约引用**(组合)。
696                // 这样「字段的类型是另一个契约」复用裸词表达,不引入新 token。
697                // 被引用的契约可在之后定义(校验发生在 @is 时,而非定义时)。
698                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        // 修饰符:required / optional / default <值> / min <数> / max <数>
725        let mut required = true;
726        let mut default = None;
727        let mut min = None;
728        let mut max = None;
729        loop {
730            // 若当前是 `标识符 :` 则视为下一个字段的开始,停止读修饰符
731            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    /// 解析对象/块, 直到遇到 closing (None=顶层)。
782    /// 外层 wrapper:深度守卫,防止 `a{a{a{ ... }}}` 无限递归导致栈溢出。
783    /// 实际实现见 [`Parser::parse_block_inner`]。
784    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        // 块内若声明了 `@is Name`,在块解析完成后应用契约
800        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                    // 顶层遇右括号也停
815                    break;
816                }
817                Tok::Comma => {
818                    self.next();
819                }
820                Tok::At => {
821                    // @name { ... } 片段定义 (不进主树)
822                    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                    // —— 契约定义:`@contract Name { ... }` ——
831                    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                        // 可选修饰符 `loose`:显式允许契约未声明的字段。
842                        // 严格是默认,放宽必须写出来(复用裸词,不引入新 token)。
843                        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                        // 命名空间前缀隔离:块内的契约按当前 ns 栈路径注册
856                        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                    // —— 契约应用:`@is Name`(在当前块内)——
867                    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                        // 命名空间隔离:先按裸名查,再按当前 ns 前缀查
878                        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                    // 可选 type [name] 参数
887                    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                        // 命名空间前缀隔离:片段定义按当前 ns 栈路径注册
924                        self.fragments.insert(self.qualify(&fname), Value::Object(sub));
925                    }
926                }
927                _ => {
928                    // key
929                    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                    // 同名冲突 -> 提升为数组
939                    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        // 块结束:若声明了 `@is`,应用契约(填默认值 + 校验 + 严格性检查)
954        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    /// 解析一个值 (在 key 之后)
966    fn parse_value(&mut self, key: &str, colon: bool) -> Result<Value, String> {
967        // 无冒号且后继是裸词: 可能是裸块 `type [name] { }`
968        if !colon && matches!(self.peek(), Some(Tok::Word(_))) {
969            // 预扫描: 收集参数直到 { / 结束; 若发现 { 则按裸块处理
970            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                // 裸块: key 为类型, 参数在 { 前
984                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                    // 进入子块 = 进入该 block 名字的命名空间
1002                    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            // 键后无值: `key }` / `key ]` / `key ,` / 行尾 —— key 本身即值 (片段引用/裸词)
1040            Some(Tok::RBrace) | Some(Tok::RBrack) | Some(Tok::Comma) | None => {
1041                if colon {
1042                    // 有冒号但无值: 空值
1043                    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    /// 外层 wrapper:深度守卫,防止深度嵌套数组触发递归下降的栈溢出。
1053    /// 实际实现见 [`Parser::parse_array_inner`]。
1054    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/// SML 语法版本
1104///
1105/// SML 源于 eclog,演进中通过 `@version` 声明文档遵循的语法版本,
1106/// 使解析器能在将来引入 v2 不兼容语法时仍正确读取旧文档。
1107#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
1108pub enum Version {
1109    /// v1:初始公开版本。字符串可裸写(`name: John`),自动识别类型。
1110    V1,
1111    /// v2:草案版,引入「字符串必须显式引号」的不兼容语法(与 v3 同语义)。
1112    V2,
1113    /// v3:正式版。取消自动字符串无引号,自由文本必须写作 `"..."`;
1114    ///     数字 / bool / null / 片段引用 `&x` / 环境变量 `$env.X` 仍为裸词。
1115    V3,
1116}
1117
1118impl Version {
1119    /// 当前实现支持的最新版本
1120    pub const CURRENT: Version = Version::V3;
1121
1122    /// 是否要求字符串显式引号(v2 / v3 为严格模式)
1123    pub fn strict_strings(self) -> bool {
1124        self >= Version::V2
1125    }
1126
1127    /// 解析版本字面量(`v1`/`1`、`v2`/`2`、`v3`/`3`)
1128    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    /// 版本名(用于错误信息与序列化回显)
1138    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// ===========================================================================
1154// 特性集 (FeatureSet)
1155//
1156// 文档可通过 `@feature` 指令在「版本基线」之上做**裁剪**(窄化),调用方也可
1157// 通过 `parse_with_features` / `parse_allowed` 限制接受的子集。文档不能扩宽
1158// 调用方给出的范围——否则 `@feature` 就成了绕过限制的后门。
1159//
1160// 为保证五端(Rust/C/JS/C++/Lua)实现一致且易于维护,特性名与位定义集中
1161// 在此(见 [`FEATURES`] 表)。新增特性只需在表中加一行,并在对应 parser 处
1162// 用 `ps.features.has(Feature::Xxx)` 判定即可,无需散落大量 if。
1163// ===========================================================================
1164
1165/// 单个特性标识。与 [`FEATURES`] 表一一对应;改表即改全端。
1166#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1167pub enum Feature {
1168    /// 裸词即字符串(v1 行为)。v2/v3 关闭后字符串必须加引号。
1169    BarewordStr,
1170    /// `include "x.sml"` 文件包含。
1171    Include,
1172    /// `$env.VAR` 环境变量内插。
1173    Env,
1174    /// `@contract` / `@is` 契约系统。
1175    Contract,
1176    /// `&frag` / `@frag` 片段复用。
1177    Fragment,
1178    /// 顶层裸数组 `[ ... ]`(无键)。
1179    TopArray,
1180    /// `include "x.sml" as ns` 命名空间包含(高优先级前缀)。
1181    Namespace,
1182    /// 无扩展名的 `include "foo"` 默认等价于 `include "foo.sml" as foo`。
1183    ImplicitNs,
1184    /// 逗号分隔的多目标 `include "a", "b" as y` 与 `import` 别名。
1185    MultiInclude,
1186    /// 通配 `include "dir/*.sml"`(glob)。
1187    GlobInclude,
1188    /// 正则匹配 `include /re/`(需 `regex-include`)。
1189    RegexInclude,
1190    /// 扩展名重写 `include "x.conf" -> "x.sml"`(将非 sml 当 sml 解析)。
1191    ExtRewrite,
1192}
1193
1194/// 返回全部已注册特性的名字,顺序与 [`FEATURES`](即特性位序)一致。
1195///
1196/// C-ABI 的 `sml_feature_name(bit)` 依赖此顺序,测试中有对应守护用例。
1197pub fn feature_names() -> Vec<&'static str> {
1198    FEATURES.iter().map(|(n, _)| *n).collect()
1199}
1200
1201/// 特性名 → 枚举 的注册表。所有端共用同一组名字,保证跨语言一致。
1202pub 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    /// 按名字查特性;未知名字返回 None(调用方据此报错,杜绝静默 typo)。
1219    pub fn from_name(name: &str) -> Option<Feature> {
1220        FEATURES.iter().find(|(n, _)| *n == name).map(|(_, f)| *f)
1221    }
1222
1223    /// 特性名(用于报错 / 序列化回显)
1224    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/// 位掩码形式的特性集合。
1234///
1235/// 设计哲学:从极简到丰富、功能可裁剪。默认基线(`baseline()`)只开极简三件套
1236/// (`include` + `namespace` + `implicit-ns`),复杂能力(多目标 / glob / 正则 /
1237/// 扩展名重写)必须显式 `@feature enable` 才生效,避免重蹈 YAML 过度复杂的覆辙。
1238#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1239pub struct FeatureSet(u64);
1240
1241impl FeatureSet {
1242    /// 全部特性位(含所有 opt-in 能力)。用于「调用方允许全集」与版本基线,
1243    /// 实际默认并不开启这些——见 [`FeatureSet::baseline`]。
1244    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    /// 极简默认集(SML 核心可用能力)。这是 `parse_file` 的默认允许集;
1253    /// 仅「多目标 / glob / 正则 / 扩展名重写」等高级能力需文档内
1254    /// `@feature enable` 显式开启(避免重蹈 YAML 覆辙)。
1255    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    /// 空集合
1268    pub fn none() -> FeatureSet {
1269        FeatureSet(0)
1270    }
1271
1272    /// 按版本基线构造默认特性集:v1 极简默认(baseline)+ 裸词字符串;
1273    /// v2/v3 关闭裸词字符串(须引号)。复杂能力(glob/regex/multi...)仍默认关闭,
1274    /// 需文档 `@feature enable` 显式开启。
1275    pub fn for_version(v: Version) -> FeatureSet {
1276        let mut s = FeatureSet::baseline();
1277        // 严格模式(v2/v3)关闭裸词字符串;非严格(v1)开启。
1278        // 显式设置该位,确保与 baseline 默认值无关。
1279        if v.strict_strings() {
1280            s = s.without(Feature::BarewordStr);
1281        } else {
1282            s = s.with(Feature::BarewordStr);
1283        }
1284        s
1285    }
1286
1287    /// 是否包含某特性
1288    pub fn has(self, f: Feature) -> bool {
1289        (self.0 & (1 << (f as u8))) != 0
1290    }
1291
1292    /// 返回开启 `f` 后的副本
1293    pub fn with(self, f: Feature) -> FeatureSet {
1294        FeatureSet(self.0 | (1 << (f as u8)))
1295    }
1296
1297    /// 返回关闭 `f` 后的副本
1298    pub fn without(self, f: Feature) -> FeatureSet {
1299        FeatureSet(self.0 & !(1 << (f as u8)))
1300    }
1301
1302    /// 与另一集合取交集(用于「文档裁剪 ∩ 调用方允许」)
1303    pub fn intersection(self, other: FeatureSet) -> FeatureSet {
1304        FeatureSet(self.0 & other.0)
1305    }
1306
1307    /// 是否无任何特性
1308    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/// `@feature` 解析模式:白名单(仅启用列出的)/ 黑名单(禁用列出的)。
1333#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1334enum FeatureMode {
1335    Default,
1336    Whitelist,
1337    Blacklist,
1338}
1339
1340/// 取出 token 的字符串内容(Word / Str 都取其文本;其余返回空串)。
1341fn tok_word(t: &Tok) -> String {
1342    match t {
1343        Tok::Word(s) | Tok::Str(s) => s.clone(),
1344        _ => String::new(),
1345    }
1346}
1347
1348/// 若该行是 `@feature` 声明,则根据 `mode` / 操作更新 `feats`,并返回 true。
1349///
1350/// 支持语法(均不区分大小写,参数以空格分隔):
1351/// - `@feature base v3`              设定基线版本(等价于 `@version`,仅用于特性派生)
1352/// - `@feature mode whitelist`       后续 enable 仅保留所列(基集先清空)
1353/// - `@feature mode blacklist`       后续 disable 仅移除所列(基集保持全开)
1354/// - `@feature enable <name>[,...]`  开启特性(可逗号批量)
1355/// - `@feature disable <name>[,...]` 关闭特性
1356/// - `@feature whitelist <a,b>`      紧凑白名单
1357/// - `@feature blacklist <a,b>`      紧凑黑名单
1358///
1359/// 未知特性名一律报错,避免拼写错误静默失效。
1360fn 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    // @feature 词法上拆成 [@, feature],拼前两个 token 才是 "@feature"
1382    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    // 去掉首 token `@`,使后续 words[0]=="feature"
1387    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    // 把 `enable x,y,z` / `whitelist a,b` 的多名拆开
1393    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                // 白名单:基集先清空,后续 enable 显式置位
1423                *feats = FeatureSet::none();
1424            }
1425            Ok(true)
1426        }
1427        "enable" => {
1428            // 直接在「当前特性集」上叠加开启(不切换白名单语义)。
1429            // 这样 `@feature enable regex-include` 在 `@version v1` 文档上会保留
1430            // bareword-string 等默认特性,而非收窄为仅所列项。
1431            // 真正的「收窄为仅所列」由显式 `@feature mode whitelist` 控制。
1432            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
1488/// 在解析前剥离全部 `@feature` 指令,返回剩余文本、推导出的特性集,以及
1489/// 由 `@feature base vN` 声明的基线版本(若文档未用 `@version` 则采用它)。
1490///
1491/// 文档内部的 `@feature` 只能收窄;调用方允许范围由 `parse_with_features`
1492/// / `parse_allowed` 的 `allowed` 参数在入口处再次交集。
1493///
1494/// 返回值三元组:(剩余文本, 特性集, @feature base 声明的版本, 是否出现过 @feature 指令)。
1495/// 若文档从未声明 `@feature`,则 `had_feature=false`,调用方应改以版本基线派生特性集
1496/// (例如 v3 默认关闭裸词字符串)。
1497fn 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; // 指令行被消费,不进入剩余文本
1509            }
1510            Ok(false) => {}
1511            Err(e) => return Err(e), // 指令非法(如未知特性名)必须上浮,不能静默吞掉
1512        }
1513        out.push_str(line);
1514        out.push('\n');
1515    }
1516    Ok((out, feats, base, had_feature))
1517}
1518
1519/// 若该行是 `@version` 声明,返回版本字面量;否则返回 None。
1520///
1521/// `version` 是保留字:不允许作为片段名(`@version { }`)使用。
1522fn version_directive(line: &str) -> Result<Option<String>, String> {
1523    let content = strip_line_comment(line).trim();
1524    // 词法失败的行(如未闭合引号)不是版本声明,交由主解析器报更准确的错
1525    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
1539/// 剥离 `@version` 声明行,返回剩余文本与声明的版本(未声明则为 None)。
1540///
1541/// 允许多次声明(include 进来的文件可各自声明),但必须一致;
1542/// 声明了实现不支持的版本时报错,避免静默按错误语法解析。
1543pub(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
1569/// 把 版本 + 文档 @feature 指令 合并为最终生效的特性集。
1570///
1571/// 规则:
1572/// - 若文档显式声明过 `@feature`(had_feature=true),则完全采用其推导的 `feats`;
1573/// - 否则(仅靠 `@version` 声明或默认),从版本基线派生(如 v3 关闭裸词字符串)。
1574/// 这样 v3 文档即使不写任何 `@feature` 也默认严格;调用方的 `allowed` 在
1575/// 入口处再与结果取交集,文档无法扩宽。
1576fn 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
1584/// 解析 SML 文本,并返回其声明的语法版本。
1585///
1586/// 未声明版本时按 `V1` 处理(裸词即字符串),**既有文档不受影响**;
1587/// 显式 `@version v3` 则返回 `V3`(此时字符串需引号)。
1588pub 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    // 版本优先级:@version 显式声明 > @feature base > 默认 V1
1592    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
1597/// 解析 SML 文件:展开 include,并返回其声明的语法版本
1598pub 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
1616/// 解析 SML 文本
1617///
1618/// 会自动识别并剥离 `@version` / `@feature` 声明(需要版本信息时用
1619/// [`parse_versioned`],需要特性裁剪信息时用 [`parse_with_features`])。
1620///
1621/// **向后兼容**:未声明 `@version` 的文档按 `V1` 解析(裸词即字符串),
1622/// 既有大量 v1 文档不受影响;仅显式 `@version v2|v3` 才启用严格字符串。
1623pub 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
1631/// 解析 SML 文本,并限制文档声明的版本必须在 `allowed` 范围内。
1632///
1633/// 用于「库固定依赖某个 SML 语法版本」的场景:若文档声明了 `allowed`
1634/// 之外的版本(例如库只接受 v1..v3,却遇到 `@version v4`),立即报错,
1635/// 而不是用不兼容的语法静默解析。
1636///
1637/// 未声明版本的文档视为 `V1`,只要 `allowed` 含 `V1` 即放行。
1638pub 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
1660/// 解析 SML 文本,同时限制文档使用的**特性子集**必须在 `allowed` 内。
1661///
1662/// 与 [`parse_allowed`](版本范围)配套:`allowed` 是调用方(库作者)给出的
1663/// 白名单,文档内部的 `@feature enable/disable` 只能**收窄**这个集合,
1664/// 不能扩宽——否则文档就能自行绕过调用方的限制。交集为空则报错。
1665///
1666/// 未声明任何 `@feature` 的文档若仅靠版本基线(如 v3),则基线特性与
1667/// `allowed` 交集;只要交集非空即放行。
1668pub 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
1686/// 不含版本处理的底层解析(文本入口)
1687fn 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
1692/// 不含版本处理的底层解析(token 流入口,供 include 展开后零拷贝复用)
1693fn 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    // 顶层支持三种形态,与 `to_sml` 的输出对称:
1708    //   - `[ ... ]` 数组:to_sml 对非对象走 dump_inline,会输出顶层数组
1709    //     (如「历史记录」这类对象数组)。此前 parse 只认键值块,导致
1710    //     能序列化却读不回("期望键, 得 LBrack"),是不对称缺陷。
1711    //   - `{ ... }` 顶层对象块
1712    //   - 键值块(传统形态)
1713    // 注:顶层**标量**仍不可往返(SML 顶层需为容器),这是格式固有限制。
1714    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
1730// ---------------------------------------------------------------------------
1731// include 指令:把外部 .sml 文件内联进来
1732//
1733// 语法:`include "path.sml"` 或 `@include "path.sml"`(两种等价)
1734// 语义:**文本内联**(类似 C 的 #include),而非对象合并。
1735//   这样 include 可以出现在块内部引入一组字段,例如:
1736//       server web { &base include "common/port.sml" }
1737//   若做成对象合并就无法表达「注入若干字段到当前块」。
1738//
1739// 相对路径按**被包含文件自身所在目录**解析(与 C 预处理器一致),
1740// 而非进程工作目录,因此嵌套 include 时路径行为可预期。
1741// ---------------------------------------------------------------------------
1742
1743/// 值嵌套深度上限:防止 `a{a{a{ ... }}}` 这类深度嵌套触发递归下降的栈溢出。
1744///
1745/// 与 [`MAX_INCLUDE_DEPTH`] 互补 —— 后者只保护 include 的文件嵌套,不保护
1746/// 单个文档内部块/数组的嵌套。栈溢出在 Rust 中是 abort,
1747/// **无法被 catch_unwind 捕获**,因此必须在递归入口主动限深,
1748/// 而不是依赖上层错误处理。
1749///
1750/// 128 层与 serde_json 的 RECURSION_LIMIT 对齐,远超任何真实配置所需。
1751const MAX_VALUE_DEPTH: usize = 128;
1752
1753/// 嵌套深度上限:既防栈溢出,也让异常深层的引用尽早失败
1754const MAX_INCLUDE_DEPTH: usize = 32;
1755
1756/// 剥离行尾注释,正确跳过引号内的 `#`(如 `key: "a#b"` 中的 # 不是注释起点)
1757pub(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            // 引号内的反斜杠会转义下一个字符,需整体跳过
1765            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/// 单个 include 目标的解析结果。
1775#[derive(Debug, Clone, PartialEq, Eq)]
1776pub struct IncludeTarget {
1777    /// 相对路径或裸名(无扩展名时按 `implicit-ns` 推导 `as`)。
1778    pub raw: String,
1779    /// 命名空间(点分路径 `a.b.c`)。`None` 表示普通内联。
1780    /// 若 `raw` 无扩展名且开启 `implicit-ns`,则自动填充为文件名。
1781    pub namespace: Option<String>,
1782    /// 是否经 `import` 关键字(语义等同 `include`)。
1783    pub via_import: bool,
1784    /// 部分引用:仅从目标文件挑出这些顶层键并入(命名空间包裹时同样只挑这些)。
1785    /// `None` 表示整文件(不挑键)。
1786    pub keys: Option<Vec<String>>,
1787}
1788
1789/// 解析一行 include / import 指令,返回 0..N 个目标。
1790///
1791/// 支持形态(逗号分隔多目标,`import` 为 `include` 别名):
1792/// - `include "x.sml"`                普通内联(带扩展名、无 as)
1793/// - `include "foo"`                  无扩展名 ⇒ 默认 `as foo`(implicit-ns)
1794/// - `include "x.sml" as ui.form`     命名空间内联(点分路径)
1795/// - `include "a", "b" as y, "c"`     多目标(multi-include)
1796/// - `import ui.buttons, admin.panel`  import 别名
1797/// - `include "*.sml"`                glob 通配(需 `glob-include`)
1798/// - `include re:"widget_.*\.sml"`    正则匹配(需 `regex-include`)
1799///
1800/// 部分引用(挑键)—— 两种等价写法:
1801/// - `import "x.sml" as w { a, b }`          挑键 a,b,挂到命名空间 w
1802/// - `import { a, b } as w in "x.sml"`        等价写法(键列表在前)
1803/// 省略 `as w` 则挑出的键直接平铺到当前作用域:
1804/// - `import "x.sml" { a, b }`
1805/// - `import { a, b } in "x.sml"`
1806/// 注:部分引用只作用于单文件目标,不与 glob/regex 通配组合。
1807///
1808/// 返回 `Ok(None)` 表示该行不是 include 指令;`Err` 表示特性未开启等语义错误。
1809pub(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    // 轻量手写解析,不依赖 tokenize(避免 `*` 等字符在 tokenize 阶段被误判)。
1813    // 形式:`include "x" [as ns], "y" as ns2, ...`(import 等价)
1814    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        // 两种部分引用语法:
1828        //   ①  import "x.sml" [as w] { a, b }
1829        //   ②  import { a, b } [as w] in "x.sml"
1830        // 先探测是否以 `{` 开头(语法②)
1831        let (raw, ns, keys, tail) = if rest.trim_start().starts_with('{') {
1832            // 语法②:键列表在前
1833            let (keys, after) = parse_key_list(rest.trim_start())?;
1834            let after = after.trim_start();
1835            // 可选 `as ns`
1836            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            // 必须跟 `in "path"` 取目标文件
1846            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            // 语法①:路径在前
1862            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            // 可选 `as ns`
1874            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            // 可选 `{ keys }`
1884            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        // 逗号分隔多目标(用已修剪的 tail 判断是否还有下一个目标)
1901        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    // 特性预检查:glob / regex 模式在解析阶段就拦截(避免走到普通路径解析引发诡异错误)
1916    for t in &targets {
1917        // 部分引用只作用于单文件目标,不能与 glob/regex 通配组合
1918        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        // 先查 re: 前缀(正则模式里的 `*` 是元字符,不是 glob 通配)
1924        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
1937/// 从字符串开头提取下一个 token:引号串(支持 `\"` 与 `\\`)或直到空白/逗号/`as` 的裸词。
1938/// 返回 (token 文本, 剩余字符串)。
1939fn 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        // 引号串(按字节处理,路径通常为 ASCII)
1946        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                // 转义:保留转义后的字符(\. -> .,\" -> " 等)
1956                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        // 裸词:取到空白或逗号
1967        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
1975/// 解析 `{ a, b, c }` 形式的键列表,返回 (键名集合, 剩余字符串)。
1976/// 键名可为裸词或引号串。遇到非 `{` 开头时返回错误。
1977fn 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        // 支持引号串键,其余按裸词(去引号)
1991        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
2004/// 根据原始路径与可选命名空间,套用 implicit-ns 规则,产出最终目标。
2005fn 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            // 部分引用(指定了 keys)且无显式 `as`:强制平铺到当前作用域,
2016            // 不触发 implicit-ns 自动命名空间(否则挑出的键会被塞进文件名命名空间)。
2017            if keys.is_some() {
2018                None
2019            } else if via_import || (features.has(Feature::ImplicitNs) && !raw.contains('.')) {
2020                // `import a.b.c`:点分一律视为命名空间路径,自动 `as a.b.c`
2021                // `include "foo"`(无点):implicit-ns 默认以文件名为命名空间
2022                Some(raw.clone())
2023            } else {
2024                None
2025            }
2026        }
2027    };
2028    IncludeTarget {
2029        raw,
2030        namespace,
2031        via_import,
2032        keys,
2033    }
2034}
2035
2036/// 把一个 include 目标解析为 0..N 个实际文件路径(已相对 `base` 解析、未 canonicalize)。
2037///
2038/// 支持:
2039/// - glob:`raw` 含 `*` 且开启 `glob-include` → 遍历 `base` 下直接条目做 `*` 通配匹配
2040/// - 正则:`raw` 以 `re:"..."` 形式且开启 `regex-include` → 遍历 `base` 下条目做最小正则匹配
2041/// - ext-rewrite:开启 `ext-rewrite` 时允许 `raw` 带非 `.sml` 扩展名(否则按原补 `.sml` 逻辑)
2042/// - 普通:`import` 点分转目录层级、裸名补 `.sml`
2043fn resolve_target_paths(
2044    t: &IncludeTarget,
2045    base: &Path,
2046    features: FeatureSet,
2047) -> Result<Vec<PathBuf>, String> {
2048    // 正则模式:re:"<pattern>"
2049    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        // 模式可含目录前缀(如 re:"lib/widget_.*"):拆出目录并入 base(归一化分隔符)
2055        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    // glob 模式:含 `*`
2060    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    // 普通路径
2069    let path = if t.via_import {
2070        // import 的「点分模块名」语义:仅当 raw 既无路径分隔、又不显式带 .sml 扩展名时,
2071        // 才把点当作目录层级分隔(a.b.c -> a/b/c.sml)。
2072        // 若显式写了路径或扩展名(如 "advanced_inc/widget_a.sml"),按字面路径处理。
2073        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        // 带扩展名:默认直接读该文件
2085        // 开启 ext-rewrite 时允许非 .sml 扩展名(当 sml 解析);关闭时若非 .sml 也允许读,
2086        // 但语义上仍要求文件存在,由 canonicalize 报错兜底。
2087        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
2095/// 遍历 `base` 目录的直接条目,按 glob(`pattern` 含 `*`)或正则(`regex` 为 Some)匹配,
2096/// 把 `a/b/pattern` 拆成 (`a/b`, `pattern`),便于把目录部分并入 base。
2097fn 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
2104/// 返回命中的完整路径。目录本身不作为命中(仅文件)。
2105fn 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    // 用于正则匹配的模式字符串(不含 re: 前缀与引号)
2115    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; // 只匹配文件
2121        }
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            // glob:`pattern` 形如 `*.sml` 或 `widgets/*.sml`;这里只处理文件名部分的通配
2130            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    // 结果按文件名排序,保证跨平台顺序稳定
2138    hits.sort();
2139    Ok(hits)
2140}
2141
2142/// 手写最小 glob 匹配(仅支持 `*` 通配,匹配整个文件名)。
2143fn glob_matches(pattern: &str, text: &str) -> bool {
2144    // 将 `a*b*c` 拆分为字面段,段间用 `*` 连接
2145    let segs: Vec<&str> = pattern.split('*').collect();
2146    if segs.is_empty() {
2147        return text.is_empty();
2148    }
2149    let mut pos = 0usize;
2150    // 首段若非 `*` 开头,必须前缀匹配
2151    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    // 末段若非 `*` 结尾,必须后缀匹配
2167    if !pattern.ends_with('*') {
2168        if pos != text.len() {
2169            return false;
2170        }
2171    }
2172    true
2173}
2174
2175/// 编译一个受限正则(支持 `. * + ? ^ $ [a-z] [^a-z] \.` 转义),返回可匹配闭包用的结构。
2176/// 这里采用「NFA-less」的回溯匹配器,足够文件名场景使用。
2177struct MiniRegex {
2178    pattern: String,
2179}
2180
2181fn compile_regex(pat: &str) -> MiniRegex {
2182    // 去掉可能的首尾 `^`/`$` 锚(由 matcher 解释)
2183    MiniRegex {
2184        pattern: pat.to_string(),
2185    }
2186}
2187
2188/// 用受限正则匹配整个 `text`(默认全匹配,支持 `^`/`$` 锚点)。
2189fn 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    // 尝试从 text 的每个位置开始匹配(非锚定时)
2196    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                // 锚定结尾:必须匹配到 text 末端
2205                if backtrack_match(p, text, start) == Some(text.len()) {
2206                    return true;
2207                }
2208            }
2209        }
2210        false
2211    }
2212}
2213
2214/// 回溯匹配:从 `text[ti]` 开始尝试匹配 `pat[pi]`,返回成功时 text 的消耗终点(usize)。
2215fn backtrack_match(pat: &str, text: &str, ti: usize) -> Option<usize> {
2216    // 递归实现,模式索引 pi 通过 chars 迭代
2217    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                    // 转义下一个字符(如 \. 匹配字面的 .)
2226                    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                    // 匹配前一个原子零次或多次(贪婪)
2245                    // 回退:尝试匹配零次(跳过 * 与前一原子),或匹配一次后继续
2246                    let prev = if pi >= 1 { Some(pchars[pi - 1]) } else { None };
2247                    // 零次:跳过 '*'(以及其前的普通原子已由上层处理,这里仅跳过 '*')
2248                    // 但为简化,* 作用于前一原子:先尝试消耗一字符再递归
2249                    if ti < tchars.len() {
2250                        // 贪婪:尽量多匹配
2251                        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                        // 从 end 回退尝试让后续模式匹配
2266                        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                    // 零次匹配:跳过 '*'
2278                    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                    // 前一原子的零或一
2311                    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; // 零次
2324                                }
2325                            }
2326                            _ => {
2327                                pi += 1; // 零次
2328                            }
2329                        }
2330                    } else {
2331                        pi += 1;
2332                    }
2333                }
2334                '[' => {
2335                    // 字符类 [abc] 或 [^abc] 或 [a-z]
2336                    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; // 未闭合
2360                    }
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
2387/// 把 text 中的 include 指令递归展开为不含指令的纯 SML 文本。
2388///
2389/// `base` 为相对路径的解析基准目录(通常是当前文件所在目录)。
2390/// `features` 决定是否允许 `include` / `namespace`(禁用则遇到指令即报错)。
2391/// 循环引用与缺失文件都会返回错误,不会静默跳过。
2392/// 把 text 中的 include 指令递归展开为 token 流(方向 B:零拷贝,不拼巨大中间字符串)。
2393///
2394/// 每个被包含文件只 `tokenize` 一次;命名空间 `as a.b.c` 用零拷贝的开/闭块 token
2395/// (`Word(a) LBrace Word(b) LBrace Word(c) LBrace ... RBrace RBrace RBrace`)包裹,
2396/// 不复制文件内容文本。子文件内的 `@version`/`@feature` 指令行在 tokenize 前被剥离,
2397/// 由主文件统一控制特性集(符合「文档只能收窄」的设计)。
2398pub 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
2409/// 递归展开 include 到 `out` token 流。
2410fn 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                    // 把一个 target 解析为 0..N 个实际文件路径(支持 glob/regex/ext-rewrite)
2433                    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                        // stack 是「当前正在展开的文件链」,命中即成环
2439                        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                        // 展开子文件 tokens
2450                        let mut inner =
2451                            expand_file_tokens(&content, &child_base, stack, features)?;
2452                        // 部分引用:仅保留指定顶层键(命名空间包裹时同样只挑这些)
2453                        if let Some(keys) = &t.keys {
2454                            inner = filter_top_level_keys(inner, keys);
2455                        }
2456                        // 命名空间包含:用 `ns { ... }` 包裹子文件 tokens(零拷贝)
2457                        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                // 非 include 行:直接 tokenize 该行并追加(保持行级语义,零拷贝)
2475                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
2485/// 读取单个文件内容,剥离其自身的 `@version`/`@feature` 行后 tokenize。
2486/// 子文件不引入新特性维度,由主文件/调用方统一控制。
2487/// 仅保留 `toks` 中顶层键名属于 `keys` 的条目;其余顶层条目被丢弃。
2488/// 嵌套层级(块 `{}` / 数组 `[]`)内的键不受影响——只有 depth==0 的顶层键被过滤。
2489/// 用于 `import "x" { a, b }` 部分引用:避免整文件内联。
2490fn 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        // 顶层必须是键(Word/Str)起始;非键 token 原样保留以免破坏结构
2497        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        // 计算该顶层条目 [i, j) 的结束位置
2508        let j = if i + 1 < n {
2509            match &toks[i + 1] {
2510                // key: value —— 值从其后的 token 开始
2511                Tok::Colon => {
2512                    if i + 2 < n {
2513                        match &toks[i + 2] {
2514                            // 值为块/数组:配对括号
2515                            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                            // 单 token 值
2534                            _ => i + 3,
2535                        }
2536                    } else {
2537                        i + 2
2538                    }
2539                }
2540                // key { ... } / key [ ... ] —— 直接配对括号
2541                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                // 裸词独立行等:单 token 条目
2560                _ => 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    // 剥离子文件内的版本/特性指令行,避免污染 token 流
2582    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
2596/// 解析 SML 文件,并展开其中的 include 指令。
2597///
2598/// 相对路径以**该文件所在目录**为基准。include 展开为零拷贝 token 流,
2599/// 不拼接中间大字符串(方向 B)。
2600pub 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    // 主文件先剥离版本/特性指令。
2609    // 便捷入口 `parse_file` 的「调用方允许集」为全开(文档自身声明决定启用哪些特性,
2610    // 真正的调用方限制由 `parse_with_features` / `parse_allowed` 负责)。
2611    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
2620/// 解析到对象 (失败抛 `ParseError`)
2621pub 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
2636// ---------------------------------------------------------------------------
2637// 序列化
2638// ---------------------------------------------------------------------------
2639
2640/// 判断字符串作为裸词写出是否安全(可无损 round-trip 回来仍是 `Str`)。
2641///
2642/// 允许裸写的「安全标识符」需满足:
2643/// - 非空;
2644/// - 不等于保留字面量 `true` / `false` / `null` / `inf` / `nan`;
2645/// - 不是纯数字或浮点字面量(否则 `coerce_word` 会把它字面量化成 Int/Float);
2646/// - 不以注释前缀开头:`--` `//` `/*` `*/` `*` `_*`;
2647/// - 不含任何会破坏语法的字符:空白、`:` `#` `{` `}` `,` `[` `]` `"` `\` `/` `*`;
2648/// - 首字符必须是字母或 `_`(避免 `-x` / `.x` / 数字开头等歧义);
2649/// - 其余字符只能是 `[A-Za-z0-9_-]`(kebab-case 的 `-` 在中间是安全的,
2650///   只有行首的 `--` 才是注释)。
2651///
2652/// 任何不满足的情况都加引号,确保序列化结果解析回来仍是原字符串。
2653fn 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    // 纯数字 / 浮点会被字面量化成 Int/Float
2662    if s.parse::<i64>().is_ok() || s.parse::<f64>().is_ok() {
2663        return true;
2664    }
2665    // 注释前缀(仅出现在开头才危险)
2666    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    // 会破坏语法的字符
2676    if s.contains([' ', '\t', '\n', '\r', ':', '#', '{', '}', ',', '[', ']', '"', '\\', '/', '*'])
2677    {
2678        return true;
2679    }
2680    // 首字符必须是字母或下划线
2681    match s.chars().next() {
2682        Some(c) if c.is_alphabetic() || c == '_' => {}
2683        _ => return true,
2684    }
2685    // 其余字符只允许 [A-Za-z0-9_.-](`.` 在中间安全,如 `web.example`;
2686    // 仅 `.` 开头才有歧义,已由首字符规则拦截)
2687    !s.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-' || c == '.')
2688}
2689
2690// TODO: keep value ordering stable for deterministic dumps. іӏоѵеԛіанхун
2691fn 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
2699/// 输出一个块。含 `__type` / `__name` 的块也按普通块原样输出所有键,
2700/// 保证元数据(枚举带数据变体的 `__type` 标记等)可完整往返。
2701/// SML 的裸块 `type [name] { ... }` 解析后正是 `__type` / `__name` 键。
2702fn 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            // 强制保留小数点,避免 1.0 被 Display 写成 "1" 后 round-trip 成 Int
2727            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(&quote_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            // 含 __type/__name 的块原样输出所有键,保证元数据可往返
2765            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
2779/// 序列化回 SML 文本 (round-trip)
2780///
2781/// 含 `__type` / `__name` 的块(如枚举带数据变体序列化的结果)
2782/// 会原样输出所有键,保证元数据可完整往返。
2783pub 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}