1mod i18n;
27mod nfa;
28pub mod regex;
29
30use std::collections::BTreeMap;
31
32pub use i18n::{BILINGUAL, CHINESE, Concept, ENGLISH, KeywordTable, StaticTable};
33use i18n::Concept::*;
34
35use sml_value::Value;
36
37#[derive(Debug, Clone, PartialEq)]
41pub enum Class {
42 Digit,
43 Alpha,
44 Space,
45 Word,
46 Any,
47 One(char),
49 Range(char, char),
51 Set(Vec<Class>),
53 Not(Box<Class>),
55}
56
57impl Class {
58 pub fn matches(&self, ch: char) -> bool {
59 match self {
60 Class::Digit => ch.is_numeric(),
61 Class::Alpha => ch.is_alphabetic(),
62 Class::Space => ch.is_whitespace(),
63 Class::Word => ch.is_alphanumeric() || ch == '_',
64 Class::Any => true,
65 Class::One(c) => ch == *c,
66 Class::Range(a, b) => (*a..=*b).contains(&ch),
67 Class::Set(cs) => cs.iter().any(|c| c.matches(ch)),
68 Class::Not(c) => !c.matches(ch),
69 }
70 }
71}
72
73#[derive(Debug, Clone, PartialEq)]
76pub enum Pat {
77 Class(Class),
78 Lit(String),
79 Seq(Vec<Pat>),
80 Alt(Vec<Pat>),
81 Repeat {
83 pat: Box<Pat>,
84 min: usize,
85 max: Option<usize>,
86 },
87 Named {
89 name: String,
90 pat: Box<Pat>,
91 },
92 Until {
94 pat: Box<Pat>,
95 },
96}
97
98const DEFAULT_BUDGET: u64 = 10_000_000;
104
105pub fn compile_rule(rule: &Value, rules: &BTreeMap<String, Value>) -> Result<Pat, String> {
107 compile_rule_with(rule, rules, &BILINGUAL)
108}
109
110pub fn compile_rule_with(
112 rule: &Value,
113 rules: &BTreeMap<String, Value>,
114 table: &dyn KeywordTable,
115) -> Result<Pat, String> {
116 let mut stack = Vec::new();
117 compile_value(rule, rules, &mut stack, table)
118}
119
120fn compile_value(
121 v: &Value,
122 rules: &BTreeMap<String, Value>,
123 stack: &mut Vec<String>,
124 table: &dyn KeywordTable,
125) -> Result<Pat, String> {
126 match v {
127 Value::Array(items) => {
129 let mut seq = Vec::new();
130 for it in items {
131 seq.push(compile_value(it, rules, stack, table)?);
132 }
133 Ok(Pat::Seq(seq))
134 }
135 Value::Str(s) => Ok(Pat::Lit(s.clone())),
137 Value::Object(map) => compile_elem(map, rules, stack, table),
138 other => Err(format!("sml: 模式元素类型不支持: {}", type_name(other))),
139 }
140}
141
142fn compile_elem(
143 map: &BTreeMap<String, Value>,
144 rules: &BTreeMap<String, Value>,
145 stack: &mut Vec<String>,
146 table: &dyn KeywordTable,
147) -> Result<Pat, String> {
148 let mut lit: Option<&Value> = None;
151 let mut cls: Option<&Value> = None;
152 let mut alt: Option<&Value> = None;
153 let mut grp: Option<&Value> = None;
154 let mut seq: Option<&Value> = None;
155 let mut use_ref: Option<&Value> = None;
156 let mut until: Option<&Value> = None;
157 let mut times: Option<&Value> = None;
158 let mut optional: Option<&Value> = None;
159 let mut name: Option<&Value> = None;
160 let mut regex_src: Option<&Value> = None;
161 let mut flat_min_raw: Option<&Value> = None;
164 let mut flat_max_raw: Option<&Value> = None;
165
166 for (k, v) in map.iter() {
167 match table.lookup(k) {
168 Some(Concept::Lit) => lit = Some(v),
169 Some(Concept::Class) => cls = Some(v),
170 Some(Concept::Alt) => alt = Some(v),
171 Some(Concept::Group) => grp = Some(v),
172 Some(Concept::Seq) => seq = Some(v),
173 Some(Concept::Use) => use_ref = Some(v),
174 Some(Concept::Until) => until = Some(v),
175 Some(Concept::Times) => times = Some(v),
176 Some(Concept::Optional) => optional = Some(v),
177 Some(Concept::Min) => flat_min_raw = Some(v),
178 Some(Concept::Max) => flat_max_raw = Some(v),
179 Some(Concept::Name) => name = Some(v),
180 Some(Concept::Regex) => regex_src = Some(v),
181 _ => {}
183 }
184 }
185
186 let base = if let Some(v) = lit {
188 Pat::Lit(as_str(v, "字面/lit")?)
189 } else if let Some(v) = cls {
190 Pat::Class(parse_class(&as_str(v, "类/class")?, table)?)
191 } else if let Some(v) = alt {
192 let items = as_array(v, "任一/alt")?;
193 let mut alts = Vec::new();
194 for it in items {
195 alts.push(compile_value(it, rules, stack, table)?);
196 }
197 Pat::Alt(alts)
198 } else if let Some(v) = grp {
199 compile_value(v, rules, stack, table)?
200 } else if let Some(v) = seq {
201 compile_value(v, rules, stack, table)?
202 } else if let Some(v) = use_ref {
203 let ref_name = as_str(v, "用/use")?;
204 if stack.contains(&ref_name) {
205 return Err(format!(
206 "sml: 规则 `{ref_name}` 存在循环引用(L1 不支持递归;如需嵌套结构请先升级到 L2)"
207 ));
208 }
209 let target = rules
210 .get(&ref_name)
211 .ok_or_else(|| format!("sml: 未定义的规则 `{ref_name}`"))?;
212 stack.push(ref_name.clone());
213 let p = compile_value(target, rules, stack, table)?;
214 stack.pop();
215 p
216 } else if let Some(v) = until {
217 let u = as_str(v, "直到/until")?;
218 match table.lookup(&u) {
219 Some(Concept::Eol) => Pat::Repeat {
221 pat: Box::new(Pat::Class(Class::Any)),
222 min: 0,
223 max: None,
224 },
225 _ => Pat::Until {
226 pat: Box::new(Pat::Lit(u)),
227 },
228 }
229 } else if let Some(v) = regex_src {
230 let src = as_str(v, "正则/regex")?;
232 crate::regex::parse_regex(&src)
233 .map_err(|e| format!("sml: regex 编译失败:{e}"))?
234 } else {
235 return Err(format!(
236 "sml: 无法识别的模式元素(需 字面/类/任一/组/序列/用/直到/正则 之一),实际键: {:?}",
237 map.keys().collect::<Vec<_>>()
238 ));
239 };
240
241 let flat_min = match flat_min_raw {
244 Some(v) => Some(as_int(v).ok_or_else(|| format!("sml: 量词最小必须为整数,得 {v:?}"))?),
245 None => None,
246 };
247 let flat_max = match flat_max_raw {
248 Some(v) => Some(as_int(v).ok_or_else(|| format!("sml: 量词最大必须为整数,得 {v:?}"))?),
249 None => None,
250 };
251
252 let base = if flat_min.is_some() || flat_max.is_some() {
253 let mn = flat_min.unwrap_or(0);
254 let mx = flat_max;
255 if mn < 0 {
256 return Err(format!("sml: 量词最小不能为负(得 {mn})"));
257 }
258 if let Some(mxv) = mx {
259 if mxv < mn {
260 return Err(format!("sml: 量词最大({mxv})不能小于最小({mn})"));
261 }
262 }
263 Pat::Repeat {
264 pat: Box::new(base),
265 min: mn as usize,
266 max: mx.map(|x| x as usize),
267 }
268 } else if let Some(t) = times {
269 let (min, max) = parse_times(t, table)?;
270 Pat::Repeat {
271 pat: Box::new(base),
272 min,
273 max,
274 }
275 } else if matches!(optional, Some(Value::Bool(true))) {
276 Pat::Repeat {
277 pat: Box::new(base),
278 min: 0,
279 max: Some(1),
280 }
281 } else {
282 base
283 };
284
285 match name {
287 None => Ok(base),
288 Some(n) => Ok(Pat::Named {
289 name: as_str(n, "名/name")?,
290 pat: Box::new(base),
291 }),
292 }
293}
294
295fn parse_class(s: &str, table: &dyn KeywordTable) -> Result<Class, String> {
296 match table.lookup(s) {
297 Some(Concept::Digit) => Ok(Class::Digit),
298 Some(Concept::Alpha) => Ok(Class::Alpha),
299 Some(Concept::Space) => Ok(Class::Space),
300 Some(Concept::Word) => Ok(Class::Word),
301 Some(Concept::Any) => Ok(Class::Any),
302 _ => {
304 let mut chars = s.chars();
305 match (chars.next(), chars.next()) {
306 (Some(c), None) => Ok(Class::One(c)),
307 _ => Err(format!("sml: 未知字符类 `{s}`")),
308 }
309 }
310 }
311}
312
313fn parse_times(v: &Value, table: &dyn KeywordTable) -> Result<(usize, Option<usize>), String> {
314 let (min, max): (i64, Option<i64>) = match v {
315 Value::Int(n) if *n >= 0 => (*n, Some(*n)),
316 Value::Str(s) => match s.as_str() {
317 "+" => (1, None),
318 "*" => (0, None),
319 "?" => (0, Some(1)),
320 other => {
321 if let Some((a, b)) = other.split_once('-') {
322 let a = a
323 .trim()
324 .parse::<i64>()
325 .map_err(|_| format!("sml: 量词 `{other}` 非法"))?;
326 let b = b
327 .trim()
328 .parse::<i64>()
329 .map_err(|_| format!("sml: 量词 `{other}` 非法"))?;
330 (a, Some(b))
331 } else {
332 return Err(format!(
333 "sml: 未知量词 `{other}`(可用 数字 / + / * / ? / a-b)"
334 ));
335 }
336 }
337 },
338 Value::Object(m) => {
339 let mut min = 0i64;
341 let mut max: Option<i64> = None;
342 for (k, v) in m.iter() {
343 match table.lookup(k) {
344 Some(Concept::Min) => {
345 min = as_int(v).ok_or_else(|| format!("sml: 量词最小必须为整数,得 {v:?}"))?
346 }
347 Some(Concept::Max) => {
348 max = Some(
349 as_int(v)
350 .ok_or_else(|| format!("sml: 量词最大必须为整数,得 {v:?}"))?,
351 )
352 }
353 _ => {}
354 }
355 }
356 (min, max)
357 }
358 other => return Err(format!("sml: 量词类型不支持: {}", type_name(other))),
359 };
360 if min < 0 {
362 return Err(format!("sml: 量词最小不能为负(得 {min})"));
363 }
364 if let Some(mx) = max {
365 if mx < min {
366 return Err(format!("sml: 量词最大({mx})不能小于最小({min})"));
367 }
368 }
369 Ok((min as usize, max.map(|x| x as usize)))
370}
371
372pub fn is_match(pat: &Pat, text: &str) -> Result<bool, String> {
378 let prog = nfa::compile(pat);
379 nfa::run(&prog, text, DEFAULT_BUDGET)
380}
381
382pub fn matches(rules: &BTreeMap<String, Value>, name: &str, text: &str) -> Result<bool, String> {
384 matches_with(rules, name, text, &BILINGUAL)
385}
386
387pub fn matches_with(
389 rules: &BTreeMap<String, Value>,
390 name: &str,
391 text: &str,
392 table: &dyn KeywordTable,
393) -> Result<bool, String> {
394 let rule = rules
395 .get(name)
396 .ok_or_else(|| format!("sml: 未定义的规则 `{name}`"))?;
397 let pat = compile_rule_with(rule, rules, table)?;
398 is_match(&pat, text)
399}
400
401fn as_str(v: &Value, field: &str) -> Result<String, String> {
406 match v {
407 Value::Str(s) => Ok(s.clone()),
408 other => Err(format!(
409 "sml: `{field}` 须为字符串,实际 {}",
410 type_name(other)
411 )),
412 }
413}
414
415fn as_array<'a>(v: &'a Value, field: &str) -> Result<&'a Vec<Value>, String> {
416 match v {
417 Value::Array(a) => Ok(a),
418 other => Err(format!("sml: `{field}` 须为数组,实际 {}", type_name(other))),
419 }
420}
421
422fn as_int(v: &Value) -> Option<i64> {
423 match v {
424 Value::Int(n) => Some(*n),
425 _ => None,
426 }
427}
428
429fn type_name(v: &Value) -> &'static str {
430 match v {
431 Value::Str(_) => "字符串",
432 Value::Int(_) => "整数",
433 Value::Float(..) => "浮点数",
434 Value::Bool(_) => "布尔",
435 Value::Null => "null",
436 Value::Array(_) => "数组",
437 Value::Object(_) => "对象",
438 }
439}