use std::collections::BTreeMap;
use std::fmt;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Null,
Bool(bool),
Int(i64),
Float(f64),
Str(String),
Array(Vec<Value>),
Object(BTreeMap<String, Value>),
}
impl Value {
pub fn get(&self, path: &str) -> Option<&Value> {
let mut cur = self;
for seg in path.split('.') {
match cur {
Value::Object(m) => cur = m.get(seg)?,
_ => return None,
}
}
Some(cur)
}
pub fn as_str(&self) -> Option<&str> {
match self {
Value::Str(s) => Some(s),
_ => None,
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", to_sml(self))
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum TypeSpec {
Any,
ContractRef(String),
Str,
Int,
Num,
Bool,
Array(Box<TypeSpec>),
Enum(Vec<String>),
}
impl TypeSpec {
fn name(&self) -> String {
match self {
TypeSpec::Any => "any".into(),
TypeSpec::Str => "str".into(),
TypeSpec::Int => "int".into(),
TypeSpec::Num => "num".into(),
TypeSpec::Bool => "bool".into(),
TypeSpec::Array(inner) => format!("[{}]", inner.name()),
TypeSpec::Enum(vals) => format!("enum [{}]", vals.join(" ")),
TypeSpec::ContractRef(name) => name.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct FieldSpec {
pub ty: TypeSpec,
pub required: bool,
pub default: Option<Value>,
pub min: Option<f64>,
pub max: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct Contract {
pub name: String,
pub fields: BTreeMap<String, FieldSpec>,
pub allow_extra: bool,
}
fn check_type(
contract: &str,
field: &str,
spec: &FieldSpec,
v: &Value,
contracts: &BTreeMap<String, Contract>,
) -> Result<(), String> {
if let TypeSpec::ContractRef(ref_name) = &spec.ty {
return match v {
Value::Object(_) => {
let mut sub = match v {
Value::Object(m) => m.clone(),
_ => unreachable!(),
};
let target = contracts.get(ref_name).ok_or_else(|| {
format!(
"sml: 字段 `{}` 引用了未定义的契约 `{}`(契约 `{}`)",
field, ref_name, contract
)
})?;
apply_contract(target, &mut sub, contracts)?;
Ok(())
}
_ => Err(format!(
"sml: 字段 `{}` 应为块并按契约 `{}` 校验,实际为 {}(契约 `{}`)",
field,
ref_name,
value_kind(v),
contract
)),
};
}
let ok = match (&spec.ty, v) {
(TypeSpec::Any, _) => true,
(TypeSpec::Str, Value::Str(_)) => true,
(TypeSpec::Int, Value::Int(_)) => true,
(TypeSpec::Num, Value::Int(_)) | (TypeSpec::Num, Value::Float(_)) => true,
(TypeSpec::Bool, Value::Bool(_)) => true,
(TypeSpec::Enum(vals), Value::Str(s)) => vals.iter().any(|x| x == s),
(TypeSpec::Enum(vals), Value::Int(i)) => vals.iter().any(|x| x == &i.to_string()),
(TypeSpec::Array(inner), Value::Array(items)) => items.iter().all(|it| {
check_type(
contract,
field,
&FieldSpec { ty: (**inner).clone(), required: true, default: None, min: None, max: None },
it,
contracts,
)
.is_ok()
}),
_ => false,
};
if !ok {
return Err(format!(
"sml: 字段 `{}` 类型应为 {},实际为 {}(契约 `{}`)",
field,
spec.ty.name(),
value_kind(v),
contract
));
}
if spec.min.is_some() || spec.max.is_some() {
let n = match v {
Value::Int(i) => Some(*i as f64),
Value::Float(f) => Some(*f),
_ => None,
};
if let Some(n) = n {
if let Some(lo) = spec.min {
if n < lo {
return Err(format!(
"sml: 字段 `{}` 值 {} 小于下界 {}(契约 `{}`)",
field, n, lo, contract
));
}
}
if let Some(hi) = spec.max {
if n > hi {
return Err(format!(
"sml: 字段 `{}` 值 {} 大于上界 {}(契约 `{}`)",
field, n, hi, contract
));
}
}
}
}
Ok(())
}
fn value_kind(v: &Value) -> &'static str {
match v {
Value::Null => "null",
Value::Bool(_) => "bool",
Value::Int(_) => "int",
Value::Float(_) => "float",
Value::Str(_) => "str",
Value::Array(_) => "array",
Value::Object(_) => "object",
}
}
fn apply_contract(
c: &Contract,
node: &mut BTreeMap<String, Value>,
contracts: &BTreeMap<String, Contract>,
) -> Result<(), String> {
if !c.allow_extra {
for k in node.keys() {
if !c.fields.contains_key(k) {
return Err(format!(
"sml: 字段 `{}` 未在契约 `{}` 中声明(严格模式;如需允许额外字段请在契约名后写 `loose`)",
k, c.name
));
}
}
}
for (k, spec) in &c.fields {
match node.get(k) {
None => {
if let Some(d) = &spec.default {
node.insert(k.clone(), d.clone());
} else if spec.required {
return Err(format!(
"sml: 字段 `{}` 必填但缺失(契约 `{}`)",
k, c.name
));
}
}
Some(v) => {
if matches!(spec.ty, TypeSpec::ContractRef(_)) {
check_type(&c.name, k, spec, v, contracts)?;
let mut sub = match v {
Value::Object(m) => m.clone(),
_ => unreachable!("check_type 已保证为块"),
};
check_type_contract_ref(&c.name, k, spec, &mut sub, contracts)?;
node.insert(k.clone(), Value::Object(sub));
} else {
check_type(&c.name, k, spec, v, contracts)?;
}
}
}
}
Ok(())
}
fn check_type_contract_ref(
contract: &str,
field: &str,
spec: &FieldSpec,
sub: &mut BTreeMap<String, Value>,
contracts: &BTreeMap<String, Contract>,
) -> Result<(), String> {
let ref_name = match &spec.ty {
TypeSpec::ContractRef(n) => n.clone(),
_ => return Ok(()),
};
let target = contracts.get(&ref_name).ok_or_else(|| {
format!(
"sml: 字段 `{}` 引用了未定义的契约 `{}`(契约 `{}`)",
field, ref_name, contract
)
})?;
check_type(contract, field, spec, &Value::Object(sub.clone()), contracts)?;
apply_contract(target, sub, contracts)
}
#[derive(Debug, Clone, PartialEq)]
enum Tok {
LBrace, RBrace, LBrack, RBrack, Comma, Colon, At, Str(String), Word(String), }
fn tokenize(text: &str) -> Result<Vec<Tok>, String> {
let mut toks = Vec::new();
let mut chars = text.chars().peekable();
let mut buf = String::new();
let mut flush = |buf: &mut String, toks: &mut Vec<Tok>| {
if !buf.is_empty() {
toks.push(Tok::Word(std::mem::take(buf)));
}
};
while let Some(c) = chars.next() {
match c {
'#' => {
for c2 in chars.by_ref() {
if c2 == '\n' {
break;
}
}
}
'-' => {
if chars.peek() == Some(&'-') {
chars.next(); for c2 in chars.by_ref() {
if c2 == '\n' {
break;
}
}
} else {
buf.push(c);
}
}
'/' => {
match chars.peek() {
Some('/') => {
chars.next(); for c2 in chars.by_ref() {
if c2 == '\n' {
break;
}
}
}
Some('*') => {
chars.next(); loop {
match chars.next() {
Some('*') => {
if chars.peek() == Some(&'/') {
chars.next();
break;
}
}
Some(_) => {}
None => break,
}
}
}
_ => buf.push(c),
}
}
'_' => {
if chars.peek() == Some(&'*') {
chars.next(); loop {
match chars.next() {
Some('*') => {
if chars.peek() == Some(&'_') {
chars.next();
break;
}
}
Some(_) => {}
None => break,
}
}
} else {
buf.push(c);
}
}
'"' => {
flush(&mut buf, &mut toks);
let mut s = String::new();
loop {
match chars.next() {
Some('"') => break,
Some('\\') => {
match chars.next() {
Some('n') => s.push('\n'),
Some('t') => s.push('\t'),
Some('r') => s.push('\r'),
Some('0') => s.push('\0'),
Some('"') => s.push('"'),
Some('\\') => s.push('\\'),
Some('u') => {
let mut hex = String::new();
if chars.peek() == Some(&'{') {
chars.next();
for c2 in chars.by_ref() {
if c2 == '}' {
break;
}
hex.push(c2);
}
} else {
for _ in 0..4 {
if let Some(c2) = chars.next() {
hex.push(c2);
}
}
}
if let Ok(cp) = u32::from_str_radix(&hex, 16) {
if let Some(ch) = char::from_u32(cp) {
s.push(ch);
}
}
}
Some(other) => s.push(other),
None => break,
}
}
Some(other) => s.push(other),
None => break,
}
}
toks.push(Tok::Str(s));
}
'{' => {
flush(&mut buf, &mut toks);
toks.push(Tok::LBrace);
}
'}' => {
flush(&mut buf, &mut toks);
toks.push(Tok::RBrace);
}
'[' => {
flush(&mut buf, &mut toks);
toks.push(Tok::LBrack);
}
']' => {
flush(&mut buf, &mut toks);
toks.push(Tok::RBrack);
}
',' => {
flush(&mut buf, &mut toks);
toks.push(Tok::Comma);
}
':' => {
flush(&mut buf, &mut toks);
toks.push(Tok::Colon);
}
'@' => {
if buf.is_empty() {
toks.push(Tok::At);
} else {
buf.push(c);
}
}
' ' | '\t' | '\n' | '\r' => {
flush(&mut buf, &mut toks);
}
_ => {
buf.push(c);
}
}
}
flush(&mut buf, &mut toks);
Ok(toks)
}
fn coerce_word(
w: &str,
fragments: &BTreeMap<String, Value>,
features: FeatureSet,
ns_prefix: &str,
) -> Result<Value, String> {
match w {
"true" => return Ok(Value::Bool(true)),
"false" => return Ok(Value::Bool(false)),
"null" => return Ok(Value::Null),
_ => {}
}
if let Some(ev) = w.strip_prefix("$env.") {
if !features.has(Feature::Env) {
return Err(format!("sml: 当前特性集禁用了 `$env`(env),裸词 `{}` 无法解析", w));
}
return Ok(Value::Str(std::env::var(ev).unwrap_or_default()));
}
if let Some(name) = w.strip_prefix('&') {
if !features.has(Feature::Fragment) {
return Err(format!("sml: 当前特性集禁用了片段引用(fragment),`{}` 无法解析", w));
}
if let Some(v) = fragments.get(name) {
return Ok(v.clone());
}
if !ns_prefix.is_empty() {
let mut probe = ns_prefix.to_string();
loop {
let full = format!("{probe}.{name}");
if let Some(v) = fragments.get(&full) {
return Ok(v.clone());
}
match probe.rfind('.') {
Some(idx) => probe.truncate(idx),
None => break,
}
}
}
return Ok(Value::Str(w.to_string()));
}
if let Ok(i) = w.parse::<i64>() {
return Ok(Value::Int(i));
}
if let Ok(f) = w.parse::<f64>() {
return Ok(Value::Float(f));
}
if !features.has(Feature::BarewordStr) {
return Err(format!(
"sml: 字符串必须加引号,裸词 `{}` 应写作 `\"{}\"`(特性 bareword-string 已禁用)",
w, w
));
}
Ok(Value::Str(w.to_string()))
}
struct Parser {
toks: Vec<Tok>,
i: usize,
fragments: BTreeMap<String, Value>,
contracts: BTreeMap<String, Contract>,
features: FeatureSet,
ns_stack: Vec<String>,
}
impl Parser {
fn ns_prefix(&self) -> String {
if self.ns_stack.is_empty() {
String::new()
} else {
self.ns_stack.join(".")
}
}
fn qualify(&self, name: &str) -> String {
let p = self.ns_prefix();
if p.is_empty() {
name.to_string()
} else {
format!("{p}.{name}")
}
}
fn peek(&self) -> Option<&Tok> {
self.toks.get(self.i)
}
fn next(&mut self) -> Option<Tok> {
let t = self.toks.get(self.i).cloned();
if t.is_some() {
self.i += 1;
}
t
}
fn parse_contract_body(&mut self) -> Result<BTreeMap<String, FieldSpec>, String> {
let mut fields: BTreeMap<String, FieldSpec> = BTreeMap::new();
loop {
match self.peek().cloned() {
None | Some(Tok::RBrace) => {
self.next();
break;
}
Some(Tok::Comma) => {
self.next();
}
_ => {
let key = match self.next() {
Some(Tok::Word(s)) | Some(Tok::Str(s)) => s,
other => {
return Err(format!("sml: 契约字段期望键, 得 {:?}", other))
}
};
if self.peek() == Some(&Tok::Colon) {
self.next();
} else {
return Err(format!("sml: 契约字段 `{}` 后须有冒号", key));
}
let spec = self.parse_field_spec()?;
fields.insert(key, spec);
}
}
}
Ok(fields)
}
fn parse_field_spec(&mut self) -> Result<FieldSpec, String> {
let ty = match self.next() {
Some(Tok::Word(w)) => match w.as_str() {
"str" => TypeSpec::Str,
"int" => TypeSpec::Int,
"num" => TypeSpec::Num,
"bool" => TypeSpec::Bool,
"any" => TypeSpec::Any,
"enum" => {
if self.peek() != Some(&Tok::LBrack) {
return Err("sml: `enum` 后须为 [ ... ]".into());
}
self.next();
let mut vals = Vec::new();
loop {
match self.peek().cloned() {
None | Some(Tok::RBrack) => {
self.next();
break;
}
Some(Tok::Comma) => {
self.next();
}
Some(Tok::Word(s)) | Some(Tok::Str(s)) => {
vals.push(s);
self.next();
}
_ => {
self.next();
}
}
}
TypeSpec::Enum(vals)
}
other => TypeSpec::ContractRef(other.to_string()),
},
Some(Tok::LBrack) => {
let inner = match self.next() {
Some(Tok::Word(w)) => match w.as_str() {
"str" => TypeSpec::Str,
"int" => TypeSpec::Int,
"num" => TypeSpec::Num,
"bool" => TypeSpec::Bool,
"any" => TypeSpec::Any,
other => {
return Err(format!("sml: 未知数组元素类型 `{}`", other))
}
},
other => {
return Err(format!("sml: 数组元素类型期望标识符, 得 {:?}", other))
}
};
if self.peek() == Some(&Tok::RBrack) {
self.next();
}
TypeSpec::Array(Box::new(inner))
}
other => return Err(format!("sml: 字段类型期望标识符, 得 {:?}", other)),
};
let mut required = true;
let mut default = None;
let mut min = None;
let mut max = None;
loop {
let is_next_field = matches!(self.peek(), Some(Tok::Word(_)))
&& matches!(self.toks.get(self.i + 1), Some(Tok::Colon));
if is_next_field {
break;
}
match self.peek().cloned() {
Some(Tok::Word(w)) => match w.as_str() {
"optional" => {
required = false;
self.next();
}
"required" => {
required = true;
self.next();
}
"default" => {
self.next();
default = Some(match self.next() {
Some(Tok::Word(w2)) => coerce_word(&w2, &self.fragments, self.features, &self.ns_prefix())?,
Some(Tok::Str(s)) => Value::Str(s),
other => {
return Err(format!("sml: default 期望值, 得 {:?}", other))
}
});
}
"min" => {
self.next();
min = Some(self.parse_spec_number()?);
}
"max" => {
self.next();
max = Some(self.parse_spec_number()?);
}
_ => break,
},
_ => break,
}
}
Ok(FieldSpec { ty, required, default, min, max })
}
fn parse_spec_number(&mut self) -> Result<f64, String> {
match self.next() {
Some(Tok::Word(w)) => {
w.parse::<f64>().map_err(|_| format!("sml: 期望数字, 得 `{}`", w))
}
other => Err(format!("sml: 期望数字, 得 {:?}", other)),
}
}
fn parse_block(&mut self, closing: Option<Tok>) -> Result<Value, String> {
let mut node: BTreeMap<String, Value> = BTreeMap::new();
let mut applied_contract: Option<String> = None;
loop {
let tok = match self.peek().cloned() {
None => break,
Some(t) => t,
};
match tok {
Tok::RBrace | Tok::RBrack => {
if let Some(cl) = &closing {
if *cl == tok {
self.next();
break;
}
}
break;
}
Tok::Comma => {
self.next();
}
Tok::At => {
self.next();
let fname = match self.next() {
Some(Tok::Word(s)) | Some(Tok::Str(s)) => s,
_ => return Err("sml: @ 后需片段名".into()),
};
if self.peek() == Some(&Tok::Colon) {
self.next();
}
if fname == "contract" {
if !self.features.has(Feature::Contract) {
return Err("@contract 需要特性 `contract`,但当前特性集已禁用".into());
}
let cname = match self.next() {
Some(Tok::Word(s)) | Some(Tok::Str(s)) => s,
other => {
return Err(format!("sml: @contract 后须契约名, 得 {:?}", other))
}
};
let mut allow_extra = false;
if let Some(Tok::Word(w)) = self.peek().cloned() {
if w == "loose" {
allow_extra = true;
self.next();
}
}
if self.peek() != Some(&Tok::LBrace) {
return Err(format!("sml: @contract {} 后须 {{ ... }}", cname));
}
self.next();
let fields = self.parse_contract_body()?;
self.contracts.insert(
self.qualify(&cname),
Contract {
name: self.qualify(&cname),
fields,
allow_extra,
},
);
continue;
}
if fname == "is" {
if !self.features.has(Feature::Contract) {
return Err("@is 需要特性 `contract`,但当前特性集已禁用".into());
}
let cname = match self.next() {
Some(Tok::Word(s)) | Some(Tok::Str(s)) => s,
other => {
return Err(format!("sml: @is 后须契约名, 得 {:?}", other))
}
};
let resolved = if self.contracts.contains_key(&cname) {
cname.clone()
} else {
self.qualify(&cname)
};
applied_contract = Some(resolved);
continue;
}
let mut ftype: Option<String> = None;
let mut farg: Option<String> = None;
if let Some(Tok::Word(s)) = self.peek().cloned() {
if *self.peek().unwrap() != Tok::LBrace {
self.next();
ftype = Some(s);
if let Some(Tok::Word(s2)) = self.peek().cloned() {
if *self.peek().unwrap() != Tok::LBrace {
self.next();
farg = Some(s2);
}
}
}
}
if self.peek() == Some(&Tok::LBrace) {
self.next();
let mut sub = match self.parse_block(Some(Tok::RBrace))? {
Value::Object(m) => m,
other => {
let mut m = BTreeMap::new();
m.insert("_value".into(), other);
m
}
};
if let Some(t) = ftype {
sub.insert("__type".into(), Value::Str(t));
}
if let Some(a) = farg {
sub.insert("__name".into(), Value::Str(a));
}
if !self.features.has(Feature::Fragment) {
return Err(format!(
"sml: 片段定义 `@{}` 需要特性 `fragment`,但当前特性集已禁用",
fname
));
}
self.fragments.insert(self.qualify(&fname), Value::Object(sub));
}
}
_ => {
let key = match self.next() {
Some(Tok::Word(s)) | Some(Tok::Str(s)) => s,
other => return Err(format!("sml: 期望键, 得 {:?}", other)),
};
let colon = self.peek() == Some(&Tok::Colon);
if colon {
self.next();
}
let val = self.parse_value(&key, colon)?;
if let Some(existing) = node.get_mut(&key) {
match existing {
Value::Array(a) => a.push(val),
_ => {
let old = node.remove(&key).unwrap();
node.insert(key, Value::Array(vec![old, val]));
}
}
} else {
node.insert(key, val);
}
}
}
}
if let Some(cname) = applied_contract {
let c = self
.contracts
.get(&cname)
.cloned()
.ok_or_else(|| format!("sml: 未定义的契约 `{}`", cname))?;
apply_contract(&c, &mut node, &self.contracts)?;
}
Ok(Value::Object(node))
}
fn parse_value(&mut self, key: &str, colon: bool) -> Result<Value, String> {
if !colon && matches!(self.peek(), Some(Tok::Word(_))) {
let mut probe = self.i;
let mut found_block = false;
while probe < self.toks.len() {
match &self.toks[probe] {
Tok::Word(_) | Tok::Str(_) => probe += 1,
Tok::LBrace => {
found_block = true;
break;
}
_ => break,
}
}
if found_block {
let mut args: Vec<Value> = Vec::new();
while let Some(t) = self.peek().cloned() {
match t {
Tok::Word(w) => {
args.push(coerce_word(&w, &self.fragments, self.features, &self.ns_prefix())?);
self.next();
}
Tok::Str(_) => {
if let Some(Tok::Str(s)) = self.next() {
args.push(Value::Str(s));
}
}
_ => break,
}
}
if self.peek() == Some(&Tok::LBrace) {
self.next();
self.ns_stack.push(key.to_string());
let mut sub = self.parse_block(Some(Tok::RBrace))?;
self.ns_stack.pop();
if let Value::Object(m) = &mut sub {
m.insert("__type".into(), Value::Str(key.to_string()));
if args.len() == 1 {
m.insert("__name".into(), args.remove(0));
}
}
return Ok(sub);
}
}
}
match self.peek().cloned() {
Some(Tok::LBrace) => {
self.next();
self.parse_block(Some(Tok::RBrace))
}
Some(Tok::LBrack) => {
self.next();
self.parse_array()
}
Some(tok @ (Tok::Word(_) | Tok::Str(_))) => {
let v = match tok {
Tok::Word(w) => coerce_word(&w, &self.fragments, self.features, &self.ns_prefix())?,
Tok::Str(s) => {
let ev = s.strip_prefix("$env.");
match ev {
Some(name) => Value::Str(std::env::var(name).unwrap_or_default()),
None => Value::Str(s),
}
}
_ => unreachable!(),
};
self.next();
Ok(v)
}
Some(Tok::RBrace) | Some(Tok::RBrack) | Some(Tok::Comma) | None => {
if colon {
Ok(Value::Null)
} else {
Ok(coerce_word(key, &self.fragments, self.features, &self.ns_prefix())?)
}
}
_ => Err("sml: 语法错误".into()),
}
}
fn parse_array(&mut self) -> Result<Value, String> {
let mut arr = Vec::new();
loop {
match self.peek().cloned() {
None => break,
Some(Tok::RBrack) => {
self.next();
break;
}
Some(Tok::Comma) => {
self.next();
}
Some(Tok::LBrace) => {
self.next();
arr.push(self.parse_block(Some(Tok::RBrace))?);
}
Some(Tok::Word(w)) => {
arr.push(coerce_word(&w, &self.fragments, self.features, &self.ns_prefix())?);
self.next();
}
Some(Tok::Str(_)) => {
if let Some(Tok::Str(s)) = self.next() {
arr.push(Value::Str(s));
}
}
_ => break,
}
}
Ok(Value::Array(arr))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Version {
V1,
V2,
V3,
}
impl Version {
pub const CURRENT: Version = Version::V3;
pub fn strict_strings(self) -> bool {
self >= Version::V2
}
fn from_word(w: &str) -> Option<Version> {
match w {
"v1" | "1" => Some(Version::V1),
"v2" | "2" => Some(Version::V2),
"v3" | "3" => Some(Version::V3),
_ => None,
}
}
pub fn name(self) -> &'static str {
match self {
Version::V1 => "v1",
Version::V2 => "v2",
Version::V3 => "v3",
}
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Feature {
BarewordStr,
Include,
Env,
Contract,
Fragment,
TopArray,
Namespace,
ImplicitNs,
MultiInclude,
GlobInclude,
RegexInclude,
ExtRewrite,
}
pub static FEATURES: &[(&str, Feature)] = &[
("bareword-string", Feature::BarewordStr),
("include", Feature::Include),
("env", Feature::Env),
("contract", Feature::Contract),
("fragment", Feature::Fragment),
("top-level-array", Feature::TopArray),
("namespace", Feature::Namespace),
("implicit-ns", Feature::ImplicitNs),
("multi-include", Feature::MultiInclude),
("glob-include", Feature::GlobInclude),
("regex-include", Feature::RegexInclude),
("ext-rewrite", Feature::ExtRewrite),
];
impl Feature {
pub fn from_name(name: &str) -> Option<Feature> {
FEATURES.iter().find(|(n, _)| *n == name).map(|(_, f)| *f)
}
pub fn name(self) -> &'static str {
FEATURES
.iter()
.find(|(_, f)| *f == self)
.map(|(n, _)| *n)
.unwrap_or("<unknown>")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FeatureSet(u64);
impl FeatureSet {
pub fn all() -> FeatureSet {
let mut m = 0u64;
for (_, f) in FEATURES {
m |= 1 << (*f as u8);
}
FeatureSet(m)
}
pub fn baseline() -> FeatureSet {
FeatureSet::none()
.with(Feature::BarewordStr)
.with(Feature::Include)
.with(Feature::Env)
.with(Feature::Contract)
.with(Feature::Fragment)
.with(Feature::TopArray)
.with(Feature::Namespace)
.with(Feature::ImplicitNs)
}
pub fn none() -> FeatureSet {
FeatureSet(0)
}
pub fn for_version(v: Version) -> FeatureSet {
let mut s = FeatureSet::baseline();
if v.strict_strings() {
s = s.without(Feature::BarewordStr);
} else {
s = s.with(Feature::BarewordStr);
}
s
}
pub fn has(self, f: Feature) -> bool {
(self.0 & (1 << (f as u8))) != 0
}
pub fn with(self, f: Feature) -> FeatureSet {
FeatureSet(self.0 | (1 << (f as u8)))
}
pub fn without(self, f: Feature) -> FeatureSet {
FeatureSet(self.0 & !(1 << (f as u8)))
}
pub fn intersection(self, other: FeatureSet) -> FeatureSet {
FeatureSet(self.0 & other.0)
}
pub fn is_empty(self) -> bool {
self.0 == 0
}
}
impl fmt::Display for FeatureSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut first = true;
for (n, feat) in FEATURES {
if self.has(*feat) {
if !first {
f.write_str(",")?;
}
f.write_str(n)?;
first = false;
}
}
if first {
f.write_str("<none>")?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum FeatureMode {
Default,
Whitelist,
Blacklist,
}
fn tok_word(t: &Tok) -> String {
match t {
Tok::Word(s) | Tok::Str(s) => s.clone(),
_ => String::new(),
}
}
fn apply_feature_directive(
line: &str,
feats: &mut FeatureSet,
mode: &mut FeatureMode,
base: &mut Option<Version>,
) -> Result<bool, String> {
let content = strip_line_comment(line).trim();
let toks = match tokenize(content) {
Ok(t) => t,
Err(_) => return Ok(false),
};
if toks.is_empty() || toks[0] != Tok::At {
return Ok(false);
}
let words: Vec<String> = toks
.iter()
.map(|t| match t {
Tok::At => "@".to_string(),
other => tok_word(other),
})
.collect();
let head = format!("{}{}", words.first().map(|s| s.as_str()).unwrap_or(""), words.get(1).map(|s| s.as_str()).unwrap_or(""));
if head != "@feature" {
return Ok(false);
}
let words: Vec<String> = words[1..].to_vec();
if words.len() < 2 {
return Err("@feature 指令缺少参数".into());
}
let arg = words[1].as_str();
let names = |from: usize| -> Vec<String> {
words[from..]
.join(",")
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect()
};
match arg {
"base" => {
let v = Version::from_word(words.get(2).map(|s| s.as_str()).unwrap_or(""))
.ok_or_else(|| {
format!(
"@feature base 需要 v1/v2/v3,收到 `{}`",
words.get(2).cloned().unwrap_or_default()
)
})?;
*feats = FeatureSet::for_version(v);
*base = Some(v);
Ok(true)
}
"mode" => {
let m = words.get(2).map(|s| s.as_str()).unwrap_or("");
*mode = match m {
"whitelist" => FeatureMode::Whitelist,
"blacklist" => FeatureMode::Blacklist,
_ => return Err(format!("@feature mode 需要 whitelist/blacklist,收到 `{m}`")),
};
if *mode == FeatureMode::Whitelist {
*feats = FeatureSet::none();
}
Ok(true)
}
"enable" => {
for n in names(2) {
let f = Feature::from_name(&n).ok_or_else(|| {
format!(
"未知特性 `{n}`,可用:{}",
FEATURES.iter().map(|(n, _)| *n).collect::<Vec<_>>().join(", ")
)
})?;
*feats = feats.with(f);
}
Ok(true)
}
"disable" => {
for n in names(2) {
let f = Feature::from_name(&n).ok_or_else(|| {
format!(
"未知特性 `{n}`,可用:{}",
FEATURES.iter().map(|(n, _)| *n).collect::<Vec<_>>().join(", ")
)
})?;
*feats = feats.without(f);
}
Ok(true)
}
"whitelist" => {
*mode = FeatureMode::Whitelist;
let mut s = FeatureSet::none();
for n in names(2) {
let f = Feature::from_name(&n).ok_or_else(|| {
format!(
"未知特性 `{n}`,可用:{}",
FEATURES.iter().map(|(n, _)| *n).collect::<Vec<_>>().join(", ")
)
})?;
s = s.with(f);
}
*feats = s;
Ok(true)
}
"blacklist" => {
let mut s = FeatureSet::all();
for n in names(2) {
let f = Feature::from_name(&n).ok_or_else(|| {
format!(
"未知特性 `{n}`,可用:{}",
FEATURES.iter().map(|(n, _)| *n).collect::<Vec<_>>().join(", ")
)
})?;
s = s.without(f);
}
*feats = s;
Ok(true)
}
_ => Err(format!("未知 @feature 子命令 `{arg}`,可用 base/mode/enable/disable")),
}
}
fn strip_features(text: &str) -> Result<(String, FeatureSet, Option<Version>, bool), String> {
let mut out = String::new();
let mut feats = FeatureSet::all();
let mut mode = FeatureMode::Default;
let mut base: Option<Version> = None;
let mut had_feature = false;
for line in text.lines() {
match apply_feature_directive(line, &mut feats, &mut mode, &mut base) {
Ok(true) => {
had_feature = true;
continue; }
Ok(false) => {}
Err(e) => return Err(e), }
out.push_str(line);
out.push('\n');
}
Ok((out, feats, base, had_feature))
}
fn version_directive(line: &str) -> Result<Option<String>, String> {
let content = strip_line_comment(line).trim();
let toks = match tokenize(content) {
Ok(t) => t,
Err(_) => return Ok(None),
};
match toks.as_slice() {
[Tok::At, Tok::Word(w), Tok::Word(v)] if w == "version" => Ok(Some(v.clone())),
[Tok::At, Tok::Word(w), Tok::Str(v)] if w == "version" => Ok(Some(v.clone())),
[Tok::At, Tok::Word(w), ..] if w == "version" => Err(
"`@version` 是版本声明指令,须写作 `@version v1`;`version` 不可作为片段名".into(),
),
_ => Ok(None),
}
}
fn strip_version(text: &str) -> Result<(String, Option<Version>), String> {
let mut declared: Option<Version> = None;
let mut rest = String::new();
for line in text.lines() {
if let Some(lit) = version_directive(line)? {
let v = Version::from_word(&lit).ok_or_else(|| {
format!(
"不支持的 SML 版本 `{lit}`(本实现支持 {})",
Version::CURRENT.name()
)
})?;
match declared {
None => declared = Some(v),
Some(prev) if prev != v => {
return Err(format!("@version 冲突:{} 与 {}", prev.name(), v.name()))
}
Some(_) => {}
}
continue;
}
rest.push_str(line);
rest.push('\n');
}
Ok((rest, declared))
}
fn features_for(v: Version, feats: FeatureSet, had_feature: bool) -> FeatureSet {
if had_feature {
feats
} else {
FeatureSet::for_version(v)
}
}
pub fn parse_versioned(text: &str) -> Result<(Value, Version), String> {
let (rest, declared) = strip_version(text)?;
let (rest, feats, base, had) = strip_features(&rest)?;
let v = declared.or(base).unwrap_or(Version::V1);
let feats = features_for(v, feats, had);
Ok((parse_impl(&rest, v, feats)?, v))
}
pub fn parse_file_versioned(path: impl AsRef<Path>) -> Result<(Value, Version), String> {
let path = path.as_ref();
let text =
std::fs::read_to_string(path).map_err(|e| format!("读取失败 {}: {e}", path.display()))?;
let base = path
.parent()
.map(|p| p.to_path_buf())
.unwrap_or_else(|| PathBuf::from("."));
let (rest, declared) = strip_version(&text)?;
let (rest, feats, base_ver, had) = strip_features(&rest)?;
let allowed = FeatureSet::all().intersection(feats);
let v = declared.or(base_ver).unwrap_or(Version::V1);
let feats = features_for(v, allowed, had);
let toks = resolve_includes(&rest, &base, allowed)?;
let val = parse_impl_tokens(toks, v, feats)?;
Ok((val, v))
}
pub fn parse(text: &str) -> Result<Value, String> {
let (rest, declared) = strip_version(text)?;
let (rest, feats, base, had) = strip_features(&rest)?;
let v = declared.or(base).unwrap_or(Version::V1);
let feats = features_for(v, feats, had);
parse_impl(&rest, v, feats)
}
pub fn parse_allowed(
text: &str,
allowed: &[Version],
) -> Result<Value, String> {
let (rest, declared) = strip_version(text)?;
let (rest, feats, base, had) = strip_features(&rest)?;
let v = declared.or(base).unwrap_or(Version::V1);
if !allowed.contains(&v) {
return Err(format!(
"sml: 文档声明版本 {} 不在本库接受的版本范围 {{{}}} 内",
v.name(),
allowed
.iter()
.map(|x| x.name())
.collect::<Vec<_>>()
.join(", ")
));
}
let feats = features_for(v, feats, had);
parse_impl(&rest, v, feats)
}
pub fn parse_with_features(
text: &str,
allowed: FeatureSet,
) -> Result<(Value, FeatureSet), String> {
let (rest, declared) = strip_version(text)?;
let (rest, feats, base, had) = strip_features(&rest)?;
let v = declared.or(base).unwrap_or(Version::V1);
let feats = features_for(v, feats, had);
let effective = feats.intersection(allowed);
if effective.is_empty() {
return Err(format!(
"sml: 文档请求的特性 {feats} 与调用方允许的特性 {allowed} 无交集"
));
}
let val = parse_impl(&rest, v, effective)?;
Ok((val, effective))
}
fn parse_impl(text: &str, version: Version, features: FeatureSet) -> Result<Value, String> {
let toks = tokenize(text)?;
parse_impl_tokens(toks, version, features)
}
fn parse_impl_tokens(
toks: Vec<Tok>,
version: Version,
features: FeatureSet,
) -> Result<Value, String> {
let mut p = Parser {
toks,
i: 0,
fragments: BTreeMap::new(),
contracts: BTreeMap::new(),
features,
ns_stack: Vec::new(),
};
match p.peek() {
Some(Tok::LBrack) => {
if !p.features.has(Feature::TopArray) {
return Err("sml: 顶层数组需要特性 `top-level-array`,但当前特性集已禁用".into());
}
p.next();
p.parse_array()
}
Some(Tok::LBrace) => {
p.next();
p.parse_block(Some(Tok::RBrace))
}
_ => p.parse_block(None),
}
}
const MAX_INCLUDE_DEPTH: usize = 32;
fn strip_line_comment(line: &str) -> &str {
let bytes = line.as_bytes();
let mut i = 0;
let mut in_quote = false;
while i < bytes.len() {
match bytes[i] {
b'"' => in_quote = !in_quote,
b'\\' if in_quote => i += 1,
b'#' if !in_quote => return &line[..i],
_ => {}
}
i += 1;
}
line
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IncludeTarget {
pub raw: String,
pub namespace: Option<String>,
pub via_import: bool,
}
fn parse_include_line(line: &str, features: FeatureSet) -> Result<Option<Vec<IncludeTarget>>, String> {
let content = strip_line_comment(line).trim();
let content = content.strip_prefix('@').unwrap_or(content).trim_start();
let (via_import, rest) = if let Some(r) = content.strip_prefix("include ") {
(false, r.trim_start())
} else if let Some(r) = content.strip_prefix("import ") {
(true, r.trim_start())
} else {
return Ok(None);
};
if !features.has(Feature::Include) {
return Ok(None);
}
let mut targets: Vec<IncludeTarget> = Vec::new();
let mut rest = rest;
loop {
let (path, tail) = match next_token(rest) {
Some((p, t)) => (p, t),
None => {
if targets.is_empty() && rest.trim().is_empty() {
return Ok(None);
} else {
break;
}
}
};
rest = tail.trim_start();
let mut ns: Option<String> = None;
let rest_after = if let Some(stripped) = rest.strip_prefix("as ") {
let (n, t) = match next_token(stripped.trim_start()) {
Some((n, t)) => (n, t),
None => return Ok(None),
};
ns = Some(n);
t.trim_start()
} else {
rest
};
targets.push(finalize_target(path, ns, via_import, features));
if let Some(stripped) = rest_after.strip_prefix(',') {
if !features.has(Feature::MultiInclude) {
return Ok(None);
}
rest = stripped.trim_start();
continue;
} else {
rest = rest_after;
break;
}
}
if targets.is_empty() {
return Ok(None);
}
for t in &targets {
if t.raw.starts_with("re:") {
if !features.has(Feature::RegexInclude) {
return Err("sml: 正则 include 需要特性 `regex-include`(请 @feature enable regex-include)".into());
}
continue;
}
if t.raw.contains('*') && !features.has(Feature::GlobInclude) {
return Err("sml: 通配 include 需要特性 `glob-include`(请 @feature enable glob-include)".into());
}
}
Ok(Some(targets))
}
fn next_token(s: &str) -> Option<(String, &str)> {
let s = s.trim_start();
if s.is_empty() {
return None;
}
if s.starts_with('"') {
let bytes = s.as_bytes();
let mut i = 1;
let mut out = String::new();
while i < bytes.len() {
if bytes[i] == b'"' {
i += 1;
break;
}
if bytes[i] == b'\\' && i + 1 < bytes.len() {
i += 1;
out.push(bytes[i] as char);
i += 1;
} else {
out.push(bytes[i] as char);
i += 1;
}
}
Some((out, &s[i..]))
} else {
let end = s
.find(|c: char| c.is_whitespace() || c == ',')
.unwrap_or(s.len());
let (tok, tail) = s.split_at(end);
Some((tok.trim().to_string(), tail))
}
}
fn finalize_target(
raw: String,
ns: Option<String>,
via_import: bool,
features: FeatureSet,
) -> IncludeTarget {
let namespace = match ns {
Some(n) => Some(n),
None => {
if via_import || (features.has(Feature::ImplicitNs) && !raw.contains('.')) {
Some(raw.clone())
} else {
None
}
}
};
IncludeTarget {
raw,
namespace,
via_import,
}
}
fn resolve_target_paths(
t: &IncludeTarget,
base: &Path,
features: FeatureSet,
) -> Result<Vec<PathBuf>, String> {
if let Some(pat) = t.raw.strip_prefix("re:") {
if !features.has(Feature::RegexInclude) {
return Err("sml: 正则 include 需要特性 `regex-include`(请 @feature enable regex-include)".into());
}
let pat = pat.trim_matches('"');
let pat = pat.replace('/', std::path::MAIN_SEPARATOR_STR);
let (dir, pat) = split_dir(&pat);
return glob_or_regex_dir(&base.join(dir), pat, Some(pat), features);
}
if t.raw.contains('*') {
if !features.has(Feature::GlobInclude) {
return Err("sml: 通配 include 需要特性 `glob-include`(请 @feature enable glob-include)".into());
}
let normalized = t.raw.replace('/', std::path::MAIN_SEPARATOR_STR);
let (dir, pat) = split_dir(&normalized);
return glob_or_regex_dir(&base.join(dir), pat, None, features);
}
let path = if t.via_import {
let rel = t
.raw
.split('.')
.collect::<Vec<_>>()
.join(std::path::MAIN_SEPARATOR_STR);
base.join(rel).with_extension("sml")
} else if t.raw.contains('.') {
let _ = features.has(Feature::ExtRewrite);
base.join(&t.raw)
} else {
base.join(format!("{}.sml", t.raw))
};
Ok(vec![path])
}
fn split_dir(pat: &str) -> (&str, &str) {
match pat.rfind(std::path::MAIN_SEPARATOR) {
Some(idx) => (&pat[..idx], &pat[idx + 1..]),
None => ("", pat),
}
}
fn glob_or_regex_dir(
base: &Path,
pattern: &str,
regex: Option<&str>,
_features: FeatureSet,
) -> Result<Vec<PathBuf>, String> {
let mut hits: Vec<PathBuf> = Vec::new();
let entries = std::fs::read_dir(base)
.map_err(|e| format!("include 目录读取失败 {}: {e}", base.display()))?;
let re = regex.map(|r| compile_regex(r));
for ent in entries {
let ent = ent.map_err(|e| format!("include 目录遍历失败: {e}"))?;
let p = ent.path();
if p.is_dir() {
continue; }
let name = match p.file_name().and_then(|n| n.to_str()) {
Some(n) => n,
None => continue,
};
let matched = if let Some(re) = &re {
regex_matches(re, name)
} else {
let pat_file = pattern.rsplit(std::path::MAIN_SEPARATOR).next().unwrap_or(pattern);
glob_matches(pat_file, name)
};
if matched {
hits.push(p);
}
}
hits.sort();
Ok(hits)
}
fn glob_matches(pattern: &str, text: &str) -> bool {
let segs: Vec<&str> = pattern.split('*').collect();
if segs.is_empty() {
return text.is_empty();
}
let mut pos = 0usize;
if !pattern.starts_with('*') {
if !text[pos..].starts_with(segs[0]) {
return false;
}
pos += segs[0].len();
}
for seg in &segs[if pattern.starts_with('*') { 0 } else { 1 }..] {
if seg.is_empty() {
continue;
}
match text[pos..].find(seg) {
Some(idx) => pos += idx + seg.len(),
None => return false,
}
}
if !pattern.ends_with('*') {
if pos != text.len() {
return false;
}
}
true
}
struct MiniRegex {
pattern: String,
}
fn compile_regex(pat: &str) -> MiniRegex {
MiniRegex {
pattern: pat.to_string(),
}
}
fn regex_matches(re: &MiniRegex, text: &str) -> bool {
let pat = &re.pattern;
let anchored_start = pat.starts_with('^');
let anchored_end = pat.ends_with('$');
let p = if anchored_start { &pat[1..] } else { pat };
let p = if anchored_end { &p[..p.len().saturating_sub(1)] } else { p };
if anchored_start {
backtrack_match(p, text, 0).is_some()
} else {
for start in 0..=text.len() {
if backtrack_match(p, text, start).is_some() {
if !anchored_end {
return true;
}
if backtrack_match(p, text, start) == Some(text.len()) {
return true;
}
}
}
false
}
}
fn backtrack_match(pat: &str, text: &str, ti: usize) -> Option<usize> {
let pchars: Vec<char> = pat.chars().collect();
let tchars: Vec<char> = text.chars().collect();
fn go(pchars: &[char], tchars: &[char], pi: usize, ti: usize) -> Option<usize> {
let mut pi = pi;
let mut ti = ti;
while pi < pchars.len() {
match pchars[pi] {
'\\' => {
if pi + 1 >= pchars.len() {
return None;
}
let pc = pchars[pi + 1];
if ti >= tchars.len() || tchars[ti] != pc {
return None;
}
pi += 2;
ti += 1;
}
'.' => {
if ti >= tchars.len() {
return None;
}
pi += 1;
ti += 1;
}
'*' => {
let prev = if pi >= 1 { Some(pchars[pi - 1]) } else { None };
if ti < tchars.len() {
let mut end = ti;
match prev {
Some('.') => {
while end < tchars.len() {
end += 1;
}
}
Some(c) if c != '\\' => {
while end < tchars.len() && tchars[end] == c {
end += 1;
}
}
_ => {}
}
let mut e = end;
while e >= ti {
if let Some(r) = go(pchars, tchars, pi + 1, e) {
return Some(r);
}
if e == ti {
break;
}
e -= 1;
}
}
return go(pchars, tchars, pi + 1, ti);
}
'+' => {
if ti >= tchars.len() {
return None;
}
let prev = pchars.get(pi.wrapping_sub(1)).copied();
let mut consumed = 0;
match prev {
Some('.') => {
if ti >= tchars.len() {
return None;
}
consumed = 1;
}
Some(c) if c != '\\' => {
if tchars[ti] != c {
return None;
}
consumed = 1;
while ti + consumed < tchars.len()
&& tchars[ti + consumed] == c
{
consumed += 1;
}
}
_ => return None,
}
pi += 1;
ti += consumed;
}
'?' => {
let prev = pchars.get(pi.wrapping_sub(1)).copied();
if ti < tchars.len() {
match prev {
Some('.') => {
pi += 1;
ti += 1;
}
Some(c) if c != '\\' => {
if tchars[ti] == c {
pi += 1;
ti += 1;
} else {
pi += 1; }
}
_ => {
pi += 1; }
}
} else {
pi += 1;
}
}
'[' => {
let mut j = pi + 1;
let negate = if j < pchars.len() && pchars[j] == '^' {
j += 1;
true
} else {
false
};
let mut cls = Vec::new();
while j < pchars.len() && pchars[j] != ']' {
if j + 2 < pchars.len()
&& pchars[j + 1] == '-'
&& pchars[j + 2] != ']'
{
let lo = pchars[j];
let hi = pchars[j + 2];
cls.push((lo, hi));
j += 3;
} else {
cls.push((pchars[j], pchars[j]));
j += 1;
}
}
if j >= pchars.len() {
return None; }
if ti >= tchars.len() {
return None;
}
let c = tchars[ti];
let in_cls = cls.iter().any(|(lo, hi)| c >= *lo && c <= *hi);
let ok = if negate { !in_cls } else { in_cls };
if !ok {
return None;
}
pi = j + 1;
ti += 1;
}
c => {
if ti >= tchars.len() || tchars[ti] != c {
return None;
}
pi += 1;
ti += 1;
}
}
}
Some(ti)
}
go(&pchars, &tchars, 0, ti)
}
pub fn resolve_includes(
text: &str,
base: &Path,
features: FeatureSet,
) -> Result<Vec<Tok>, String> {
let mut stack: Vec<PathBuf> = Vec::new();
let mut toks: Vec<Tok> = Vec::new();
expand_includes(text, base, &mut stack, features, &mut toks)?;
Ok(toks)
}
fn expand_includes(
text: &str,
base: &Path,
stack: &mut Vec<PathBuf>,
features: FeatureSet,
out: &mut Vec<Tok>,
) -> Result<(), String> {
if stack.len() >= MAX_INCLUDE_DEPTH {
return Err(format!("include 嵌套超过 {MAX_INCLUDE_DEPTH} 层"));
}
for line in text.lines() {
match parse_include_line(line, features)? {
Some(targets) => {
if !features.has(Feature::Include) {
return Err("sml: 当前特性集禁用了 include(include 特性)".into());
}
for t in targets {
if t.namespace.is_some() && !features.has(Feature::Namespace) {
return Err(
"sml: 当前特性集禁用了命名空间包含(namespace 特性)".into(),
);
}
let paths = resolve_target_paths(&t, base, features)?;
for path in paths {
let canon = path.canonicalize().map_err(|e| {
format!("include 无法定位 {}: {e}", path.display())
})?;
if stack.iter().any(|p| p == &canon) {
return Err(format!("include 循环引用: {}", canon.display()));
}
let content = std::fs::read_to_string(&canon)
.map_err(|e| format!("include 读取失败 {}: {e}", canon.display()))?;
let child_base = canon
.parent()
.map(|p| p.to_path_buf())
.unwrap_or_else(|| PathBuf::from("."));
stack.push(canon.clone());
if let Some(ns) = &t.namespace {
for seg in ns.split('.') {
out.push(Tok::Word(seg.to_string()));
out.push(Tok::LBrace);
}
let inner =
expand_file_tokens(&content, &child_base, stack, features)?;
out.extend(inner);
for _ in ns.split('.') {
out.push(Tok::RBrace);
}
} else {
let inner =
expand_file_tokens(&content, &child_base, stack, features)?;
out.extend(inner);
}
stack.pop();
}
}
}
None => {
let line_toks = tokenize(line).map_err(|e| {
format!("include 预处理词法错误:{e}(于行:{line})")
})?;
out.extend(line_toks);
}
}
}
Ok(())
}
fn expand_file_tokens(
content: &str,
base: &Path,
stack: &mut Vec<PathBuf>,
features: FeatureSet,
) -> Result<Vec<Tok>, String> {
let cleaned: String = content
.lines()
.filter(|l| {
let t = strip_line_comment(l).trim();
let t = t.strip_prefix('@').unwrap_or(t).trim_start();
!(t.starts_with("version") || t.starts_with("feature"))
})
.collect::<Vec<_>>()
.join("\n");
let mut toks = Vec::new();
expand_includes(&cleaned, base, stack, features, &mut toks)?;
Ok(toks)
}
pub fn parse_file(path: impl AsRef<Path>) -> Result<Value, String> {
let path = path.as_ref();
let text = std::fs::read_to_string(path)
.map_err(|e| format!("读取失败 {}: {e}", path.display()))?;
let base = path
.parent()
.map(|p| p.to_path_buf())
.unwrap_or_else(|| PathBuf::from("."));
let (rest, declared) = strip_version(&text)?;
let (rest, feats, base_ver, had) = strip_features(&rest)?;
let v = declared.or(base_ver).unwrap_or(Version::V1);
let feats = features_for(v, feats, had);
let allowed = FeatureSet::all().intersection(feats);
let toks = resolve_includes(&rest, &base, allowed)?;
parse_impl_tokens(toks, v, allowed)
}
pub fn loads(text: &str) -> Result<Value, ParseError> {
parse(text).map_err(ParseError)
}
#[derive(Debug)]
pub struct ParseError(pub String);
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "sml parse error: {}", self.0)
}
}
impl std::error::Error for ParseError {}
fn quote_if_needed(s: &str) -> String {
if s.is_empty() || s.contains([' ', '\t', '\n', '\r', ':', '#', '{', '}']) {
format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\""))
} else {
s.to_string()
}
}
fn dump_block(m: &BTreeMap<String, Value>, indent: usize, out: &mut String) {
if m.is_empty() {
out.push_str("{}");
return;
}
out.push_str(&format!("\n{}{{", " ".repeat(indent)));
for (k, val) in m {
out.push_str(&format!("\n{}{}: ", " ".repeat(indent + 1), k));
dump_value(val, indent + 1, out);
}
out.push_str(&format!("\n{}}}", " ".repeat(indent)));
}
fn dump_value(v: &Value, indent: usize, out: &mut String) {
let pad = " ".repeat(indent);
match v {
Value::Null => out.push_str("null"),
Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
Value::Int(i) => out.push_str(&i.to_string()),
Value::Float(f) => out.push_str(&format!("{}", f)),
Value::Str(s) => out.push_str("e_if_needed(s)),
Value::Array(a) => {
if a.is_empty() {
out.push_str("[]");
} else {
out.push('[');
for e in a {
out.push('\n');
out.push_str(&format!("{}{}", " ".repeat(indent + 1), dump_inline(e)));
}
out.push_str(&format!("\n{}]", pad));
}
}
Value::Object(m) => dump_block(m, indent, out),
}
}
fn dump_scalar(v: &Value) -> String {
match v {
Value::Null => "null".into(),
Value::Bool(b) => b.to_string(),
Value::Int(i) => i.to_string(),
Value::Float(f) => f.to_string(),
Value::Str(s) => quote_if_needed(s),
_ => "".into(),
}
}
fn dump_inline(v: &Value) -> String {
match v {
Value::Object(m) => {
let parts: Vec<String> = m
.iter()
.map(|(k, val)| format!("{}: {}", k, dump_inline(val)))
.collect();
format!("{{ {} }}", parts.join(", "))
}
Value::Array(a) => {
let parts: Vec<String> = a.iter().map(dump_inline).collect();
format!("[ {} ]", parts.join(", "))
}
other => dump_scalar(other),
}
}
pub fn to_sml(v: &Value) -> String {
let mut out = String::new();
if let Value::Object(m) = v {
if m.contains_key("__type") {
dump_block(m, 0, &mut out);
} else {
for (k, val) in m {
out.push_str(&format!("{}: ", k));
dump_value(val, 0, &mut out);
out.push('\n');
}
}
} else {
out.push_str(&dump_inline(v));
}
out
}
use std::os::raw::{c_char, c_int};
use std::ptr;
fn cstr(s: &str) -> *mut c_char {
let c = std::ffi::CString::new(s).unwrap_or_default();
c.into_raw()
}
#[cfg_attr(edge2024, unsafe(no_mangle))]
#[cfg_attr(not(edge2024), no_mangle)]
pub extern "C" fn sml_parse(text: *const c_char) -> *mut c_char {
if text.is_null() {
return ptr::null_mut();
}
let t = unsafe { std::ffi::CStr::from_ptr(text) }.to_string_lossy().into_owned();
match parse(&t) {
Ok(v) => cstr(&jsonify(&v)),
Err(_) => ptr::null_mut(),
}
}
#[cfg_attr(edge2024, unsafe(no_mangle))]
#[cfg_attr(not(edge2024), no_mangle)]
pub extern "C" fn sml_dump(json: *const c_char) -> *mut c_char {
if json.is_null() {
return ptr::null_mut();
}
let j = unsafe { std::ffi::CStr::from_ptr(json) }.to_string_lossy().into_owned();
match json_to_value(&j) {
Some(v) => cstr(&to_sml(&v)),
None => ptr::null_mut(),
}
}
#[cfg_attr(edge2024, unsafe(no_mangle))]
#[cfg_attr(not(edge2024), no_mangle)]
pub unsafe extern "C" fn sml_free(p: *mut c_char) {
if !p.is_null() {
drop(unsafe { std::ffi::CString::from_raw(p) });
}
}
#[cfg_attr(edge2024, unsafe(no_mangle))]
#[cfg_attr(not(edge2024), no_mangle)]
pub extern "C" fn sml_version() -> *mut c_char {
cstr(concat!("sml ", env!("CARGO_PKG_VERSION")))
}
fn jsonify(v: &Value) -> String {
fn esc(s: &str) -> String {
s.replace('\\', "\\\\").replace('"', "\\\"")
}
match v {
Value::Null => "null".into(),
Value::Bool(b) => b.to_string(),
Value::Int(i) => i.to_string(),
Value::Float(f) => f.to_string(),
Value::Str(s) => format!("\"{}\"", esc(s)),
Value::Array(a) => {
let parts: Vec<String> = a.iter().map(jsonify).collect();
format!("[{}]", parts.join(","))
}
Value::Object(m) => {
let parts: Vec<String> = m
.iter()
.map(|(k, val)| format!("\"{}\":{}", esc(k), jsonify(val)))
.collect();
format!("{{{}}}", parts.join(","))
}
}
}
fn json_to_value(s: &str) -> Option<Value> {
let bytes = s.as_bytes();
let mut i = 0;
let _n = bytes.len();
let mut skip_ws = |b: &[u8], i: &mut usize| {
while *i < b.len() && matches!(b[*i], b' ' | b'\t' | b'\n' | b'\r') {
*i += 1;
}
};
let mut parse_str = |b: &[u8], i: &mut usize| -> Option<String> {
skip_ws(b, i);
if *i >= b.len() || b[*i] != b'"' {
return None;
}
*i += 1;
let mut out = String::new();
while *i < b.len() {
let c = b[*i];
if c == b'"' {
*i += 1;
return Some(out);
}
if c == b'\\' && *i + 1 < b.len() {
*i += 1;
let e = b[*i];
out.push(match e {
b'n' => '\n',
b't' => '\t',
b'r' => '\r',
b'"' => '"',
b'\\' => '\\',
_ => e as char,
});
} else {
out.push(c as char);
}
*i += 1;
}
None
};
fn parse_val_impl(
b: &[u8],
i: &mut usize,
s: &str,
parse_str: &dyn Fn(&[u8], &mut usize) -> Option<String>,
) -> Option<Value> {
let mut skip_ws = |b: &[u8], i: &mut usize| {
while *i < b.len() && matches!(b[*i], b' ' | b'\t' | b'\n' | b'\r') {
*i += 1;
}
};
skip_ws(b, i);
if *i >= b.len() {
return None;
}
match b[*i] {
b'{' => {
*i += 1;
let mut m = BTreeMap::new();
skip_ws(b, i);
if *i < b.len() && b[*i] == b'}' {
*i += 1;
return Some(Value::Object(m));
}
loop {
skip_ws(b, i);
let k = parse_str(b, i)?;
skip_ws(b, i);
if *i < b.len() && b[*i] == b':' {
*i += 1;
}
let v = parse_val_impl(b, i, s, parse_str)?;
m.insert(k, v);
skip_ws(b, i);
if *i < b.len() && b[*i] == b',' {
*i += 1;
} else if *i < b.len() && b[*i] == b'}' {
*i += 1;
break;
}
}
Some(Value::Object(m))
}
b'[' => {
*i += 1;
let mut a = Vec::new();
skip_ws(b, i);
if *i < b.len() && b[*i] == b']' {
*i += 1;
return Some(Value::Array(a));
}
loop {
a.push(parse_val_impl(b, i, s, parse_str)?);
skip_ws(b, i);
if *i < b.len() && b[*i] == b',' {
*i += 1;
} else if *i < b.len() && b[*i] == b']' {
*i += 1;
break;
}
}
Some(Value::Array(a))
}
b'"' => parse_str(b, i).map(Value::Str),
b't' => {
if s[*i..].starts_with("true") {
*i += 4;
Some(Value::Bool(true))
} else {
None
}
}
b'f' => {
if s[*i..].starts_with("false") {
*i += 5;
Some(Value::Bool(false))
} else {
None
}
}
b'n' => {
if s[*i..].starts_with("null") {
*i += 4;
Some(Value::Null)
} else {
None
}
}
_ => {
let start = *i;
while *i < b.len()
&& (b[*i].is_ascii_digit()
|| matches!(b[*i], b'-' | b'+' | b'.' | b'e' | b'E'))
{
*i += 1;
}
let tok = s[start..*i].to_string();
if let Ok(iv) = tok.parse::<i64>() {
Some(Value::Int(iv))
} else if let Ok(fv) = tok.parse::<f64>() {
Some(Value::Float(fv))
} else {
None
}
}
}
}
parse_val_impl(bytes, &mut i, s, &parse_str)
}
#[cfg(feature = "serde")]
pub mod serde {
use super::Value;
use ::serde::de::{self, MapAccess, SeqAccess, Visitor};
use ::serde::ser::{
SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant,
SerializeTuple, SerializeTupleStruct, SerializeTupleVariant,
};
use ::serde::{Deserialize, Deserializer, Serialize, Serializer};
use ::std::collections::BTreeMap;
use ::std::fmt;
type Error = ::serde::de::value::Error;
fn type_err(v: &Value, expected: &str) -> Error {
de::Error::custom(format!(
"期望 {expected},实际为 {}",
super::__private::describe_value(v)
))
}
impl Serialize for Value {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Value::Null => serializer.serialize_unit(),
Value::Bool(b) => serializer.serialize_bool(*b),
Value::Int(i) => serializer.serialize_i64(*i),
Value::Float(f) => serializer.serialize_f64(*f),
Value::Str(s) => serializer.serialize_str(s),
Value::Array(a) => a.serialize(serializer),
Value::Object(m) => {
let mut map = serializer.serialize_map(Some(m.len()))?;
for (k, v) in m {
map.serialize_entry(k, v)?;
}
map.end()
}
}
}
}
impl<'de> Deserialize<'de> for Value {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(ValueVisitor)
}
}
struct ValueVisitor;
impl<'de> Visitor<'de> for ValueVisitor {
type Value = Value;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("any valid SML/JSON value")
}
fn visit_unit<E: de::Error>(self) -> Result<Value, E> {
Ok(Value::Null)
}
fn visit_none<E: de::Error>(self) -> Result<Value, E> {
Ok(Value::Null)
}
fn visit_some<D>(self, d: D) -> Result<Value, D::Error>
where
D: Deserializer<'de>,
{
Deserialize::deserialize(d)
}
fn visit_bool<E: de::Error>(self, v: bool) -> Result<Value, E> {
Ok(Value::Bool(v))
}
fn visit_i64<E: de::Error>(self, v: i64) -> Result<Value, E> {
Ok(Value::Int(v))
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<Value, E> {
Ok(i64::try_from(v)
.map(Value::Int)
.unwrap_or_else(|_| Value::Float(v as f64)))
}
fn visit_f64<E: de::Error>(self, v: f64) -> Result<Value, E> {
Ok(Value::Float(v))
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<Value, E> {
Ok(Value::Str(v.to_string()))
}
fn visit_string<E: de::Error>(self, v: String) -> Result<Value, E> {
Ok(Value::Str(v))
}
fn visit_seq<A>(self, mut seq: A) -> Result<Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut v = Vec::new();
while let Some(x) = seq.next_element()? {
v.push(x);
}
Ok(Value::Array(v))
}
fn visit_map<A>(self, mut map: A) -> Result<Value, A::Error>
where
A: MapAccess<'de>,
{
let mut m = BTreeMap::new();
while let Some((k, v)) = map.next_entry::<String, Value>()? {
m.insert(k, v);
}
Ok(Value::Object(m))
}
}
pub fn from_str<T: de::DeserializeOwned>(text: &str) -> Result<T, String> {
let value = crate::parse(text)?;
from_value(value)
}
pub fn from_value<T: de::DeserializeOwned>(value: Value) -> Result<T, String> {
T::deserialize(ValueDeserializer(value)).map_err(|e| e.to_string())
}
pub fn to_value<T: Serialize + ?Sized>(value: &T) -> Result<Value, String> {
value.serialize(ValueSerializer).map_err(|e| e.to_string())
}
pub fn to_string<T: Serialize + ?Sized>(value: &T) -> Result<String, String> {
Ok(crate::to_sml(&to_value(value)?))
}
struct ValueSerializer;
impl Serializer for ValueSerializer {
type Ok = Value;
type Error = Error;
type SerializeSeq = SeqSerializer;
type SerializeTuple = SeqSerializer;
type SerializeTupleStruct = SeqSerializer;
type SerializeTupleVariant = TupleVariantSerializer;
type SerializeMap = MapSerializer;
type SerializeStruct = MapSerializer;
type SerializeStructVariant = StructVariantSerializer;
fn serialize_bool(self, v: bool) -> Result<Value, Error> {
Ok(Value::Bool(v))
}
fn serialize_i8(self, v: i8) -> Result<Value, Error> {
Ok(Value::Int(v as i64))
}
fn serialize_i16(self, v: i16) -> Result<Value, Error> {
Ok(Value::Int(v as i64))
}
fn serialize_i32(self, v: i32) -> Result<Value, Error> {
Ok(Value::Int(v as i64))
}
fn serialize_i64(self, v: i64) -> Result<Value, Error> {
Ok(Value::Int(v))
}
fn serialize_u8(self, v: u8) -> Result<Value, Error> {
Ok(Value::Int(v as i64))
}
fn serialize_u16(self, v: u16) -> Result<Value, Error> {
Ok(Value::Int(v as i64))
}
fn serialize_u32(self, v: u32) -> Result<Value, Error> {
Ok(Value::Int(v as i64))
}
fn serialize_u64(self, v: u64) -> Result<Value, Error> {
Ok(i64::try_from(v)
.map(Value::Int)
.unwrap_or_else(|_| Value::Float(v as f64)))
}
fn serialize_f32(self, v: f32) -> Result<Value, Error> {
Ok(Value::Float(v as f64))
}
fn serialize_f64(self, v: f64) -> Result<Value, Error> {
Ok(Value::Float(v))
}
fn serialize_char(self, v: char) -> Result<Value, Error> {
Ok(Value::Str(v.to_string()))
}
fn serialize_str(self, v: &str) -> Result<Value, Error> {
Ok(Value::Str(v.to_string()))
}
fn serialize_bytes(self, v: &[u8]) -> Result<Value, Error> {
Ok(Value::Array(v.iter().map(|&b| Value::Int(b as i64)).collect()))
}
fn serialize_none(self) -> Result<Value, Error> {
Ok(Value::Null)
}
fn serialize_some<T: Serialize + ?Sized>(self, v: &T) -> Result<Value, Error> {
v.serialize(ValueSerializer)
}
fn serialize_unit(self) -> Result<Value, Error> {
Ok(Value::Null)
}
fn serialize_unit_struct(self, _name: &'static str) -> Result<Value, Error> {
Ok(Value::Null)
}
fn serialize_unit_variant(
self,
_name: &'static str,
_idx: u32,
variant: &'static str,
) -> Result<Value, Error> {
Ok(Value::Str(variant.to_string()))
}
fn serialize_newtype_struct<T: Serialize + ?Sized>(
self,
_name: &'static str,
v: &T,
) -> Result<Value, Error> {
v.serialize(ValueSerializer)
}
fn serialize_newtype_variant<T: Serialize + ?Sized>(
self,
_name: &'static str,
_idx: u32,
variant: &'static str,
value: &T,
) -> Result<Value, Error> {
Ok(Value::Object(BTreeMap::from([
("__type".into(), Value::Str(variant.to_string())),
("_value".into(), value.serialize(ValueSerializer)?),
])))
}
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Error> {
Ok(SeqSerializer(Vec::new()))
}
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Error> {
self.serialize_seq(Some(len))
}
fn serialize_tuple_struct(
self,
_name: &'static str,
len: usize,
) -> Result<Self::SerializeTupleStruct, Error> {
self.serialize_seq(Some(len))
}
fn serialize_tuple_variant(
self,
_name: &'static str,
_idx: u32,
variant: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleVariant, Error> {
Ok(TupleVariantSerializer {
variant: variant.to_string(),
values: Vec::new(),
})
}
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Error> {
Ok(MapSerializer {
map: BTreeMap::new(),
key: None,
})
}
fn serialize_struct(self, _name: &'static str, len: usize) -> Result<Self::SerializeStruct, Error> {
self.serialize_map(Some(len))
}
fn serialize_struct_variant(
self,
_name: &'static str,
_idx: u32,
variant: &'static str,
_len: usize,
) -> Result<Self::SerializeStructVariant, Error> {
Ok(StructVariantSerializer {
variant: variant.to_string(),
map: BTreeMap::new(),
})
}
}
struct SeqSerializer(Vec<Value>);
impl SerializeSeq for SeqSerializer {
type Ok = Value;
type Error = Error;
fn serialize_element<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Error> {
self.0.push(value.serialize(ValueSerializer)?);
Ok(())
}
fn end(self) -> Result<Value, Error> {
Ok(Value::Array(self.0))
}
}
impl SerializeTuple for SeqSerializer {
type Ok = Value;
type Error = Error;
fn serialize_element<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Error> {
SerializeSeq::serialize_element(self, value)
}
fn end(self) -> Result<Value, Error> {
SerializeSeq::end(self)
}
}
impl SerializeTupleStruct for SeqSerializer {
type Ok = Value;
type Error = Error;
fn serialize_field<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Error> {
SerializeSeq::serialize_element(self, value)
}
fn end(self) -> Result<Value, Error> {
SerializeSeq::end(self)
}
}
struct MapSerializer {
map: BTreeMap<String, Value>,
key: Option<String>,
}
impl SerializeMap for MapSerializer {
type Ok = Value;
type Error = Error;
fn serialize_key<T: Serialize + ?Sized>(&mut self, key: &T) -> Result<(), Error> {
self.key = Some(key.serialize(KeySerializer)?);
Ok(())
}
fn serialize_value<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Error> {
let k = self
.key
.take()
.ok_or_else(|| de::Error::custom("serialize_value 前需先 serialize_key"))?;
self.map.insert(k, value.serialize(ValueSerializer)?);
Ok(())
}
fn end(self) -> Result<Value, Error> {
Ok(Value::Object(self.map))
}
}
impl SerializeStruct for MapSerializer {
type Ok = Value;
type Error = Error;
fn serialize_field<T: Serialize + ?Sized>(
&mut self,
key: &'static str,
value: &T,
) -> Result<(), Error> {
self.map
.insert(key.to_string(), value.serialize(ValueSerializer)?);
Ok(())
}
fn end(self) -> Result<Value, Error> {
Ok(Value::Object(self.map))
}
}
struct KeySerializer;
macro_rules! key_unsupported {
($(fn $m:ident($($a:ident : $t:ty),*) -> Result<String, Error>;)*) => {
$(
fn $m(self, $($a: $t),*) -> Result<String, Error> {
Err(de::Error::custom("SML 对象的键必须是字符串"))
}
)*
};
}
impl Serializer for KeySerializer {
type Ok = String;
type Error = Error;
type SerializeSeq = ::serde::ser::Impossible<String, Error>;
type SerializeTuple = ::serde::ser::Impossible<String, Error>;
type SerializeTupleStruct = ::serde::ser::Impossible<String, Error>;
type SerializeTupleVariant = ::serde::ser::Impossible<String, Error>;
type SerializeMap = ::serde::ser::Impossible<String, Error>;
type SerializeStruct = ::serde::ser::Impossible<String, Error>;
type SerializeStructVariant = ::serde::ser::Impossible<String, Error>;
fn serialize_str(self, v: &str) -> Result<String, Error> {
Ok(v.to_string())
}
fn serialize_char(self, v: char) -> Result<String, Error> {
Ok(v.to_string())
}
key_unsupported! {
fn serialize_bool(_v: bool) -> Result<String, Error>;
fn serialize_i8(_v: i8) -> Result<String, Error>;
fn serialize_i16(_v: i16) -> Result<String, Error>;
fn serialize_i32(_v: i32) -> Result<String, Error>;
fn serialize_i64(_v: i64) -> Result<String, Error>;
fn serialize_u8(_v: u8) -> Result<String, Error>;
fn serialize_u16(_v: u16) -> Result<String, Error>;
fn serialize_u32(_v: u32) -> Result<String, Error>;
fn serialize_u64(_v: u64) -> Result<String, Error>;
fn serialize_f32(_v: f32) -> Result<String, Error>;
fn serialize_f64(_v: f64) -> Result<String, Error>;
fn serialize_bytes(_v: &[u8]) -> Result<String, Error>;
fn serialize_none() -> Result<String, Error>;
fn serialize_unit() -> Result<String, Error>;
fn serialize_unit_struct(_n: &'static str) -> Result<String, Error>;
fn serialize_unit_variant(_n: &'static str, _i: u32, _v: &'static str) -> Result<String, Error>;
}
fn serialize_some<T: Serialize + ?Sized>(self, _v: &T) -> Result<String, Error> {
Err(de::Error::custom("SML 对象的键必须是字符串"))
}
fn serialize_newtype_struct<T: Serialize + ?Sized>(
self,
_n: &'static str,
_v: &T,
) -> Result<String, Error> {
Err(de::Error::custom("SML 对象的键必须是字符串"))
}
fn serialize_newtype_variant<T: Serialize + ?Sized>(
self,
_n: &'static str,
_i: u32,
_v: &'static str,
_x: &T,
) -> Result<String, Error> {
Err(de::Error::custom("SML 对象的键必须是字符串"))
}
fn serialize_seq(self, _l: Option<usize>) -> Result<Self::SerializeSeq, Error> {
Err(de::Error::custom("SML 对象的键必须是字符串"))
}
fn serialize_tuple(self, _l: usize) -> Result<Self::SerializeTuple, Error> {
Err(de::Error::custom("SML 对象的键必须是字符串"))
}
fn serialize_tuple_struct(
self,
_n: &'static str,
_l: usize,
) -> Result<Self::SerializeTupleStruct, Error> {
Err(de::Error::custom("SML 对象的键必须是字符串"))
}
fn serialize_tuple_variant(
self,
_n: &'static str,
_i: u32,
_v: &'static str,
_l: usize,
) -> Result<Self::SerializeTupleVariant, Error> {
Err(de::Error::custom("SML 对象的键必须是字符串"))
}
fn serialize_map(self, _l: Option<usize>) -> Result<Self::SerializeMap, Error> {
Err(de::Error::custom("SML 对象的键必须是字符串"))
}
fn serialize_struct(self, _n: &'static str, _l: usize) -> Result<Self::SerializeStruct, Error> {
Err(de::Error::custom("SML 对象的键必须是字符串"))
}
fn serialize_struct_variant(
self,
_n: &'static str,
_i: u32,
_v: &'static str,
_l: usize,
) -> Result<Self::SerializeStructVariant, Error> {
Err(de::Error::custom("SML 对象的键必须是字符串"))
}
}
struct TupleVariantSerializer {
variant: String,
values: Vec<Value>,
}
impl SerializeTupleVariant for TupleVariantSerializer {
type Ok = Value;
type Error = Error;
fn serialize_field<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Error> {
self.values.push(value.serialize(ValueSerializer)?);
Ok(())
}
fn end(self) -> Result<Value, Error> {
Ok(Value::Object(BTreeMap::from([
("__type".into(), Value::Str(self.variant)),
("_value".into(), Value::Array(self.values)),
])))
}
}
struct StructVariantSerializer {
variant: String,
map: BTreeMap<String, Value>,
}
impl SerializeStructVariant for StructVariantSerializer {
type Ok = Value;
type Error = Error;
fn serialize_field<T: Serialize + ?Sized>(
&mut self,
key: &'static str,
value: &T,
) -> Result<(), Error> {
self.map
.insert(key.to_string(), value.serialize(ValueSerializer)?);
Ok(())
}
fn end(self) -> Result<Value, Error> {
let mut m = BTreeMap::new();
m.insert("__type".into(), Value::Str(self.variant));
m.extend(self.map);
Ok(Value::Object(m))
}
}
macro_rules! deser_int {
($(fn $m:ident($v:ident, $call:ident);)*) => {
$(
fn $m<V>(self, $v: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de> {
match self.0 {
Value::Int(i) => $v.$call(i as _),
Value::Float(f)
if f.fract() == 0.0
&& f >= i64::MIN as f64
&& f <= i64::MAX as f64 =>
{
$v.$call(f as _)
}
other => Err(type_err(&other, stringify!($m).trim_start_matches("deserialize_"))),
}
}
)*
};
}
struct ValueDeserializer(Value);
impl<'de> Deserializer<'de> for ValueDeserializer {
type Error = Error;
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
match self.0 {
Value::Null => visitor.visit_unit(),
Value::Bool(b) => visitor.visit_bool(b),
Value::Int(i) => visitor.visit_i64(i),
Value::Float(f) => visitor.visit_f64(f),
Value::Str(s) => visitor.visit_string(s),
Value::Array(a) => visitor.visit_seq(SeqDeserializer { items: a, idx: 0 }),
Value::Object(m) => visitor.visit_map(MapDeserializer { map: m, pending: None }),
}
}
fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
match self.0 {
Value::Bool(b) => visitor.visit_bool(b),
other => Err(type_err(&other, "布尔")),
}
}
deser_int! {
fn deserialize_i8(v, visit_i8);
fn deserialize_i16(v, visit_i16);
fn deserialize_i32(v, visit_i32);
fn deserialize_i64(v, visit_i64);
fn deserialize_u8(v, visit_u8);
fn deserialize_u16(v, visit_u16);
fn deserialize_u32(v, visit_u32);
}
fn deserialize_u64<V>(self, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
match self.0 {
Value::Int(i) if i >= 0 => visitor.visit_u64(i as u64),
Value::Float(f)
if f.fract() == 0.0 && f >= 0.0 && f <= u64::MAX as f64 =>
{
visitor.visit_u64(f as u64)
}
other => Err(type_err(&other, "u64")),
}
}
fn deserialize_f32<V>(self, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
match self.0 {
Value::Int(i) => visitor.visit_f32(i as f32),
Value::Float(f) => visitor.visit_f32(f as f32),
other => Err(type_err(&other, "f32")),
}
}
fn deserialize_f64<V>(self, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
match self.0 {
Value::Int(i) => visitor.visit_f64(i as f64),
Value::Float(f) => visitor.visit_f64(f),
other => Err(type_err(&other, "f64")),
}
}
fn deserialize_char<V>(self, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
match self.0 {
Value::Str(s) if s.chars().count() == 1 => {
visitor.visit_char(s.chars().next().unwrap())
}
other => Err(type_err(&other, "字符")),
}
}
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
match self.0 {
Value::Str(s) => visitor.visit_string(s),
other => Err(type_err(&other, "字符串")),
}
}
fn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
self.deserialize_str(visitor)
}
fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
match self.0 {
Value::Array(items) => {
let mut buf = Vec::with_capacity(items.len());
for it in items {
match it {
Value::Int(i) if (0..=255).contains(&i) => buf.push(i as u8),
other => return Err(type_err(&other, "字节")),
}
}
visitor.visit_byte_buf(buf)
}
other => Err(type_err(&other, "字节数组")),
}
}
fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
self.deserialize_bytes(visitor)
}
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
match self.0 {
Value::Null => visitor.visit_none(),
other => visitor.visit_some(ValueDeserializer(other)),
}
}
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
match self.0 {
Value::Null => visitor.visit_unit(),
other => Err(type_err(&other, "unit")),
}
}
fn deserialize_unit_struct<V>(
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
self.deserialize_unit(visitor)
}
fn deserialize_newtype_struct<V>(
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
self.deserialize_any(visitor)
}
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
match self.0 {
Value::Array(a) => visitor.visit_seq(SeqDeserializer { items: a, idx: 0 }),
other => Err(type_err(&other, "数组")),
}
}
fn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
self.deserialize_seq(visitor)
}
fn deserialize_tuple_struct<V>(
self,
_name: &'static str,
_len: usize,
visitor: V,
) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
self.deserialize_seq(visitor)
}
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
match self.0 {
Value::Object(m) => visitor.visit_map(MapDeserializer { map: m, pending: None }),
other => Err(type_err(&other, "块/对象")),
}
}
fn deserialize_struct<V>(
self,
_name: &'static str,
_fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
self.deserialize_map(visitor)
}
fn deserialize_enum<V>(
self,
_name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
match self.0 {
Value::Str(s) => visitor.visit_enum(EnumDeserializer {
variant: s,
kind: EnumKind::Unit,
}),
Value::Object(mut m) => {
if let Some(ty) = m.remove("__type") {
let variant = match ty {
Value::Str(s) => s,
_ => return Err(de::Error::custom("`__type` 的值必须是字符串")),
};
let kind = match m.remove("_value") {
Some(Value::Array(items)) => EnumKind::Tuple(items),
Some(other) => EnumKind::Newtype(other),
None if m.is_empty() => EnumKind::Unit,
None => EnumKind::Struct(m),
};
return visitor.visit_enum(EnumDeserializer { variant, kind });
}
if m.len() == 1 {
let (k, v) = m.pop_first().expect("len==1 必有键");
let kind = match v {
Value::Str(s) if s == k => EnumKind::Unit,
other => EnumKind::Newtype(other),
};
return visitor.visit_enum(EnumDeserializer { variant: k, kind });
}
Err(de::Error::custom(
"枚举块需要 `__type` 键(SML 约定)或单键外部标签 `{ VariantName: ... }`",
))
}
other => Err(type_err(&other, "枚举")),
}
}
fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
self.deserialize_str(visitor)
}
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
self.deserialize_any(visitor)
}
}
struct SeqDeserializer {
items: Vec<Value>,
idx: usize,
}
impl<'de> SeqAccess<'de> for SeqDeserializer {
type Error = Error;
fn next_element_seed<T: de::DeserializeSeed<'de>>(
&mut self,
seed: T,
) -> Result<Option<T::Value>, Error> {
if self.idx >= self.items.len() {
return Ok(None);
}
let item = self.items[self.idx].clone();
self.idx += 1;
seed.deserialize(ValueDeserializer(item)).map(Some)
}
}
struct MapDeserializer {
map: BTreeMap<String, Value>,
pending: Option<Value>,
}
impl<'de> MapAccess<'de> for MapDeserializer {
type Error = Error;
fn next_key_seed<K: de::DeserializeSeed<'de>>(
&mut self,
seed: K,
) -> Result<Option<K::Value>, Error> {
let Some((k, v)) = self.map.pop_first() else {
return Ok(None);
};
self.pending = Some(v);
seed.deserialize(KeyDeserializer(&k)).map(Some)
}
fn next_value_seed<V: de::DeserializeSeed<'de>>(
&mut self,
seed: V,
) -> Result<V::Value, Error> {
let v = self.pending.take().ok_or_else(|| {
de::Error::custom("value 缺失:需先调用 next_key_seed")
})?;
seed.deserialize(ValueDeserializer(v))
}
}
struct KeyDeserializer<'a>(&'a str);
macro_rules! key_delegate {
($($m:ident),* $(,)?) => {
$(
fn $m<V>(self, visitor: V) -> Result<V::Value, Error>
where V: Visitor<'de> {
self.deserialize_any(visitor)
}
)*
};
}
impl<'de, 'a> Deserializer<'de> for KeyDeserializer<'a> {
type Error = Error;
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
visitor.visit_str(self.0)
}
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
visitor.visit_str(self.0)
}
fn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
visitor.visit_str(self.0)
}
fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
visitor.visit_str(self.0)
}
fn deserialize_enum<V>(
self,
_name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
visitor.visit_enum(EnumDeserializer {
variant: self.0.to_string(),
kind: EnumKind::Unit,
})
}
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
visitor.visit_some(self)
}
fn deserialize_unit_struct<V>(
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
self.deserialize_unit(visitor)
}
fn deserialize_newtype_struct<V>(
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
self.deserialize_any(visitor)
}
fn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
self.deserialize_seq(visitor)
}
fn deserialize_tuple_struct<V>(
self,
_name: &'static str,
_len: usize,
visitor: V,
) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
self.deserialize_seq(visitor)
}
fn deserialize_struct<V>(
self,
_name: &'static str,
_fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
self.deserialize_map(visitor)
}
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
self.deserialize_any(visitor)
}
key_delegate! {
deserialize_bool, deserialize_i8, deserialize_i16, deserialize_i32,
deserialize_i64, deserialize_u8, deserialize_u16, deserialize_u32,
deserialize_u64, deserialize_f32, deserialize_f64, deserialize_char,
deserialize_bytes, deserialize_byte_buf, deserialize_unit,
deserialize_seq, deserialize_map,
}
}
#[derive(Debug)]
enum EnumKind {
Unit,
Newtype(Value),
Tuple(Vec<Value>),
Struct(BTreeMap<String, Value>),
}
struct EnumDeserializer {
variant: String,
kind: EnumKind,
}
impl<'de> de::EnumAccess<'de> for EnumDeserializer {
type Error = Error;
type Variant = VariantAccess;
fn variant_seed<V: de::DeserializeSeed<'de>>(
self,
seed: V,
) -> Result<(V::Value, Self::Variant), Error> {
let variant = seed.deserialize(KeyDeserializer(&self.variant))?;
Ok((variant, VariantAccess { kind: self.kind }))
}
}
struct VariantAccess {
kind: EnumKind,
}
impl<'de> de::VariantAccess<'de> for VariantAccess {
type Error = Error;
fn unit_variant(self) -> Result<(), Error> {
match self.kind {
EnumKind::Unit => Ok(()),
_ => Err(de::Error::custom("该变体携带数据,不能按单元变体解析")),
}
}
fn newtype_variant_seed<T: de::DeserializeSeed<'de>>(
self,
seed: T,
) -> Result<T::Value, Error> {
match self.kind {
EnumKind::Newtype(v) => seed.deserialize(ValueDeserializer(v)),
EnumKind::Tuple(items) => {
seed.deserialize(ValueDeserializer(Value::Array(items)))
}
_ => Err(de::Error::custom("该变体没有单值数据")),
}
}
fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
match self.kind {
EnumKind::Tuple(items) => {
visitor.visit_seq(SeqDeserializer { items, idx: 0 })
}
_ => Err(de::Error::custom("该变体不是元组形态")),
}
}
fn struct_variant<V>(
self,
_fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Error>
where
V: Visitor<'de>,
{
match self.kind {
EnumKind::Struct(m) => {
visitor.visit_map(MapDeserializer { map: m, pending: None })
}
_ => Err(de::Error::custom("该变体不是结构体形态")),
}
}
}
}
pub trait SmlSerialize {
fn to_sml_value(&self) -> Value;
fn to_sml(&self) -> String {
crate::to_sml(&self.to_sml_value())
}
}
pub trait SmlDeserialize: Sized {
fn from_sml_value(v: &Value) -> Result<Self, String>;
fn from_sml(text: &str) -> Result<Self, String> {
let v = crate::parse(text).map_err(|e| format!("SML 解析失败: {e}"))?;
Self::from_sml_value(&v)
}
}
#[cfg(feature = "derive")]
pub use swsml_derive::{SmlDeserialize, SmlSerialize};
pub fn to_string<T: SmlSerialize + ?Sized>(value: &T) -> String {
crate::to_sml(&value.to_sml_value())
}
pub fn from_str<T: SmlDeserialize>(text: &str) -> Result<T, String> {
T::from_sml(text)
}
#[doc(hidden)]
pub mod __private {
use super::{SmlDeserialize, SmlSerialize, Value};
use std::collections::{BTreeMap, HashMap};
pub fn describe_value(v: &Value) -> String {
match v {
Value::Null => "null".to_string(),
Value::Bool(b) => b.to_string(),
Value::Int(i) => i.to_string(),
Value::Float(f) => f.to_string(),
Value::Str(s) => format!("字符串 `{s}`"),
Value::Array(a) => format!("数组({} 个元素)", a.len()),
Value::Object(o) => format!("块({} 个键)", o.len()),
}
}
pub fn take_value(m: &BTreeMap<String, Value>) -> Result<Value, String> {
m.get("_value")
.cloned()
.ok_or_else(|| "缺少 _value 键".to_string())
}
pub fn take_array(m: &BTreeMap<String, Value>) -> Result<Vec<Value>, String> {
match m.get("_value") {
Some(Value::Array(a)) => Ok(a.clone()),
Some(other) => Err(format!("_value 期望数组,实际为 {}", describe_value(other))),
None => Err("缺少 _value 键".to_string()),
}
}
pub fn flatten_from<T: SmlDeserialize>(m: &BTreeMap<String, Value>) -> Result<T, String> {
T::from_sml_value(&Value::Object(m.clone()))
}
impl SmlSerialize for bool {
#[inline]
fn to_sml_value(&self) -> Value {
Value::Bool(*self)
}
}
impl SmlDeserialize for bool {
#[inline]
fn from_sml_value(v: &Value) -> Result<Self, String> {
match v {
Value::Bool(b) => Ok(*b),
other => Err(format!("期望布尔,实际为 {}", describe_value(other))),
}
}
}
macro_rules! impl_int {
($($t:ty),* $(,)?) => {$(
impl SmlSerialize for $t {
#[inline]
fn to_sml_value(&self) -> Value { Value::Int(*self as i64) }
}
impl SmlDeserialize for $t {
#[inline]
fn from_sml_value(v: &Value) -> Result<Self, String> {
match v {
Value::Int(i) => <$t>::try_from(*i)
.map_err(|_| format!("整数 {i} 超出 {} 范围", stringify!($t))),
Value::Float(f)
if f.fract() == 0.0
&& *f >= <$t>::MIN as f64
&& *f <= <$t>::MAX as f64 => Ok(*f as $t),
Value::Float(f) => Err(format!("期望整数,实际为小数 {f}")),
other => Err(format!("期望整数,实际为 {}", describe_value(other))),
}
}
}
)*};
}
impl_int!(i8, i16, i32, i64, isize, u8, u16, u32, usize);
impl SmlSerialize for u64 {
#[inline]
fn to_sml_value(&self) -> Value {
i64::try_from(*self).map(Value::Int).unwrap_or_else(|_| Value::Float(*self as f64))
}
}
impl SmlDeserialize for u64 {
#[inline]
fn from_sml_value(v: &Value) -> Result<Self, String> {
match v {
Value::Int(i) => u64::try_from(*i).map_err(|_| format!("整数 {i} 为负数,超出 u64 范围")),
Value::Float(f) if f.fract() == 0.0 && *f >= 0.0 => Ok(*f as u64),
Value::Float(f) => Err(format!("期望非负整数,实际为 {f}")),
other => Err(format!("期望整数,实际为 {}", describe_value(other))),
}
}
}
macro_rules! impl_big {
($($t:ty),* $(,)?) => {$(
impl SmlSerialize for $t {
#[inline]
fn to_sml_value(&self) -> Value {
i64::try_from(*self).map(Value::Int).unwrap_or_else(|_| Value::Float(*self as f64))
}
}
impl SmlDeserialize for $t {
#[inline]
fn from_sml_value(v: &Value) -> Result<Self, String> {
match v {
Value::Int(i) => Ok(*i as $t),
Value::Float(f) if f.fract() == 0.0 => Ok(*f as $t),
Value::Float(f) => Err(format!("期望整数,实际为小数 {f}")),
other => Err(format!("期望整数,实际为 {}", describe_value(other))),
}
}
}
)*};
}
impl_big!(i128, u128);
macro_rules! impl_float {
($($t:ty),* $(,)?) => {$(
impl SmlSerialize for $t {
#[inline]
fn to_sml_value(&self) -> Value { Value::Float(*self as f64) }
}
impl SmlDeserialize for $t {
#[inline]
fn from_sml_value(v: &Value) -> Result<Self, String> {
match v {
Value::Int(i) => Ok(*i as $t),
Value::Float(f) => Ok(*f as $t),
other => Err(format!("期望数字,实际为 {}", describe_value(other))),
}
}
}
)*};
}
impl_float!(f32, f64);
impl SmlSerialize for char {
#[inline]
fn to_sml_value(&self) -> Value {
Value::Str(self.to_string())
}
}
impl SmlDeserialize for char {
#[inline]
fn from_sml_value(v: &Value) -> Result<Self, String> {
match v {
Value::Str(s) => {
let mut it = s.chars();
match (it.next(), it.next()) {
(Some(c), None) => Ok(c),
_ => Err(format!("期望单个字符,实际为 `{s}`")),
}
}
other => Err(format!("期望字符串,实际为 {}", describe_value(other))),
}
}
}
impl SmlSerialize for String {
#[inline]
fn to_sml_value(&self) -> Value {
Value::Str(self.clone())
}
}
impl SmlDeserialize for String {
#[inline]
fn from_sml_value(v: &Value) -> Result<Self, String> {
match v {
Value::Str(s) => Ok(s.clone()),
other => Err(format!("期望字符串,实际为 {}", describe_value(other))),
}
}
}
impl SmlSerialize for str {
#[inline]
fn to_sml_value(&self) -> Value {
Value::Str(self.to_string())
}
}
impl SmlSerialize for &str {
#[inline]
fn to_sml_value(&self) -> Value {
Value::Str(self.to_string())
}
}
impl SmlSerialize for () {
#[inline]
fn to_sml_value(&self) -> Value {
Value::Null
}
}
impl SmlDeserialize for () {
#[inline]
fn from_sml_value(v: &Value) -> Result<Self, String> {
match v {
Value::Null => Ok(()),
other => Err(format!("期望 null,实际为 {}", describe_value(other))),
}
}
}
impl SmlSerialize for Value {
#[inline]
fn to_sml_value(&self) -> Value {
self.clone()
}
}
impl SmlDeserialize for Value {
#[inline]
fn from_sml_value(v: &Value) -> Result<Self, String> {
Ok(v.clone())
}
}
impl<T: SmlSerialize> SmlSerialize for Option<T> {
#[inline]
fn to_sml_value(&self) -> Value {
match self {
Some(v) => v.to_sml_value(),
None => Value::Null,
}
}
}
impl<T: SmlDeserialize> SmlDeserialize for Option<T> {
#[inline]
fn from_sml_value(v: &Value) -> Result<Self, String> {
match v {
Value::Null => Ok(None),
other => Ok(Some(T::from_sml_value(other)?)),
}
}
}
impl<T: SmlSerialize> SmlSerialize for Vec<T> {
#[inline]
fn to_sml_value(&self) -> Value {
Value::Array(self.iter().map(SmlSerialize::to_sml_value).collect())
}
}
impl<T: SmlDeserialize> SmlDeserialize for Vec<T> {
#[inline]
fn from_sml_value(v: &Value) -> Result<Self, String> {
match v {
Value::Array(a) => a.iter().map(SmlDeserialize::from_sml_value).collect(),
other => Err(format!("期望数组,实际为 {}", describe_value(other))),
}
}
}
impl<T: SmlSerialize> SmlSerialize for Box<T> {
#[inline]
fn to_sml_value(&self) -> Value {
(**self).to_sml_value()
}
}
impl<T: SmlDeserialize> SmlDeserialize for Box<T> {
#[inline]
fn from_sml_value(v: &Value) -> Result<Self, String> {
Ok(Box::new(T::from_sml_value(v)?))
}
}
impl<V: SmlSerialize> SmlSerialize for BTreeMap<String, V> {
#[inline]
fn to_sml_value(&self) -> Value {
Value::Object(
self.iter()
.map(|(k, v)| (k.clone(), v.to_sml_value()))
.collect(),
)
}
}
impl<V: SmlDeserialize> SmlDeserialize for BTreeMap<String, V> {
#[inline]
fn from_sml_value(v: &Value) -> Result<Self, String> {
match v {
Value::Object(m) => {
let mut out = BTreeMap::new();
for (k, val) in m {
out.insert(k.clone(), V::from_sml_value(val)?);
}
Ok(out)
}
other => Err(format!("期望块(object),实际为 {}", describe_value(other))),
}
}
}
impl<V: SmlSerialize> SmlSerialize for HashMap<String, V> {
#[inline]
fn to_sml_value(&self) -> Value {
Value::Object(
self.iter()
.map(|(k, v)| (k.clone(), v.to_sml_value()))
.collect(),
)
}
}
impl<V: SmlDeserialize> SmlDeserialize for HashMap<String, V> {
#[inline]
fn from_sml_value(v: &Value) -> Result<Self, String> {
match v {
Value::Object(m) => {
let mut out = HashMap::new();
for (k, val) in m {
out.insert(k.clone(), V::from_sml_value(val)?);
}
Ok(out)
}
other => Err(format!("期望块(object),实际为 {}", describe_value(other))),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn version_defaults_to_v1_when_absent() {
let (v, ver) = parse_versioned("a: 1\n").unwrap();
assert_eq!(ver, Version::V1);
assert_eq!(v.get("a"), Some(&Value::Int(1)));
}
#[test]
fn version_declared_as_v1() {
let (v, ver) = parse_versioned("@version v1\na: 1\n").unwrap();
assert_eq!(ver, Version::V1);
assert_eq!(v.get("a"), Some(&Value::Int(1)));
}
#[test]
fn version_declaration_is_stripped_not_parsed_as_content() {
let v = parse("@version v1\na: 1\n").unwrap();
assert_eq!(v.get("a"), Some(&Value::Int(1)));
assert!(v.get("version").is_none(), "@version 不应进入数据");
}
#[test]
fn unsupported_version_is_rejected() {
let err = parse_versioned("@version v99\na: 1\n").unwrap_err();
assert!(err.contains("不支持"), "应拒绝不支持的版本,got: {err}");
assert!(err.contains("v99"), "错误应含版本号,got: {err}");
}
#[test]
fn conflicting_version_is_rejected() {
let err = parse_versioned("@version v1\n@version v2\n").unwrap_err();
assert!(!err.is_empty());
let (_, ver) = parse_versioned("@version v1\n@version v1\n").unwrap();
assert_eq!(ver, Version::V1, "重复但一致的声明应被接受");
}
#[test]
fn version_is_reserved_as_fragment_name() {
let err = parse("@version { x: 1 }\n").unwrap_err();
assert!(err.contains("保留") || err.contains("版本声明"), "got: {err}");
}
#[test]
fn version_works_with_include() {
let d = tmpdir("version");
std::fs::write(d.join("p.sml"), "@version v1\nb: 2\n").unwrap();
std::fs::write(d.join("main.sml"), "@version v1\ninclude \"p.sml\"\n").unwrap();
let (v, ver) = parse_file_versioned(d.join("main.sml")).unwrap();
assert_eq!(ver, Version::V1);
assert_eq!(v.get("b"), Some(&Value::Int(2)), "版本与 include 应协同");
let _ = std::fs::remove_dir_all(&d);
}
#[test]
fn version_display_matches_name() {
assert_eq!(Version::V1.name(), "v1");
assert_eq!(format!("{}", Version::V1), "v1");
}
fn tmpdir(tag: &str) -> std::path::PathBuf {
let mut d = std::env::temp_dir();
d.push(format!("sml_test_{tag}_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&d);
std::fs::create_dir_all(&d).expect("create tmpdir");
d
}
#[test]
fn include_inlines_external_file() {
let d = tmpdir("inline");
std::fs::write(d.join("part.sml"), "port: 8080\n").unwrap();
std::fs::write(d.join("main.sml"), "@version v1\nhost: local\ninclude \"part.sml\"\n").unwrap();
let v = parse_file(d.join("main.sml")).unwrap();
assert_eq!(v.get("host").unwrap().as_str(), Some("local"));
assert_eq!(v.get("port"), Some(&Value::Int(8080)));
let _ = std::fs::remove_dir_all(&d);
}
#[test]
fn include_at_prefix_is_equivalent() {
let d = tmpdir("at");
std::fs::write(d.join("p.sml"), "b: 2\n").unwrap();
std::fs::write(d.join("m.sml"), "@include \"p.sml\"\n").unwrap();
let v = parse_file(d.join("m.sml")).unwrap();
assert_eq!(v.get("b"), Some(&Value::Int(2)));
let _ = std::fs::remove_dir_all(&d);
}
#[test]
fn include_resolves_relative_to_including_file() {
let d = tmpdir("nested");
std::fs::create_dir_all(d.join("sub")).unwrap();
std::fs::write(d.join("sub/leaf.sml"), "@version v1\nleaf: yes\n").unwrap();
std::fs::write(d.join("sub/mid2.sml"), "@version v1\ninclude \"leaf.sml\"\n").unwrap();
std::fs::write(d.join("main.sml"), "@version v1\ninclude \"sub/mid2.sml\"\n").unwrap();
let v = parse_file(d.join("main.sml")).unwrap();
assert_eq!(
v.get("leaf").unwrap().as_str(),
Some("yes"),
"嵌套 include 的路径应相对各自所在目录解析"
);
let _ = std::fs::remove_dir_all(&d);
}
#[test]
fn include_inside_block_injects_fields() {
let d = tmpdir("block");
std::fs::write(d.join("fields.sml"), "@version v1\nregion: cn-north-1\nzone: a\n").unwrap();
std::fs::write(d.join("main.sml"), "@version v1\nserver web {\ninclude \"fields.sml\"\nport: 8080\n}\n").unwrap();
let v = parse_file(d.join("main.sml")).unwrap();
let server = v.get("server").expect("应有 server 块");
assert_eq!(server.get("region").unwrap().as_str(), Some("cn-north-1"));
assert_eq!(server.get("zone").unwrap().as_str(), Some("a"));
assert_eq!(server.get("port"), Some(&Value::Int(8080)));
let _ = std::fs::remove_dir_all(&d);
}
#[test]
fn include_detects_cycles() {
let d = tmpdir("cycle");
std::fs::write(d.join("a.sml"), "include \"b.sml\"\n").unwrap();
std::fs::write(d.join("b.sml"), "include \"a.sml\"\n").unwrap();
let err = parse_file(d.join("a.sml")).unwrap_err();
assert!(err.contains("循环引用"), "应报循环引用,got: {err}");
let _ = std::fs::remove_dir_all(&d);
}
#[test]
fn include_missing_file_is_error() {
let d = tmpdir("missing");
std::fs::write(d.join("m.sml"), "include \"nope.sml\"\n").unwrap();
let err = parse_file(d.join("m.sml")).unwrap_err();
assert!(err.contains("nope.sml"), "错误应含缺失文件名,got: {err}");
let _ = std::fs::remove_dir_all(&d);
}
#[test]
fn hash_in_quoted_string_is_not_a_comment() {
assert_eq!(strip_line_comment("k: \"a#b\""), "k: \"a#b\"");
assert_eq!(strip_line_comment("k: v # comment"), "k: v ");
}
#[test]
fn glob_include_requires_feature() {
let d = tmpdir("globoff");
std::fs::write(d.join("a.sml"), "x: 1\n").unwrap();
std::fs::write(d.join("main.sml"), "@version v1\ninclude \"*.sml\"\n").unwrap();
let err = parse_file(d.join("main.sml")).unwrap_err();
assert!(err.contains("glob-include"), "应要求 glob-include,got: {err}");
let _ = std::fs::remove_dir_all(&d);
}
#[test]
fn glob_include_expands_multiple_files() {
let d = tmpdir("glob");
std::fs::create_dir_all(d.join("lib")).unwrap();
std::fs::write(d.join("lib/a.sml"), "@version v1\nx: 1\n").unwrap();
std::fs::write(d.join("lib/b.sml"), "@version v1\ny: 2\n").unwrap();
std::fs::write(d.join("note.txt"), "ignored\n").unwrap();
std::fs::write(d.join("main.sml"), "@version v1\n@feature enable glob-include\ninclude \"lib/*.sml\"\n").unwrap();
let v = parse_file(d.join("main.sml")).unwrap();
assert_eq!(v.get("x"), Some(&Value::Int(1)));
assert_eq!(v.get("y"), Some(&Value::Int(2)));
let _ = std::fs::remove_dir_all(&d);
}
#[test]
fn regex_include_requires_feature() {
let d = tmpdir("regexoff");
std::fs::write(d.join("a.sml"), "x: 1\n").unwrap();
std::fs::write(d.join("main.sml"), "@version v1\ninclude \"re:.*\\.sml\"\n").unwrap();
let err = parse_file(d.join("main.sml")).unwrap_err();
assert!(err.contains("regex-include"), "应要求 regex-include,got: {err}");
let _ = std::fs::remove_dir_all(&d);
}
#[test]
fn regex_include_matches_files() {
let d = tmpdir("regex");
std::fs::write(d.join("widget_a.sml"), "@version v1\nx: 1\n").unwrap();
std::fs::write(d.join("widget_b.sml"), "@version v1\ny: 2\n").unwrap();
std::fs::write(d.join("other.sml"), "@version v1\nz: 3\n").unwrap();
std::fs::write(
d.join("main.sml"),
"@version v1\n@feature enable regex-include\ninclude \"re:widget_.*\\.sml\"\n",
)
.unwrap();
let v = parse_file(d.join("main.sml")).unwrap();
assert_eq!(v.get("x"), Some(&Value::Int(1)));
assert_eq!(v.get("y"), Some(&Value::Int(2)));
assert_eq!(v.get("z"), None, "other.sml 不应被正则匹配");
let _ = std::fs::remove_dir_all(&d);
}
#[test]
fn ext_rewrite_allows_non_sml() {
let d = tmpdir("exrew");
std::fs::write(d.join("conf.smlc"), "@version v1\nx: 9\n").unwrap();
std::fs::write(
d.join("main.sml"),
"@version v1\n@feature enable ext-rewrite\ninclude \"conf.smlc\"\n",
)
.unwrap();
let v = parse_file(d.join("main.sml")).unwrap();
assert_eq!(v.get("x"), Some(&Value::Int(9)));
let _ = std::fs::remove_dir_all(&d);
}
#[test]
fn include_line_is_not_confused_with_key_named_include() {
let f = FeatureSet::baseline();
assert_eq!(parse_include_line("key: include", f), Ok(None));
assert_eq!(
parse_include_line("include \"a.sml\"", f),
Ok(Some(vec![IncludeTarget { raw: "a.sml".into(), namespace: None, via_import: false }]))
);
assert_eq!(
parse_include_line("@include \"a.sml\"", f),
Ok(Some(vec![IncludeTarget { raw: "a.sml".into(), namespace: None, via_import: false }]))
);
assert_eq!(
parse_include_line("include \"a.sml\" as ui.form", f),
Ok(Some(vec![IncludeTarget { raw: "a.sml".into(), namespace: Some("ui.form".into()), via_import: false }]))
);
assert_eq!(
parse_include_line("include \"widgets\"", f),
Ok(Some(vec![IncludeTarget { raw: "widgets".into(), namespace: Some("widgets".into()), via_import: false }]))
);
assert_eq!(
parse_include_line("import ui.buttons", f),
Ok(Some(vec![IncludeTarget { raw: "ui.buttons".into(), namespace: Some("ui.buttons".into()), via_import: true }]))
);
let fm = FeatureSet::all();
assert_eq!(
parse_include_line("include \"a.sml\", \"b\" as y", fm),
Ok(Some(vec![
IncludeTarget { raw: "a.sml".into(), namespace: None, via_import: false },
IncludeTarget { raw: "b".into(), namespace: Some("y".into()), via_import: false },
]))
);
assert_eq!(parse_include_line("# include \"a.sml\"", f), Ok(None));
}
#[test]
fn email_in_bare_word_survives() {
let v = parse("to: a@b.c\nfrom: \"sal <sal@mail.swebase.cn>\"\n").unwrap();
assert_eq!(v.get("to").unwrap().as_str(), Some("a@b.c"), "got: {v:?}");
assert_eq!(
v.get("from").unwrap().as_str(),
Some("sal <sal@mail.swebase.cn>"),
"got: {v:?}"
);
}
#[test]
fn email_roundtrips_through_to_sml() {
let v = Value::Object(BTreeMap::from([(
"to".to_string(),
Value::Str("SALflake@qq.com".into()),
)]));
let back = parse(&to_sml(&v)).unwrap();
assert_eq!(back, v, "邮箱必须能往返,got:\n{}", to_sml(&v));
}
#[test]
fn fragment_definition_still_works() {
let v = parse("@base { region: cn }\nregion: &base\n").unwrap();
assert_eq!(
v.get("region").unwrap().get("region").unwrap().as_str(),
Some("cn"),
"片段引用应展开为定义的内容,got: {v:?}"
);
}
#[test]
fn toplevel_array_roundtrips() {
let v = Value::Array(vec![
Value::Object(BTreeMap::from([
("ts".to_string(), Value::Str("2026-01-01".into())),
("to".to_string(), Value::Str("a@b.c".into())),
])),
Value::Object(BTreeMap::from([
("ts".to_string(), Value::Str("2026-01-02".into())),
("to".to_string(), Value::Str("x@y.z".into())),
])),
]);
let text = to_sml(&v);
let back = parse(&text).unwrap();
assert_eq!(back, v, "顶层对象数组必须能往返,got text:\n{text}");
}
#[test]
fn toplevel_array_of_scalars_roundtrips() {
let v = Value::Array(vec![
Value::Int(1),
Value::Str("two".into()),
Value::Bool(true),
]);
let back = parse(&to_sml(&v)).unwrap();
assert_eq!(back, v, "顶层标量数组必须能往返");
}
#[test]
fn toplevel_object_block_roundtrips() {
let mut m = BTreeMap::new();
m.insert("k".to_string(), Value::Int(1));
let v = Value::Object(m);
let back = parse(&to_sml(&v)).unwrap();
assert_eq!(back, v, "顶层对象块必须能往返");
}
#[test]
fn toplevel_empty_array_roundtrips() {
let v = Value::Array(vec![]);
let back = parse(&to_sml(&v)).unwrap();
assert_eq!(back, v, "空数组必须能往返");
}
#[cfg(feature = "serde")]
#[test]
fn serde_roundtrip_preserves_shape() {
let v = parse("name: John\nage: 27\ntags: [a b]\nnested { k: v }\n").unwrap();
let json = serde_json::to_string(&v).unwrap();
assert!(json.contains("\"name\":\"John\""), "got: {json}");
assert!(json.contains("\"age\":27"), "got: {json}");
assert!(json.contains("\"tags\":[\"a\",\"b\"]"), "got: {json}");
assert!(json.contains("\"nested\":{\"k\":\"v\"}"), "got: {json}");
let back: Value = serde_json::from_str(&json).unwrap();
assert_eq!(back, v, "serde 往返应还原原值");
}
#[cfg(feature = "serde")]
#[test]
fn serde_deserializes_json_into_value() {
let v: Value = serde_json::from_str(r#"{"s":"x","i":5,"f":1.5,"b":true,"n":null,"a":[1,2]}"#).unwrap();
assert_eq!(v.get("s").unwrap().as_str(), Some("x"));
assert_eq!(v.get("i"), Some(&Value::Int(5)));
assert_eq!(v.get("f"), Some(&Value::Float(1.5)));
assert_eq!(v.get("b"), Some(&Value::Bool(true)));
assert_eq!(v.get("n"), Some(&Value::Null));
assert!(matches!(v.get("a"), Some(Value::Array(a)) if a.len() == 2));
}
#[test]
fn nested_array_inside_object_inside_array_survives_roundtrip() {
let mut item = BTreeMap::new();
item.insert("path".to_string(), Value::Str("a.txt".into()));
item.insert(
"chunks".to_string(),
Value::Array(vec![
Value::Str("c1".into()),
Value::Str("c2".into()),
]),
);
let mut root = BTreeMap::new();
root.insert(
"entries".to_string(),
Value::Array(vec![Value::Object(item)]),
);
let text = to_sml(&Value::Object(root));
assert!(!text.contains("[..]"), "嵌套数组不得被缩略: {text}");
let back = parse(&text).unwrap();
let chunks = back.get("entries").and_then(|e| match e {
Value::Array(a) => a.first(),
_ => None,
});
let chunks = match chunks {
Some(Value::Object(m)) => m.get("chunks"),
_ => None,
};
match chunks {
Some(Value::Array(a)) => {
assert_eq!(a.len(), 2, "两个块都应保留: {text}");
assert_eq!(
a.iter().filter_map(|c| c.as_str()).collect::<Vec<_>>(),
vec!["c1", "c2"]
);
}
other => panic!("chunks 应解析为数组,实际 {other:?}"),
}
}
#[test]
fn utf8_in_quoted_string_survives_roundtrip() {
let v = parse(r#"note: "修复若干问题""#).unwrap();
assert_eq!(
v.get("note").and_then(|x| x.as_str()),
Some("修复若干问题"),
"引号串中的中文不应被破坏"
);
let v2 = parse("region: 华北").unwrap();
assert_eq!(v2.get("region").and_then(|x| x.as_str()), Some("华北"));
let v3 = parse(r#"k: "\u{4fee}\u{590d}""#).unwrap();
assert_eq!(v3.get("k").and_then(|x| x.as_str()), Some("修复"));
}
#[test]
fn parse_basic() {
let text = "firstName: John\nage: 27\nisAlive: true\nspouse: null\n";
let v = parse(text).unwrap();
assert_eq!(v.get("firstName"), Some(&Value::Str("John".into())));
assert_eq!(v.get("age"), Some(&Value::Int(27)));
assert_eq!(v.get("isAlive"), Some(&Value::Bool(true)));
assert_eq!(v.get("spouse"), Some(&Value::Null));
}
#[test]
fn parse_nested() {
let text = "address:\n{\n streetAddress: \"21 2nd Street\"\n state: NY\n}\n";
let v = parse(text).unwrap();
assert_eq!(
v.get("address.streetAddress"),
Some(&Value::Str("21 2nd Street".into()))
);
assert_eq!(v.get("address.state"), Some(&Value::Str("NY".into())));
}
#[test]
fn parse_array() {
let text = "phoneNumbers:\n[\n { type: home }\n { type: office }\n]\n";
let v = parse(text).unwrap();
if let Some(Value::Array(a)) = v.get("phoneNumbers") {
assert_eq!(a.len(), 2);
assert_eq!(a[0].get("type"), Some(&Value::Str("home".into())));
} else {
panic!("not array");
}
}
#[test]
fn parse_fragment() {
let text = "@base { region: cn-north-1 }\nserver web { &base }\n";
let v = parse(text).unwrap();
assert_eq!(
v.get("server.&base.region"),
Some(&Value::Str("cn-north-1".into()))
);
assert_eq!(v.get("server.__type"), Some(&Value::Str("server".into())));
assert_eq!(v.get("server.__name"), Some(&Value::Str("web".into())));
}
#[test]
fn roundtrip() {
let text = "name: myapp\nport: 8080\nflags: [ a b c ]\n";
let v = parse(text).unwrap();
let out = to_sml(&v);
let v2 = parse(&out).unwrap();
assert_eq!(v, v2);
}
#[test]
fn env_inline() {
unsafe { std::env::set_var("SML_TEST_VAR", "hello") };
let text = "greeting: $env.SML_TEST_VAR\n";
let v = parse(text).unwrap();
assert_eq!(v.get("greeting"), Some(&Value::Str("hello".into())));
}
#[test]
fn c_abi_json_bridge() {
let text = "name: John\nage: 27\n";
let v = parse(text).unwrap();
let j = jsonify(&v);
assert!(j.contains("\"name\":\"John\""));
let back = json_to_value(&j).unwrap();
assert_eq!(back, v);
}
}
#[cfg(test)]
mod feature {
use super::*;
#[test]
fn feature_unknown_name_errors() {
let r = parse("@feature enable nope\nx: 1\n");
assert!(r.is_err());
assert!(r.unwrap_err().contains("未知特性"));
}
#[test]
fn feature_whitelist_narrows() {
let v = match parse("@feature whitelist bareword-string,include\nx: John\n").unwrap() {
Value::Object(m) => m,
_ => panic!("应为对象"),
};
assert_eq!(v.get("x"), Some(&Value::Str("John".into())));
}
#[test]
fn feature_blacklist_removes() {
let r = parse("@feature blacklist bareword-string\nx: John\n");
assert!(r.is_err());
assert!(r.unwrap_err().contains("字符串必须加引号"));
}
#[test]
fn feature_mode_whitelist_enable() {
let r = parse("@feature mode whitelist\n@feature enable fragment\nx: &frag\n");
assert!(r.is_ok());
}
#[test]
fn caller_allowed_intersection_empty_errors() {
let allowed = FeatureSet::none().with(Feature::Env);
let r = parse_with_features(
"@feature mode whitelist\n@feature enable contract\nx: 1\n",
allowed,
);
assert!(r.is_err());
}
#[test]
fn caller_allowed_subset_ok() {
let allowed = FeatureSet::all();
let (v, eff) = parse_with_features(
"@feature whitelist bareword-string,include\nx: John\n",
allowed,
)
.unwrap();
assert!(eff.has(Feature::BarewordStr));
assert!(eff.has(Feature::Include));
assert!(!eff.has(Feature::Env));
assert_eq!(v.get("x"), Some(&Value::Str("John".into())));
}
#[test]
fn feature_namespace_include() {
let dir = std::env::temp_dir().join("sml_feat_ns_test");
let _ = std::fs::create_dir_all(&dir);
let sub = dir.join("sub.sml");
let main = dir.join("main.sml");
std::fs::write(&sub, "a: 1\nb: 2\n").unwrap();
let rel = format!("include \"sub.sml\" as pkg\n");
std::fs::write(&main, &rel).unwrap();
let v = match parse_file(&main) {
Ok(v) => v,
Err(e) => {
let _ = std::fs::remove_dir_all(&dir);
panic!("parse_file 失败: {e}");
}
};
let _ = std::fs::remove_dir_all(&dir);
let pkg = match v.get("pkg") {
Some(Value::Object(m)) => m.clone(),
_ => panic!("pkg 应为对象"),
};
assert_eq!(pkg.get("a"), Some(&Value::Int(1)));
assert_eq!(pkg.get("b"), Some(&Value::Int(2)));
}
#[test]
fn version_v3_disables_bareword() {
let r = parse("@version v3\nname: John\n");
assert!(r.is_err());
let v = parse("@version v3\nname: \"John\"\nage: 27\n").unwrap();
assert_eq!(v.get("name"), Some(&Value::Str("John".into())));
assert_eq!(v.get("age"), Some(&Value::Int(27)));
}
#[test]
fn feature_base_derives_strict() {
let r = parse("@feature base v3\nname: John\n");
assert!(r.is_err());
}
}