1use crate::rule::{AltSpec, CompareOp, Condition, RuleSpec};
6use crate::utility::{modlist, ListMods};
7use crate::{
8 builtins::is_builtin_action, AltBack, AltBackWithMatch, AltCondition, AltConditionWithLexer,
9 AltConditionWithLexerAndMatch, AltConditionWithMatch, AltError, AltErrorWithMatch, AltModifier,
10 AltModifierWithMatch, AltNext, AltNextWithMatch, BudgetCheck, CommentSuffixMatcher,
11 ConfigModifier, ErrorSuffixCallback, ImperativeLexMatcher, LexCheck, LexMatcherCallback,
12 LexMatcherFactory, MapMerge, MatchTokenCallback, ParsePrepare, Tabnas, TextModifier, Value,
13 ValueTransform,
14};
15use indexmap::IndexMap;
16use regex::RegexBuilder;
17use serde_json::{Map, Value as JsonValue};
18use std::collections::HashMap;
19use std::fmt;
20
21const RESERVED_MAP_KEYS: [&str; 3] = ["__proto__", "constructor", "prototype"];
29
30#[derive(Clone)]
31struct AltRefs {
32 conditions: HashMap<String, AltCondition>,
33 match_conditions: HashMap<String, AltConditionWithMatch>,
34 lexer_conditions: HashMap<String, AltConditionWithLexer>,
35 lexer_match_conditions: HashMap<String, AltConditionWithLexerAndMatch>,
36 modifiers: HashMap<String, AltModifier>,
37 match_modifiers: HashMap<String, AltModifierWithMatch>,
38 errors: HashMap<String, AltError>,
39 match_errors: HashMap<String, AltErrorWithMatch>,
40 pushes: HashMap<String, AltNext>,
41 match_pushes: HashMap<String, AltNextWithMatch>,
42 replaces: HashMap<String, AltNext>,
43 match_replaces: HashMap<String, AltNextWithMatch>,
44 backtracks: HashMap<String, AltBack>,
45 match_backtracks: HashMap<String, AltBackWithMatch>,
46 match_tokens: HashMap<String, (MatchTokenCallback, bool)>,
47 value_transforms: HashMap<String, ValueTransform>,
48 text_modifiers: HashMap<String, TextModifier>,
49 lex_checks: HashMap<String, LexCheck>,
50 comment_suffixes: HashMap<String, CommentSuffixMatcher>,
51 match_values: HashMap<String, MatchTokenCallback>,
52 parse_prepares: HashMap<String, ParsePrepare>,
53 budget_checks: HashMap<String, BudgetCheck>,
54 lex_matches: HashMap<String, LexMatcherCallback>,
55 imperative_lex_matches: HashMap<String, ImperativeLexMatcher>,
56 lex_match_factories: HashMap<String, LexMatcherFactory>,
57 error_suffixes: HashMap<String, ErrorSuffixCallback>,
58 config_modifiers: HashMap<String, ConfigModifier>,
59 parser_starts: HashMap<String, crate::ParserStart>,
60 parser_starts_with_instance: HashMap<String, crate::ParserStartWithInstance>,
61 parser_starts_with_context: HashMap<String, crate::ParserStartWithContext>,
62 map_merges: HashMap<String, MapMerge>,
63}
64
65impl AltRefs {
66 fn knows(&self, name: &str) -> bool {
68 self.conditions.contains_key(name)
69 || self.match_conditions.contains_key(name)
70 || self.lexer_conditions.contains_key(name)
71 || self.lexer_match_conditions.contains_key(name)
72 || self.modifiers.contains_key(name)
73 || self.match_modifiers.contains_key(name)
74 || self.errors.contains_key(name)
75 || self.match_errors.contains_key(name)
76 || self.pushes.contains_key(name)
77 || self.match_pushes.contains_key(name)
78 || self.replaces.contains_key(name)
79 || self.match_replaces.contains_key(name)
80 || self.backtracks.contains_key(name)
81 || self.match_backtracks.contains_key(name)
82 || self.match_tokens.contains_key(name)
83 || self.value_transforms.contains_key(name)
84 || self.text_modifiers.contains_key(name)
85 || self.lex_checks.contains_key(name)
86 || self.comment_suffixes.contains_key(name)
87 || self.match_values.contains_key(name)
88 || self.parse_prepares.contains_key(name)
89 || self.budget_checks.contains_key(name)
90 || self.lex_matches.contains_key(name)
91 || self.imperative_lex_matches.contains_key(name)
92 || self.lex_match_factories.contains_key(name)
93 || self.error_suffixes.contains_key(name)
94 || self.config_modifiers.contains_key(name)
95 || self.parser_starts.contains_key(name)
96 || self.parser_starts_with_instance.contains_key(name)
97 || self.parser_starts_with_context.contains_key(name)
98 }
99}
100
101impl From<&Tabnas> for AltRefs {
102 fn from(tabnas: &Tabnas) -> Self {
103 Self {
104 conditions: tabnas.alt_conditions.clone(),
105 match_conditions: tabnas.alt_match_conditions.clone(),
106 lexer_conditions: tabnas.alt_lexer_conditions.clone(),
107 lexer_match_conditions: tabnas.alt_lexer_match_conditions.clone(),
108 modifiers: tabnas.alt_modifiers.clone(),
109 match_modifiers: tabnas.alt_match_modifiers.clone(),
110 errors: tabnas.alt_errors.clone(),
111 match_errors: tabnas.alt_match_errors.clone(),
112 pushes: tabnas.alt_pushes.clone(),
113 match_pushes: tabnas.alt_match_pushes.clone(),
114 replaces: tabnas.alt_replaces.clone(),
115 match_replaces: tabnas.alt_match_replaces.clone(),
116 backtracks: tabnas.alt_backtracks.clone(),
117 match_backtracks: tabnas.alt_match_backtracks.clone(),
118 match_tokens: tabnas.match_token_refs.clone(),
119 value_transforms: tabnas.value_transform_refs.clone(),
120 text_modifiers: tabnas.text_modifier_refs.clone(),
121 lex_checks: tabnas.lex_check_refs.clone(),
122 comment_suffixes: tabnas.comment_suffix_refs.clone(),
123 match_values: tabnas.match_value_refs.clone(),
124 parse_prepares: tabnas.parse_prepare_refs.clone(),
125 budget_checks: tabnas.budget_check_refs.clone(),
126 lex_matches: tabnas.lex_match_refs.clone(),
127 imperative_lex_matches: tabnas.imperative_lex_match_refs.clone(),
128 lex_match_factories: tabnas.lex_match_factory_refs.clone(),
129 error_suffixes: tabnas.error_suffix_refs.clone(),
130 config_modifiers: tabnas.config_modifier_refs.clone(),
131 parser_starts: tabnas.parser_start_refs.clone(),
132 parser_starts_with_instance: tabnas.parser_start_instance_refs.clone(),
133 parser_starts_with_context: tabnas.parser_start_context_refs.clone(),
134 map_merges: tabnas.map_merge_refs.clone(),
135 }
136 }
137}
138
139pub const BUILTIN_SCHEMA_VERSION: u64 = 5;
146
147#[derive(Debug, Clone, PartialEq, Eq)]
148pub struct GrammarError(pub String);
149
150impl fmt::Display for GrammarError {
151 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152 f.write_str(&self.0)
153 }
154}
155
156impl std::error::Error for GrammarError {}
157
158#[derive(Debug, Clone)]
160pub struct GrammarSpec {
161 document: JsonValue,
162 pub clear: bool,
163 pub version: Option<u64>,
164 pub meta: Option<IndexMap<String, JsonValue>>,
165}
166
167#[derive(Debug, Clone, PartialEq, Eq)]
169pub enum GrammarGroups {
170 Csv(String),
171 List(Vec<String>),
172}
173
174impl From<&str> for GrammarGroups {
175 fn from(value: &str) -> Self {
176 Self::Csv(value.into())
177 }
178}
179
180impl From<String> for GrammarGroups {
181 fn from(value: String) -> Self {
182 Self::Csv(value)
183 }
184}
185
186impl From<Vec<String>> for GrammarGroups {
187 fn from(value: Vec<String>) -> Self {
188 Self::List(value)
189 }
190}
191
192impl GrammarGroups {
193 fn normalized(&self) -> Vec<String> {
194 match self {
195 Self::Csv(value) => value.split(',').map(str::to_owned).collect(),
196 Self::List(value) => value.clone(),
197 }
198 .into_iter()
199 .map(|value| value.trim().to_owned())
200 .filter(|value| !value.is_empty())
201 .collect()
202 }
203}
204
205#[derive(Debug, Clone, Default, PartialEq, Eq)]
206pub struct GrammarSettingAlt {
207 pub g: Option<GrammarGroups>,
208}
209
210#[derive(Debug, Clone, Default, PartialEq, Eq)]
211pub struct GrammarSettingRule {
212 pub alt: Option<GrammarSettingAlt>,
213}
214
215#[derive(Debug, Clone, Default, PartialEq, Eq)]
217pub struct GrammarSetting {
218 pub rule: Option<GrammarSettingRule>,
219}
220
221impl GrammarSetting {
222 pub fn groups(groups: impl Into<GrammarGroups>) -> Self {
223 Self {
224 rule: Some(GrammarSettingRule {
225 alt: Some(GrammarSettingAlt {
226 g: Some(groups.into()),
227 }),
228 }),
229 }
230 }
231
232 fn groups_ref(&self) -> Vec<String> {
233 self.rule
234 .as_ref()
235 .and_then(|rule| rule.alt.as_ref())
236 .and_then(|alt| alt.g.as_ref())
237 .map(GrammarGroups::normalized)
238 .unwrap_or_default()
239 }
240}
241
242impl GrammarSpec {
243 pub fn from_json(src: &str) -> Result<Self, GrammarError> {
244 let document: JsonValue = serde_json::from_str(src)
245 .map_err(|error| GrammarError(format!("Grammar: invalid JSON: {error}")))?;
246 Self::from_value(document)
247 }
248
249 pub fn from_slice(src: &[u8]) -> Result<Self, GrammarError> {
250 let document: JsonValue = serde_json::from_slice(src)
251 .map_err(|error| GrammarError(format!("Grammar: invalid JSON: {error}")))?;
252 Self::from_value(document)
253 }
254
255 pub fn from_value(document: JsonValue) -> Result<Self, GrammarError> {
256 let root = object(&document, "document")?;
257 let version = match root.get("v") {
258 None => None,
259 Some(JsonValue::Number(number)) => number.as_u64().filter(|value| *value > 0),
260 Some(_) => None,
261 };
262 if root.contains_key("v") && version.is_none() {
263 return Err(GrammarError(
264 "Grammar: invalid builtin schema version (expected a positive integer)".into(),
265 ));
266 }
267 if version.is_some_and(|value| value > BUILTIN_SCHEMA_VERSION) {
268 return Err(GrammarError(format!(
269 "Grammar: requires builtin schema version {}, but this engine supports up to {}",
270 version.unwrap_or_default(),
271 BUILTIN_SCHEMA_VERSION
272 )));
273 }
274 let meta = root.get("meta").and_then(JsonValue::as_object).map(|map| {
275 map.iter()
276 .map(|(key, value)| (key.clone(), value.clone()))
277 .collect()
278 });
279 Ok(Self {
280 clear: root.get("clear") == Some(&JsonValue::Bool(true)),
281 version,
282 meta,
283 document,
284 })
285 }
286}
287
288impl Tabnas {
289 pub fn grammar(&mut self, grammar: &GrammarSpec) -> Result<&mut Self, GrammarError> {
293 self.grammar_with_setting(grammar, &GrammarSetting::default())
294 }
295
296 pub fn grammar_with_setting(
299 &mut self,
300 grammar: &GrammarSpec,
301 setting: &GrammarSetting,
302 ) -> Result<&mut Self, GrammarError> {
303 let mut staged = self.clone();
304 let mut grammar = grammar.clone();
305 append_setting_groups(&mut grammar.document, &setting.groups_ref())?;
306 staged.install_grammar(&grammar)?;
307 *self = staged;
308 Ok(self)
309 }
310
311 fn install_grammar(&mut self, grammar: &GrammarSpec) -> Result<(), GrammarError> {
312 let root = object(&grammar.document, "document")?;
313 let refs = AltRefs::from(&*self);
314 if self.options.config_modify.is_empty() {
315 self.raw_options = self.options.peek().clone();
319 }
320 if grammar.clear {
321 self.rules.clear();
322 self.options.fixed.tokens.clear();
323 self.raw_options.fixed.tokens.clear();
324 }
325 if let Some(options) = root.get("options") {
326 apply_options(&mut self.raw_options, object(options, "options")?, &refs)?;
327 let mut resolved = self.raw_options.clone();
328 resolved
329 .refresh_configuration()
330 .map_err(|error| GrammarError(format!("Grammar: {error}")))?;
331 *self.options = resolved;
332 self.plugin_options = self.options.plugin.clone();
333 }
334 if let Some(rules) = root.get("rule") {
335 for (name, value) in object(rules, "rule")? {
336 if value.is_null() {
337 self.rules.shift_remove(name);
338 continue;
339 }
340 let value = object(value, &format!("rule.{name}"))?;
341 let mut spec = self
342 .rules
343 .get(name)
344 .cloned()
345 .unwrap_or_else(|| RuleSpec::new(name));
346 if let Some(open) = value.get("open") {
347 apply_alt_list(
348 &mut spec.open,
349 open,
350 &format!("{name}.open"),
351 &mut self.options,
352 &refs,
353 )?;
354 }
355 if let Some(close) = value.get("close") {
356 apply_alt_list(
357 &mut spec.close,
358 close,
359 &format!("{name}.close"),
360 &mut self.options,
361 &refs,
362 )?;
363 }
364 wire_state_actions(self, &mut spec);
365 validate_action_references(self, &spec)?;
366 self.rules.insert(name.clone(), spec);
367 }
368 }
369 self.raw_options.tokens = self.options.tokens.clone();
373 self.emit_debug_config();
374 Ok(())
375 }
376
377 pub fn grammar_json(&mut self, src: &str) -> Result<&mut Self, GrammarError> {
378 let grammar = GrammarSpec::from_json(src)?;
379 self.grammar(&grammar)
380 }
381
382 pub fn grammar_json_with_setting(
383 &mut self,
384 src: &str,
385 setting: &GrammarSetting,
386 ) -> Result<&mut Self, GrammarError> {
387 let grammar = GrammarSpec::from_json(src)?;
388 self.grammar_with_setting(&grammar, setting)
389 }
390}
391
392fn append_setting_groups(document: &mut JsonValue, groups: &[String]) -> Result<(), GrammarError> {
393 if groups.is_empty() {
394 return Ok(());
395 }
396 let Some(rules) = document
397 .as_object_mut()
398 .and_then(|root| root.get_mut("rule"))
399 .and_then(JsonValue::as_object_mut)
400 else {
401 return Ok(());
402 };
403 for rule in rules.values_mut().filter_map(JsonValue::as_object_mut) {
404 for phase in ["open", "close"] {
405 let Some(value) = rule.get_mut(phase) else {
406 continue;
407 };
408 let alts = if value.is_array() {
409 value.as_array_mut()
410 } else {
411 value
412 .as_object_mut()
413 .and_then(|wrapper| wrapper.get_mut("alts"))
414 .and_then(JsonValue::as_array_mut)
415 };
416 let Some(alts) = alts else {
417 continue;
418 };
419 for alt in alts.iter_mut().filter_map(JsonValue::as_object_mut) {
420 let mut existing = match alt.remove("g") {
421 None | Some(JsonValue::Null) => Vec::new(),
422 Some(JsonValue::String(value)) => value.split(',').map(str::to_owned).collect(),
423 Some(JsonValue::Array(values)) => values
424 .into_iter()
425 .filter_map(|value| value.as_str().map(str::to_owned))
426 .collect(),
427 Some(_) => {
428 return Err(GrammarError(
429 "Grammar: alternate g must be a string or array".into(),
430 ));
431 }
432 };
433 existing.extend(groups.iter().cloned());
434 alt.insert(
435 "g".into(),
436 JsonValue::Array(existing.into_iter().map(JsonValue::String).collect()),
437 );
438 }
439 }
440 }
441 Ok(())
442}
443
444fn object<'a>(
445 value: &'a JsonValue,
446 label: &str,
447) -> Result<&'a Map<String, JsonValue>, GrammarError> {
448 value
449 .as_object()
450 .ok_or_else(|| GrammarError(format!("Grammar: {label} must be an object")))
451}
452
453fn apply_alt_list(
454 target: &mut Vec<AltSpec>,
455 value: &JsonValue,
456 label: &str,
457 options: &mut crate::Options,
458 refs: &AltRefs,
459) -> Result<(), GrammarError> {
460 let (alts, inject) = if let Some(array) = value.as_array() {
461 (array, None)
462 } else {
463 let wrapper = object(value, label)?;
464 let alts = wrapper
465 .get("alts")
466 .and_then(JsonValue::as_array)
467 .ok_or_else(|| GrammarError(format!("Grammar: {label}.alts must be an array")))?;
468 (alts, wrapper.get("inject").and_then(JsonValue::as_object))
469 };
470 if inject
471 .and_then(|item| item.get("clear"))
472 .and_then(JsonValue::as_bool)
473 == Some(true)
474 {
475 target.clear();
476 }
477 let mods = inject.map(|inject| ListMods {
478 delete: integer_list(inject.get("delete")),
479 move_items: integer_list(inject.get("move")),
480 custom: None,
481 });
482 *target = modlist(std::mem::take(target), mods.as_ref());
483 let parsed: Result<Vec<_>, _> = alts
484 .iter()
485 .enumerate()
486 .map(|(index, alt)| parse_alt(alt, &format!("{label} alt[{index}]"), options, refs))
487 .collect();
488 let mut parsed = parsed?;
489 if inject
490 .and_then(|item| item.get("append"))
491 .and_then(JsonValue::as_bool)
492 == Some(true)
493 {
494 target.append(&mut parsed);
495 } else {
496 parsed.append(target);
497 *target = parsed;
498 }
499 Ok(())
500}
501
502fn integer_list(value: Option<&JsonValue>) -> Vec<isize> {
503 value
504 .and_then(JsonValue::as_array)
505 .into_iter()
506 .flatten()
507 .filter_map(JsonValue::as_i64)
508 .filter_map(|value| isize::try_from(value).ok())
509 .collect()
510}
511
512fn parse_alt(
513 value: &JsonValue,
514 label: &str,
515 options: &mut crate::Options,
516 refs: &AltRefs,
517) -> Result<AltSpec, GrammarError> {
518 let map = object(value, label)?;
519 let mut alt = AltSpec::default();
520 if let Some(spec) = map.get("s") {
521 let slots: Vec<&str> = match spec {
522 JsonValue::String(sequence) => sequence.split_whitespace().collect(),
523 JsonValue::Array(sequence) => sequence
524 .iter()
525 .map(|slot| {
526 slot.as_str().ok_or_else(|| {
527 GrammarError(format!("Grammar: {label}.s entries must be strings"))
528 })
529 })
530 .collect::<Result<_, _>>()?,
531 _ => {
532 return Err(GrammarError(format!(
533 "Grammar: {label}.s must be a string or array"
534 )))
535 }
536 };
537 for slot in slots {
538 let mut tins = Vec::new();
539 let mut names = Vec::new();
540 for name in slot.split_whitespace() {
541 names.push(name.to_string());
542 if let Some(set) = options.token_set.get(name.trim_start_matches('#')) {
543 tins.extend(set.iter().copied());
544 } else {
545 tins.push(
546 options
547 .token(name)
548 .unwrap_or_else(|| options.register_token(name)),
549 );
550 }
551 }
552 alt.s_bound.push(tins.clone());
557 alt.s.push(tins);
558 alt.s_names.push(names);
559 }
560 }
561 match map.get("b") {
562 None | Some(JsonValue::Null) | Some(JsonValue::Bool(false)) => {}
563 Some(JsonValue::String(reference)) if reference.starts_with('@') => {
564 if let Some(backtrack) = refs.match_backtracks.get(reference) {
565 alt.b_match = Some(backtrack.clone());
566 } else {
567 alt.b_fn = Some(refs.backtracks.get(reference).cloned().ok_or_else(|| {
568 GrammarError(format!(
569 "Grammar: unknown backtrack function reference: {reference}"
570 ))
571 })?);
572 }
573 }
574 Some(value) => {
575 alt.b = value.as_u64().map(|v| v as usize).ok_or_else(|| {
576 GrammarError(format!("Grammar: {label}.b must be a non-negative integer"))
577 })?;
578 }
579 }
580 alt.p = string_field(map, "p", label)?;
581 alt.r = string_field(map, "r", label)?;
582 if let Some(reference) = alt.p.as_deref().filter(|value| value.starts_with('@')) {
583 if let Some(route) = refs.match_pushes.get(reference) {
584 alt.p_match = Some(route.clone());
585 } else {
586 alt.p_fn = Some(refs.pushes.get(reference).cloned().ok_or_else(|| {
587 GrammarError(format!(
588 "Grammar: unknown push function reference: {reference}"
589 ))
590 })?);
591 }
592 alt.p = None;
593 }
594 if let Some(reference) = alt.r.as_deref().filter(|value| value.starts_with('@')) {
595 if let Some(route) = refs.match_replaces.get(reference) {
596 alt.r_match = Some(route.clone());
597 } else {
598 alt.r_fn = Some(refs.replaces.get(reference).cloned().ok_or_else(|| {
599 GrammarError(format!(
600 "Grammar: unknown replace function reference: {reference}"
601 ))
602 })?);
603 }
604 alt.r = None;
605 }
606 alt.a = match map.get("a") {
607 None | Some(JsonValue::Null) | Some(JsonValue::Bool(false)) => Vec::new(),
608 Some(JsonValue::String(action)) => vec![action.clone()],
609 Some(JsonValue::Array(actions)) => actions
610 .iter()
611 .map(|action| {
612 action.as_str().map(str::to_owned).ok_or_else(|| {
613 GrammarError(format!("Grammar: {label}.a entries must be strings"))
614 })
615 })
616 .collect::<Result<_, _>>()?,
617 Some(_) => {
618 return Err(GrammarError(format!(
619 "Grammar: {label}.a must be a string or array"
620 )))
621 }
622 };
623 match map.get("c") {
624 Some(JsonValue::String(reference)) => {
625 if matches!(
626 reference.as_str(),
627 "@probePhase0$" | "@probePhase1$" | "@probePhase2$"
628 ) {
629 alt.c_ref = Some(reference.clone());
630 } else if let Some(condition) = refs.conditions.get(reference) {
631 alt.c_fn = Some(condition.clone());
632 } else if let Some(condition) = refs.lexer_match_conditions.get(reference) {
633 alt.c_lex_match = Some(condition.clone());
634 } else if let Some(condition) = refs.match_conditions.get(reference) {
635 alt.c_match = Some(condition.clone());
636 } else if let Some(condition) = refs.lexer_conditions.get(reference) {
637 alt.c_lex = Some(condition.clone());
638 } else {
639 return Err(GrammarError(format!(
640 "Grammar: unknown condition function reference: {reference}"
641 )));
642 }
643 }
644 value => alt.c = parse_conditions(value, label)?,
645 }
646 if let Some(reference) = optional_ref_field(map, "h", label)? {
647 if let Some(modifier) = refs.match_modifiers.get(&reference) {
648 alt.h_match = Some(modifier.clone());
649 } else {
650 alt.h = Some(refs.modifiers.get(&reference).cloned().ok_or_else(|| {
651 GrammarError(format!(
652 "Grammar: unknown modifier function reference: {reference}"
653 ))
654 })?);
655 }
656 }
657 if let Some(reference) = optional_ref_field(map, "e", label)? {
658 if let Some(error) = refs.match_errors.get(&reference) {
659 alt.e_match = Some(error.clone());
660 } else {
661 alt.e = Some(refs.errors.get(&reference).cloned().ok_or_else(|| {
662 GrammarError(format!(
663 "Grammar: unknown error function reference: {reference}"
664 ))
665 })?);
666 }
667 }
668 alt.n = number_map(map.get("n"), label)?;
669 alt.u = value_map(map.get("u"), label)?;
670 alt.k = value_map(map.get("k"), label)?;
671 for action in &alt.a {
672 if matches!(
673 action.as_str(),
674 "@node$"
675 | "@capture$"
676 | "@fold$"
677 | "@object$"
678 | "@array$"
679 | "@key$"
680 | "@setval$"
681 | "@push$"
682 | "@value$"
683 ) {
684 let key = action.trim_start_matches('@');
685 if let Some(config) = alt.k.remove(key) {
686 validate_builtin_config(action, &config, label)?;
687 alt.action_configs.insert(action.clone(), config);
688 }
689 }
690 }
691 alt.g = match map.get("g") {
692 None => String::new(),
693 Some(JsonValue::String(tags)) => tags.clone(),
694 Some(JsonValue::Array(tags)) => tags
695 .iter()
696 .filter_map(JsonValue::as_str)
697 .collect::<Vec<_>>()
698 .join(","),
699 Some(_) => {
700 return Err(GrammarError(format!(
701 "Grammar: {label}.g must be a string or array"
702 )))
703 }
704 };
705 validate_group_tags(&alt.g, label)?;
706 Ok(alt)
707}
708
709fn optional_ref_field(
710 map: &Map<String, JsonValue>,
711 key: &str,
712 label: &str,
713) -> Result<Option<String>, GrammarError> {
714 match map.get(key) {
715 None | Some(JsonValue::Null) => Ok(None),
716 Some(JsonValue::String(reference)) if reference.starts_with('@') => {
717 Ok(Some(reference.clone()))
718 }
719 Some(_) => Err(GrammarError(format!(
720 "Grammar: {label}.{key} must be a function reference"
721 ))),
722 }
723}
724
725fn validate_action_references(tabnas: &Tabnas, spec: &RuleSpec) -> Result<(), GrammarError> {
726 for (state, alts) in [("open", &spec.open), ("close", &spec.close)] {
727 for (index, alt) in alts.iter().enumerate() {
728 for action in &alt.a {
729 if !is_builtin_action(action)
730 && !tabnas.actions.contains_key(action)
731 && !tabnas.context_actions.contains_key(action)
732 && !tabnas.matched_actions.contains_key(action)
733 {
734 return Err(GrammarError(format!(
735 "Grammar: {}.{state} alt[{index}]: unknown action function reference: {action}",
736 spec.name
737 )));
738 }
739 }
740 }
741 }
742 for (phase, actions) in [
743 ("bo", &spec.bo),
744 ("ao", &spec.ao),
745 ("bc", &spec.bc),
746 ("ac", &spec.ac),
747 ] {
748 for action in actions {
749 if !is_builtin_action(action)
750 && !tabnas.actions.contains_key(action)
751 && !tabnas.context_actions.contains_key(action)
752 && !tabnas.state_actions.contains_key(action)
753 {
754 return Err(GrammarError(format!(
755 "Grammar: {}.{phase}: unknown state action function reference: {action}",
756 spec.name
757 )));
758 }
759 }
760 }
761 Ok(())
762}
763
764fn wire_state_actions(tabnas: &Tabnas, spec: &mut RuleSpec) {
765 let has_action = |name: &str| {
766 tabnas.context_actions.contains_key(name) || tabnas.state_actions.contains_key(name)
767 };
768 let rule_name = spec.name.clone();
769 for (phase, actions) in [
770 ("bo", &mut spec.bo),
771 ("ao", &mut spec.ao),
772 ("bc", &mut spec.bc),
773 ("ac", &mut spec.ac),
774 ] {
775 let base = format!("@{rule_name}-{phase}");
776 let replacement = format!("{base}/replace");
777 if has_action(&replacement) {
778 actions.clear();
779 actions.push(replacement);
780 continue;
781 }
782
783 let prepend = format!("{base}/prepend");
784 if has_action(&prepend) && !actions.contains(&prepend) {
785 actions.insert(0, prepend);
786 }
787
788 let explicit_append = format!("{base}/append");
789 let append = has_action(&explicit_append)
790 .then_some(explicit_append)
791 .or_else(|| has_action(&base).then_some(base));
792 if let Some(append) = append.filter(|append| !actions.contains(append)) {
793 actions.push(append);
794 }
795 }
796}
797
798fn validate_builtin_config(action: &str, config: &Value, label: &str) -> Result<(), GrammarError> {
799 let Value::Object(config) = config else {
800 return Err(GrammarError(format!(
801 "Grammar: {label}.k.{} must be an object",
802 action.trim_start_matches('@')
803 )));
804 };
805 let fields: &[(&str, &str)] = match action {
806 "@node$" => &[
807 ("init", "boolean"),
808 ("rule", "string"),
809 ("kind", "string"),
810 ("nterms", "non-negative integer"),
811 ],
812 "@capture$" => &[("rule", "string"), ("kind", "string")],
813 "@fold$" => &[("cN", "non-negative integer")],
814 "@object$" | "@array$" => &[("implicit", "boolean")],
815 "@key$" => &[("slot", "string"), ("from", "integer"), ("lit", "string")],
816 "@setval$" => &[("slot", "string"), ("src", "boolean")],
817 "@push$" => &[("src", "boolean"), ("chain", "boolean")],
825 "@value$" => &[("from", "integer")],
826 _ => return Ok(()),
827 };
828 for key in config.keys() {
829 if !fields.iter().any(|(field, _)| key == field) {
830 return Err(GrammarError(format!(
831 "Grammar: {label}.k.{} has unknown field {key}",
832 action.trim_start_matches('@')
833 )));
834 }
835 }
836 for (field, expected) in fields {
837 let Some(value) = config.get(*field) else {
838 continue;
839 };
840 let valid = match *expected {
841 "boolean" => matches!(value, Value::Bool(_)),
842 "string" => matches!(value, Value::String(_)),
843 "integer" => {
844 matches!(value, Value::Number(number) if number.is_finite() && number.fract() == 0.0)
845 }
846 "non-negative integer" => {
847 matches!(value, Value::Number(number) if number.is_finite() && *number >= 0.0 && number.fract() == 0.0)
848 }
849 _ => false,
850 };
851 if !valid {
852 return Err(GrammarError(format!(
853 "Grammar: {label}.k.{}.{} must be a {expected}",
854 action.trim_start_matches('@'),
855 field
856 )));
857 }
858 }
859 Ok(())
860}
861
862fn parse_conditions(
863 value: Option<&JsonValue>,
864 label: &str,
865) -> Result<Vec<Condition>, GrammarError> {
866 let Some(value) = value else {
867 return Ok(Vec::new());
868 };
869 if value.is_null() {
870 return Ok(Vec::new());
871 }
872 let conditions = object(value, &format!("{label}.c"))?;
873 let roots = [
874 "n", "u", "k", "d", "i", "name", "state", "node", "need", "oN", "cN", "o", "c", "o0", "o1",
875 "c0", "c1", "parent", "child", "prev", "next", "spec",
876 ];
877 let mut output = Vec::new();
878 for (path, definition) in conditions {
879 if definition.is_null() {
880 continue;
881 }
882 let parts: Vec<String> = path.split('.').map(str::to_owned).collect();
883 if !roots.contains(&parts[0].as_str()) {
884 return Err(GrammarError(format!(
885 "{label}: unknown condition path: \"{path}\""
886 )));
887 }
888 if let Some(operators) = definition.as_object() {
889 for (operator, value) in operators {
890 let op = match operator.as_str() {
891 "$eq" => CompareOp::Eq,
892 "$ne" => CompareOp::Ne,
893 "$lt" => CompareOp::Lt,
894 "$lte" => CompareOp::Lte,
895 "$gt" => CompareOp::Gt,
896 "$gte" => CompareOp::Gte,
897 "$exist" => CompareOp::Exist,
898 _ => {
899 return Err(GrammarError(format!(
900 "{label}: unknown condition operator: {operator}"
901 )))
902 }
903 };
904 output.push(Condition {
905 path: parts.clone(),
906 op,
907 value: Value::from_json(value),
908 });
909 }
910 } else {
911 output.push(Condition {
912 path: parts,
913 op: CompareOp::Eq,
914 value: Value::from_json(definition),
915 });
916 }
917 }
918 Ok(output)
919}
920
921fn validate_group_tags(tags: &str, label: &str) -> Result<(), GrammarError> {
922 let valid = regex::Regex::new("^[a-z][a-z0-9-]+$").expect("static group regex");
923 for tag in tags.split(',').map(str::trim).filter(|tag| !tag.is_empty()) {
924 if !valid.is_match(tag) {
925 return Err(GrammarError(format!(
926 "{label}: invalid group tag: \"{tag}\""
927 )));
928 }
929 }
930 Ok(())
931}
932
933fn string_field(
934 map: &Map<String, JsonValue>,
935 key: &str,
936 label: &str,
937) -> Result<Option<String>, GrammarError> {
938 match map.get(key) {
939 None | Some(JsonValue::Null) | Some(JsonValue::Bool(false)) => Ok(None),
940 Some(JsonValue::String(value)) => Ok(Some(value.clone())),
941 Some(_) => Err(GrammarError(format!(
942 "Grammar: {label}.{key} must be a string"
943 ))),
944 }
945}
946
947fn number_map(
948 value: Option<&JsonValue>,
949 label: &str,
950) -> Result<HashMap<String, i32>, GrammarError> {
951 let Some(value) = value else {
952 return Ok(HashMap::new());
953 };
954 object(value, &format!("{label}.n"))?
955 .iter()
956 .map(|(key, value)| {
957 value
958 .as_i64()
959 .and_then(|v| i32::try_from(v).ok())
960 .map(|v| (key.clone(), v))
961 .ok_or_else(|| GrammarError(format!("Grammar: {label}.n.{key} must be an integer")))
962 })
963 .collect()
964}
965
966fn value_map(
967 value: Option<&JsonValue>,
968 label: &str,
969) -> Result<HashMap<String, Value>, GrammarError> {
970 let Some(value) = value else {
971 return Ok(HashMap::new());
972 };
973 Ok(object(value, label)?
974 .iter()
975 .map(|(key, value)| (key.clone(), Value::from_json(value)))
976 .collect())
977}
978
979fn apply_lex_check(
980 map: &Map<String, JsonValue>,
981 label: &str,
982 target: &mut Option<LexCheck>,
983 refs: &AltRefs,
984) -> Result<(), GrammarError> {
985 let Some(value) = map.get("check") else {
986 return Ok(());
987 };
988 if value.is_null() || value == &JsonValue::Bool(false) {
989 *target = None;
990 return Ok(());
991 }
992 let reference = value.as_str().ok_or_else(|| {
993 GrammarError(format!(
994 "Grammar: {label}.check must be a function reference or null"
995 ))
996 })?;
997 *target = Some(refs.lex_checks.get(reference).cloned().ok_or_else(|| {
998 GrammarError(format!(
999 "Grammar: unknown lexer check function reference: {reference}"
1000 ))
1001 })?);
1002 Ok(())
1003}
1004
1005fn resolve_value_producer(
1006 value: Option<&JsonValue>,
1007 refs: &AltRefs,
1008) -> (Option<Value>, Option<ValueTransform>) {
1009 match value.filter(|value| !value.is_null()) {
1010 Some(JsonValue::String(reference)) if refs.value_transforms.contains_key(reference) => {
1011 (None, refs.value_transforms.get(reference).cloned())
1012 }
1013 Some(JsonValue::String(reference)) if reference.starts_with("@@") => {
1014 (Some(Value::String(reference[1..].to_string())), None)
1015 }
1016 Some(value) => (Some(Value::from_json(value)), None),
1017 None => (None, None),
1018 }
1019}
1020
1021fn is_code_slot(path: &str) -> bool {
1024 const EXACT: &[&str] = &[
1025 "options.parser.start",
1026 "options.map.merge",
1027 "options.number.exclude",
1028 "options.parse.budget.onCheck",
1029 "options.errmsg.suffix",
1030 ];
1031 if EXACT.contains(&path) {
1032 return true;
1033 }
1034 path.ends_with(".check")
1035 || path.starts_with("options.match.token.")
1036 || path.starts_with("options.match.value.")
1037 || path.starts_with("options.lex.match.")
1038 || path.starts_with("options.parse.prepare.")
1039 || path.starts_with("options.config.modify.")
1040 || path.starts_with("options.text.modify")
1041 || (path.starts_with("options.value.def.") && path.ends_with(".val"))
1042 || (path.starts_with("options.comment.def.") && path.ends_with(".suffix"))
1043}
1044
1045fn typed_data_refs(
1054 value: &JsonValue,
1055 path: &str,
1056 refs: &AltRefs,
1057) -> Result<JsonValue, GrammarError> {
1058 match value {
1059 JsonValue::String(text) => {
1060 if is_code_slot(path) {
1061 return Ok(value.clone());
1062 }
1063 if let Some(literal) = text.strip_prefix("@@") {
1064 return Ok(JsonValue::String(format!("@{literal}")));
1065 }
1066 let is_regex = text.starts_with("@/") || text.starts_with("@~/");
1067 if !is_regex && (is_builtin_action(text) || refs.knows(text)) {
1068 return Err(GrammarError(format!(
1069 "Grammar: {path}: a function reference is not allowed in a data slot \
1070 (got {text:?}; spell a literal at sign as @@)"
1071 )));
1072 }
1073 Ok(value.clone())
1074 }
1075 JsonValue::Object(entries) => {
1076 let mut out = Map::with_capacity(entries.len());
1077 for (key, entry) in entries {
1078 out.insert(
1079 key.clone(),
1080 typed_data_refs(entry, &format!("{path}.{key}"), refs)?,
1081 );
1082 }
1083 Ok(JsonValue::Object(out))
1084 }
1085 JsonValue::Array(items) => Ok(JsonValue::Array(
1086 items
1087 .iter()
1088 .enumerate()
1089 .map(|(index, item)| typed_data_refs(item, &format!("{path}[{index}]"), refs))
1090 .collect::<Result<Vec<_>, _>>()?,
1091 )),
1092 _ => Ok(value.clone()),
1093 }
1094}
1095
1096const CALLER_KEYED_MAPS: [&str; 15] = [
1101 "fixed.token",
1102 "match.token",
1103 "match.value",
1104 "tokenSet",
1105 "token_set",
1106 "comment.def",
1107 "value.def",
1108 "string.escape",
1109 "string.replace",
1110 "error",
1111 "hint",
1112 "parse.prepare",
1113 "config.modify",
1114 "lex.match",
1115 "plugin",
1116];
1117
1118fn reject_reserved_names(map: &Map<String, JsonValue>) -> Result<(), GrammarError> {
1135 for path in CALLER_KEYED_MAPS {
1136 let mut node = map;
1137 let mut segments = path.split('.').peekable();
1138 let entries = loop {
1139 let Some(segment) = segments.next() else {
1140 break None;
1141 };
1142 let Some(child) = node.get(segment).and_then(JsonValue::as_object) else {
1143 break None;
1144 };
1145 if segments.peek().is_none() {
1146 break Some(child);
1147 }
1148 node = child;
1149 };
1150 let Some(entries) = entries else { continue };
1151 for name in entries.keys() {
1152 if RESERVED_MAP_KEYS.contains(&name.as_str()) {
1153 let kind = path.rsplit('.').next().unwrap_or(path);
1154 return Err(GrammarError(format!(
1155 "Grammar: options.{path}.{name}: `{name}` is a reserved name \
1156 and cannot be a {kind} entry (it would reach the prototype chain)"
1157 )));
1158 }
1159 }
1160 }
1161 Ok(())
1162}
1163
1164fn apply_options(
1165 options: &mut crate::Options,
1166 map: &Map<String, JsonValue>,
1167 refs: &AltRefs,
1168) -> Result<(), GrammarError> {
1169 let typed = typed_data_refs(&JsonValue::Object(map.clone()), "options", refs)?;
1170 let JsonValue::Object(typed) = typed else {
1171 unreachable!("an object maps to an object");
1172 };
1173 let map = &typed;
1174 reject_reserved_names(map)?;
1175 if let Some(tag) = map.get("tag").and_then(JsonValue::as_str) {
1176 options.tag = tag.into();
1177 }
1178 if let Some(plugin) = map.get("plugin") {
1179 for (name, value) in object(plugin, "options.plugin")? {
1180 if value.is_null() || value == &JsonValue::Bool(false) {
1181 options.plugin.shift_remove(&name.to_lowercase());
1182 continue;
1183 }
1184 let name = name.to_lowercase();
1185 let current = options
1186 .plugin
1187 .shift_remove(&name)
1188 .unwrap_or_else(|| Value::object(IndexMap::new()));
1189 options.plugin.insert(
1190 name,
1191 crate::merge_plugin_values(current, Value::from_json(value)),
1192 );
1193 }
1194 }
1195 for (field, target) in [("error", &mut options.error), ("hint", &mut options.hint)] {
1196 if let Some(overrides) = map.get(field) {
1197 for (code, template) in object(overrides, &format!("options.{field}"))? {
1198 if template.is_null() || template == &JsonValue::Bool(false) {
1199 target.remove(code);
1200 } else {
1201 target.insert(
1202 code.clone(),
1203 template
1204 .as_str()
1205 .ok_or_else(|| {
1206 GrammarError(format!(
1207 "Grammar: options.{field}.{code} must be a string or null"
1208 ))
1209 })?
1210 .into(),
1211 );
1212 }
1213 }
1214 }
1215 }
1216 if let Some(errmsg) = map.get("errmsg") {
1217 let errmsg = object(errmsg, "options.errmsg")?;
1218 if let Some(name) = errmsg.get("name") {
1219 options.errmsg.name = name
1220 .as_str()
1221 .ok_or_else(|| {
1222 GrammarError("Grammar: options.errmsg.name must be a string".into())
1223 })?
1224 .into();
1225 }
1226 if let Some(suffix) = errmsg.get("suffix") {
1227 options.errmsg.suffix = match suffix {
1228 JsonValue::Null | JsonValue::Bool(false) => crate::ErrorSuffix::Disabled,
1229 JsonValue::Bool(true) => crate::ErrorSuffix::Standard,
1230 JsonValue::String(reference) => {
1231 refs.error_suffixes.get(reference).cloned().map_or_else(
1232 || crate::ErrorSuffix::Text(reference.clone()),
1233 crate::ErrorSuffix::Callback,
1234 )
1235 }
1236 _ => {
1237 return Err(GrammarError(
1238 "Grammar: options.errmsg.suffix must be a boolean, string, or null".into(),
1239 ))
1240 }
1241 };
1242 }
1243 if let Some(link) = errmsg.get("link") {
1244 options.errmsg.link = if link.is_null() {
1245 String::new()
1246 } else {
1247 link.as_str()
1248 .ok_or_else(|| {
1249 GrammarError("Grammar: options.errmsg.link must be a string or null".into())
1250 })?
1251 .into()
1252 };
1253 }
1254 }
1255 if let Some(color) = map.get("color") {
1256 let color = object(color, "options.color")?;
1257 set_bool(color, "active", &mut options.color.active);
1258 for (field, target) in [
1259 ("reset", &mut options.color.reset),
1260 ("hi", &mut options.color.hi),
1261 ("lo", &mut options.color.lo),
1262 ("line", &mut options.color.line),
1263 ] {
1264 if let Some(value) = color.get(field) {
1265 *target = value
1266 .as_str()
1267 .ok_or_else(|| {
1268 GrammarError(format!("Grammar: options.color.{field} must be a string"))
1269 })?
1270 .into();
1271 }
1272 }
1273 }
1274 if let Some(text) = map.get("text") {
1275 let text = object(text, "options.text")?;
1276 set_bool(text, "lex", &mut options.text.lex);
1277 apply_lex_check(text, "options.text", &mut options.text.check, refs)?;
1278 if let Some(modify) = text.get("modify") {
1279 let references =
1280 match modify {
1281 JsonValue::Null | JsonValue::Bool(false) => Vec::new(),
1282 JsonValue::String(reference) => vec![reference.as_str()],
1283 JsonValue::Array(references) => references
1284 .iter()
1285 .map(|reference| {
1286 reference.as_str().ok_or_else(|| {
1287 GrammarError(
1288 "Grammar: options.text.modify entries must be function references"
1289 .into(),
1290 )
1291 })
1292 })
1293 .collect::<Result<Vec<_>, _>>()?,
1294 _ => return Err(GrammarError(
1295 "Grammar: options.text.modify must be a function reference, array, or null"
1296 .into(),
1297 )),
1298 };
1299 options.text.modify = references
1300 .into_iter()
1301 .map(|reference| {
1302 refs.text_modifiers.get(reference).cloned().ok_or_else(|| {
1303 GrammarError(format!(
1304 "Grammar: unknown text modifier function reference: {reference}"
1305 ))
1306 })
1307 })
1308 .collect::<Result<_, _>>()?;
1309 }
1310 }
1311 if let Some(space) = map.get("space") {
1312 let space = object(space, "options.space")?;
1313 set_bool(space, "lex", &mut options.space.lex);
1314 apply_lex_check(space, "options.space", &mut options.space.check, refs)?;
1315 if let Some(chars) = space.get("chars") {
1316 options.space.chars = chars
1317 .as_str()
1318 .ok_or_else(|| {
1319 GrammarError("Grammar: options.space.chars must be a string".into())
1320 })?
1321 .into();
1322 }
1323 }
1324 if let Some(number) = map.get("number") {
1325 let number = object(number, "options.number")?;
1326 set_bool(number, "lex", &mut options.number.lex);
1327 set_bool(number, "hex", &mut options.number.hex);
1328 set_bool(number, "oct", &mut options.number.oct);
1329 set_bool(number, "bin", &mut options.number.bin);
1330 apply_lex_check(number, "options.number", &mut options.number.check, refs)?;
1331 if let Some(separator) = number.get("sep") {
1332 options.number.sep = separator.as_str().map(str::to_owned);
1333 }
1334 if let Some(exclude) = number.get("exclude") {
1335 options.number.exclude = exclude.as_str().map(str::to_owned);
1336 }
1337 }
1338 if let Some(string) = map.get("string") {
1339 let string = object(string, "options.string")?;
1340 set_bool(string, "lex", &mut options.string.lex);
1341 apply_lex_check(string, "options.string", &mut options.string.check, refs)?;
1342 if let Some(chars) = string.get("chars").and_then(JsonValue::as_str) {
1343 options.string.chars = chars.into();
1344 }
1345 if let Some(chars) = string
1346 .get("multiChars")
1347 .or_else(|| string.get("multi_chars"))
1348 .and_then(JsonValue::as_str)
1349 {
1350 options.string.multi_chars = chars.into();
1351 }
1352 if let Some(escape_char) = string.get("escapeChar") {
1353 let escape_char = escape_char.as_str().ok_or_else(|| {
1354 GrammarError("Grammar: options.string.escapeChar must be a string".into())
1355 })?;
1356 let mut chars = escape_char.chars();
1357 options.string.escape_char = chars.next().ok_or_else(|| {
1358 GrammarError("Grammar: options.string.escapeChar must not be empty".into())
1359 })?;
1360 if chars.next().is_some() {
1361 return Err(GrammarError(
1362 "Grammar: options.string.escapeChar must contain one character".into(),
1363 ));
1364 }
1365 }
1366 for (field, target) in [
1367 ("escape", &mut options.string.escape),
1368 ("replace", &mut options.string.replace),
1369 ] {
1370 if let Some(entries) = string.get(field) {
1371 for (key, value) in object(entries, &format!("options.string.{field}"))? {
1372 let mut chars = key.chars();
1373 let character = chars.next().ok_or_else(|| {
1374 GrammarError(format!(
1375 "Grammar: options.string.{field} keys must not be empty"
1376 ))
1377 })?;
1378 if chars.next().is_some() {
1379 return Err(GrammarError(format!(
1380 "Grammar: options.string.{field} keys must contain one character"
1381 )));
1382 }
1383 if value.is_null() {
1384 target.remove(&character);
1385 } else {
1386 target.insert(
1387 character,
1388 value
1389 .as_str()
1390 .ok_or_else(|| {
1391 GrammarError(format!(
1392 "Grammar: options.string.{field}.{key} must be a string or null"
1393 ))
1394 })?
1395 .into(),
1396 );
1397 }
1398 }
1399 }
1400 }
1401 if let Some(value) = string
1402 .get("allowUnknown")
1403 .or_else(|| string.get("allow_unknown"))
1404 .and_then(JsonValue::as_bool)
1405 {
1406 options.string.allow_unknown = value;
1407 }
1408 if let Some(value) = string
1409 .get("escapeStrict")
1410 .or_else(|| string.get("escape_strict"))
1411 .and_then(JsonValue::as_bool)
1412 {
1413 options.string.escape_strict = value;
1414 }
1415 if let Some(value) = string
1416 .get("allowControl")
1417 .or_else(|| string.get("allow_control"))
1418 .and_then(JsonValue::as_bool)
1419 {
1420 options.string.allow_control = value;
1421 }
1422 if let Some(value) = string.get("abandon").and_then(JsonValue::as_bool) {
1423 options.string.abandon = value;
1424 }
1425 }
1426 if let Some(line) = map.get("line") {
1427 let line = object(line, "options.line")?;
1428 set_bool(line, "lex", &mut options.line.lex);
1429 set_bool(line, "single", &mut options.line.single);
1430 apply_lex_check(line, "options.line", &mut options.line.check, refs)?;
1431 if let Some(chars) = line.get("chars") {
1432 options.line.chars = chars
1433 .as_str()
1434 .ok_or_else(|| GrammarError("Grammar: options.line.chars must be a string".into()))?
1435 .into();
1436 }
1437 if let Some(chars) = line.get("rowChars") {
1438 options.line.row_chars = chars
1439 .as_str()
1440 .ok_or_else(|| {
1441 GrammarError("Grammar: options.line.rowChars must be a string".into())
1442 })?
1443 .into();
1444 }
1445 if let Some(chars) = line.get("fixed").and_then(JsonValue::as_str) {
1446 options.line.fixed = chars.chars().collect();
1447 }
1448 }
1449 if let Some(comment) = map.get("comment") {
1450 let comment = object(comment, "options.comment")?;
1451 set_bool(comment, "lex", &mut options.comment.lex);
1452 apply_lex_check(comment, "options.comment", &mut options.comment.check, refs)?;
1453 if let Some(definitions) = comment.get("def") {
1454 for (name, value) in object(definitions, "options.comment.def")? {
1455 if value.is_null() || value == &JsonValue::Bool(false) {
1456 options.comment.definitions.shift_remove(name);
1457 continue;
1458 }
1459 let value = object(value, &format!("options.comment.def.{name}"))?;
1460 let definition =
1461 options
1462 .comment
1463 .definitions
1464 .entry(name.clone())
1465 .or_insert(crate::CommentDef {
1466 line: false,
1467 start: String::new(),
1468 end: String::new(),
1469 lex: false,
1470 suffixes: Vec::new(),
1471 suffix_matcher: None,
1472 eat_line: false,
1473 });
1474 set_bool(value, "line", &mut definition.line);
1475 set_bool(value, "lex", &mut definition.lex);
1476 set_bool(value, "eatline", &mut definition.eat_line);
1477 for (field, target) in [
1478 ("start", &mut definition.start),
1479 ("end", &mut definition.end),
1480 ] {
1481 if let Some(text) = value.get(field) {
1482 *target = text
1483 .as_str()
1484 .ok_or_else(|| {
1485 GrammarError(format!(
1486 "Grammar: options.comment.def.{name}.{field} must be a string"
1487 ))
1488 })?
1489 .into();
1490 }
1491 }
1492 if let Some(suffix) = value.get("suffix") {
1493 definition.suffix_matcher = None;
1494 definition.suffixes = match suffix {
1495 JsonValue::Null => Vec::new(),
1496 JsonValue::String(reference)
1497 if refs.comment_suffixes.contains_key(reference) =>
1498 {
1499 definition.suffix_matcher =
1500 refs.comment_suffixes.get(reference).cloned();
1501 Vec::new()
1502 }
1503 JsonValue::String(suffix) => {
1504 if suffix.is_empty() {
1505 Vec::new()
1506 } else {
1507 vec![suffix.clone()]
1508 }
1509 }
1510 JsonValue::Array(suffixes) => suffixes
1511 .iter()
1512 .map(|suffix| {
1513 suffix.as_str().map(str::to_owned).ok_or_else(|| {
1514 GrammarError(format!(
1515 "Grammar: options.comment.def.{name}.suffix entries must be strings"
1516 ))
1517 })
1518 })
1519 .collect::<Result<Vec<_>, _>>()?,
1520 _ => {
1521 return Err(GrammarError(format!(
1522 "Grammar: options.comment.def.{name}.suffix must be a string, array, or null"
1523 )))
1524 }
1525 };
1526 definition.suffixes.retain(|suffix| !suffix.is_empty());
1527 definition
1528 .suffixes
1529 .sort_by(|a, b| b.len().cmp(&a.len()).then_with(|| a.cmp(b)));
1530 }
1531 }
1532 }
1533 }
1534 if let Some(value) = map.get("value") {
1535 let value = object(value, "options.value")?;
1536 set_bool(value, "lex", &mut options.value.lex);
1537 if let Some(definitions) = value.get("def") {
1538 for (name, value) in object(definitions, "options.value.def")? {
1539 if value.is_null() || value == &JsonValue::Bool(false) {
1540 options.value.definitions.shift_remove(name);
1541 continue;
1542 }
1543 let value = object(value, &format!("options.value.def.{name}"))?;
1544 let (val, transform) = resolve_value_producer(value.get("val"), refs);
1545 let matcher = value
1546 .get("match")
1547 .map(|matcher| {
1548 let source = matcher.as_str().ok_or_else(|| {
1549 GrammarError(format!(
1550 "Grammar: options.value.def.{name}.match must be a serialized regex"
1551 ))
1552 })?;
1553 compile_serialized_regex(
1554 source,
1555 &format!("options.value.def.{name}.match"),
1556 true,
1557 )
1558 .map(|(regex, _)| regex)
1559 })
1560 .transpose()?;
1561 let consume = match value.get("consume") {
1562 Some(consume) => consume.as_bool().ok_or_else(|| {
1563 GrammarError(format!(
1564 "Grammar: options.value.def.{name}.consume must be a boolean"
1565 ))
1566 })?,
1567 None => false,
1568 };
1569 options.value.definitions.insert(
1570 name.clone(),
1571 crate::ValueDef {
1572 val,
1573 matcher,
1574 transform,
1575 consume,
1576 },
1577 );
1578 }
1579 }
1580 }
1581 if let Some(ender) = map.get("ender") {
1582 options.ender = match ender {
1583 JsonValue::Null => Vec::new(),
1584 JsonValue::String(ender) => ender.chars().map(String::from).collect(),
1589 JsonValue::Array(enders) => enders
1590 .iter()
1591 .map(|ender| {
1592 ender.as_str().map(str::to_owned).ok_or_else(|| {
1593 GrammarError("Grammar: options.ender entries must be strings".into())
1594 })
1595 })
1596 .collect::<Result<_, _>>()?,
1597 _ => {
1598 return Err(GrammarError(
1599 "Grammar: options.ender must be a string, array, or null".into(),
1600 ))
1601 }
1602 };
1603 options.ender.retain(|ender| !ender.is_empty());
1604 }
1605 if let Some(map_options) = map.get("map") {
1606 let map_options = object(map_options, "options.map")?;
1607 set_bool(map_options, "extend", &mut options.map.extend);
1608 set_bool(map_options, "child", &mut options.map.child);
1609 set_bool(map_options, "ordered", &mut options.map.ordered);
1610 if let Some(reference) = map_options.get("merge") {
1611 options.map.merge = match reference {
1612 JsonValue::Null | JsonValue::Bool(false) => None,
1613 JsonValue::String(reference) => {
1614 Some(refs.map_merges.get(reference).cloned().ok_or_else(|| {
1615 GrammarError(format!(
1616 "Grammar: unknown map merge function reference: {reference}"
1617 ))
1618 })?)
1619 }
1620 _ => {
1621 return Err(GrammarError(
1622 "Grammar: options.map.merge must be a function reference or null".into(),
1623 ))
1624 }
1625 };
1626 }
1627 }
1628 if let Some(list_options) = map.get("list") {
1629 let list_options = object(list_options, "options.list")?;
1630 set_bool(list_options, "property", &mut options.list.property);
1631 set_bool(list_options, "pair", &mut options.list.pair);
1632 set_bool(list_options, "child", &mut options.list.child);
1633 }
1634 if let Some(safe_options) = map.get("safe") {
1635 let safe_options = object(safe_options, "options.safe")?;
1636 set_bool(safe_options, "key", &mut options.safe.key);
1637 }
1638 if let Some(info) = map.get("info") {
1639 let info = object(info, "options.info")?;
1640 set_bool(info, "map", &mut options.info.map);
1641 set_bool(info, "list", &mut options.info.list);
1642 set_bool(info, "text", &mut options.info.text);
1643 if let Some(marker) = info.get("marker") {
1644 options.info.marker = marker
1645 .as_str()
1646 .ok_or_else(|| {
1647 GrammarError("Grammar: options.info.marker must be a string".into())
1648 })?
1649 .to_string();
1650 }
1651 }
1652 if let Some(lex) = map.get("lex") {
1653 let lex = object(lex, "options.lex")?;
1654 set_bool(lex, "empty", &mut options.lex.empty);
1655 set_bool(lex, "relex", &mut options.lex.relex);
1656 if let Some(value) = lex.get("emptyResult") {
1657 options.lex.empty_result = Value::from_json(value);
1658 }
1659 if let Some(matchers) = lex.get("match") {
1660 for (name, spec) in object(matchers, "options.lex.match")? {
1661 if spec.is_null() || spec == &JsonValue::Bool(false) {
1662 options.lex.matchers.shift_remove(name);
1663 continue;
1664 }
1665 let spec = object(spec, &format!("options.lex.match.{name}"))?;
1666 let order = spec
1667 .get("order")
1668 .and_then(JsonValue::as_f64)
1669 .filter(|order| order.is_finite())
1670 .ok_or_else(|| {
1671 GrammarError(format!(
1672 "Grammar: options.lex.match.{name}.order must be a finite number"
1673 ))
1674 })?;
1675 if order < 0.0 {
1676 options.lex.matchers.shift_remove(name);
1677 continue;
1678 }
1679 let reference = spec
1680 .get("make")
1681 .and_then(JsonValue::as_str)
1682 .ok_or_else(|| {
1683 GrammarError(format!(
1684 "Grammar: options.lex.match.{name}.make must be a function reference"
1685 ))
1686 })?;
1687 let matcher = refs.lex_matches.get(reference).cloned();
1688 let imperative = refs.imperative_lex_matches.get(reference).cloned();
1689 let factory = refs.lex_match_factories.get(reference).cloned();
1690 if matcher.is_none() && imperative.is_none() && factory.is_none() {
1691 return Err(GrammarError(format!(
1692 "Grammar: unknown custom lexer matcher function reference: {reference}"
1693 )));
1694 }
1695 options.lex.matchers.insert(
1696 name.clone(),
1697 crate::LexMatcher {
1698 name: name.clone(),
1699 order,
1700 matcher,
1701 imperative,
1702 factory,
1703 },
1704 );
1705 }
1706 options.lex.matchers.sort_by(|name_a, left, name_b, right| {
1707 left.order
1708 .total_cmp(&right.order)
1709 .then_with(|| name_a.cmp(name_b))
1710 });
1711 }
1712 }
1713 if let Some(rewind) = map.get("rewind") {
1714 let rewind = object(rewind, "options.rewind")?;
1715 if let Some(history) = rewind.get("history") {
1716 options.rewind.history = match history {
1720 JsonValue::Null => crate::RewindOptions::default().history,
1721 JsonValue::Bool(false) => None,
1722 JsonValue::Number(number) => match number.as_i64() {
1723 Some(value) if value < 0 => Some(0),
1724 Some(value) => Some(usize::try_from(value).map_err(|_| {
1725 GrammarError(
1726 "Grammar: options.rewind.history is outside the supported range".into(),
1727 )
1728 })?),
1729 None => {
1730 return Err(GrammarError(
1731 "Grammar: options.rewind.history must be an integer, null or false"
1732 .into(),
1733 ))
1734 }
1735 },
1736 _ => {
1737 return Err(GrammarError(
1738 "Grammar: options.rewind.history must be an integer, null or false".into(),
1739 ))
1740 }
1741 };
1742 }
1743 }
1744 if let Some(rule) = map.get("rule") {
1745 let rule = object(rule, "options.rule")?;
1746 if let Some(start) = rule.get("start").and_then(JsonValue::as_str) {
1747 options.rule.start = start.into();
1748 }
1749 if let Some(finish) = rule.get("finish").and_then(JsonValue::as_bool) {
1750 options.rule.finish = finish;
1751 }
1752 if let Some(maxmul) = rule.get("maxmul") {
1753 options.rule.maxmul = match maxmul.as_i64() {
1754 Some(value) if value <= 0 => 3,
1755 Some(value) => usize::try_from(value).map_err(|_| {
1756 GrammarError(
1757 "Grammar: options.rule.maxmul is outside the supported range".into(),
1758 )
1759 })?,
1760 None => {
1761 return Err(GrammarError(
1762 "Grammar: options.rule.maxmul must be an integer".into(),
1763 ))
1764 }
1765 };
1766 }
1767 if let Some(include) = rule.get("include").and_then(JsonValue::as_str) {
1768 options.rule.include = include.into();
1769 }
1770 if let Some(exclude) = rule.get("exclude").and_then(JsonValue::as_str) {
1771 options.rule.exclude = exclude.into();
1772 }
1773 if let Some(history) = rule.get("history") {
1777 options.rule.history =
1778 match history {
1779 JsonValue::Null | JsonValue::Bool(false) => None,
1780 JsonValue::Number(_) => match history.as_i64() {
1781 Some(value) if value >= 1 => Some(
1782 usize::try_from(value)
1783 .ok()
1784 .filter(|value| *value <= crate::options::MAX_RULE_HISTORY)
1785 .ok_or_else(|| {
1786 GrammarError(format!(
1787 "Grammar: options.rule.history is outside the supported \
1788 range (at most {})",
1789 crate::options::MAX_RULE_HISTORY
1790 ))
1791 })?,
1792 ),
1793 _ => return Err(GrammarError(
1794 "Grammar: options.rule.history must be an integer of at least 1, null \
1795 or false"
1796 .into(),
1797 )),
1798 },
1799 _ => return Err(GrammarError(
1800 "Grammar: options.rule.history must be an integer of at least 1, null or \
1801 false"
1802 .into(),
1803 )),
1804 };
1805 }
1806 }
1807 if let Some(parse) = map.get("parse") {
1808 let parse = object(parse, "options.parse")?;
1809 if let Some(prepare) = parse.get("prepare") {
1810 let prepare = object(prepare, "options.parse.prepare")?;
1811 for (name, reference) in prepare {
1812 if reference.is_null() || reference == &JsonValue::Bool(false) {
1813 options.parse.named_prepare.shift_remove(name);
1814 continue;
1815 }
1816 let reference = reference.as_str().ok_or_else(|| {
1817 GrammarError(format!(
1818 "Grammar: options.parse.prepare.{name} must be a function reference or null"
1819 ))
1820 })?;
1821 let callback = refs.parse_prepares.get(reference).cloned().ok_or_else(|| {
1822 GrammarError(format!(
1823 "Grammar: unknown parse prepare function reference: {reference}"
1824 ))
1825 })?;
1826 options.parse.named_prepare.insert(name.clone(), callback);
1827 }
1828 options.parse.named_prepare.sort_keys();
1829 }
1830 if let Some(budget) = parse.get("budget") {
1831 let budget = object(budget, "options.parse.budget")?;
1832 if let Some(interval) = budget
1833 .get("checkEveryN")
1834 .or_else(|| budget.get("check_every_n"))
1835 {
1836 options.parse.budget.check_every_n = interval
1837 .as_u64()
1838 .and_then(|value| usize::try_from(value).ok())
1839 .ok_or_else(|| {
1840 GrammarError(
1841 "Grammar: options.parse.budget.checkEveryN must be a non-negative integer"
1842 .into(),
1843 )
1844 })?;
1845 }
1846 if let Some(reference) = budget.get("onCheck").or_else(|| budget.get("on_check")) {
1847 options.parse.budget.on_check = match reference {
1848 JsonValue::Null | JsonValue::Bool(false) => None,
1849 JsonValue::String(reference) => Some(
1850 refs.budget_checks
1851 .get(reference)
1852 .cloned()
1853 .ok_or_else(|| {
1854 GrammarError(format!(
1855 "Grammar: unknown parse budget function reference: {reference}"
1856 ))
1857 })?,
1858 ),
1859 _ => {
1860 return Err(GrammarError(
1861 "Grammar: options.parse.budget.onCheck must be a function reference or null"
1862 .into(),
1863 ))
1864 }
1865 };
1866 }
1867 }
1868 if let Some(recover) = parse.get("recover") {
1869 let recover = object(recover, "options.parse.recover")?;
1870 set_bool(recover, "enabled", &mut options.parse.recover.enabled);
1871 set_bool(
1872 recover,
1873 "popUntilValid",
1874 &mut options.parse.recover.pop_until_valid,
1875 );
1876 if let Some(groups) = recover.get("syncGroups") {
1877 options.parse.recover.sync_groups = match groups {
1878 JsonValue::Null => vec!["close".into(), "comma".into(), "end".into()],
1879 JsonValue::Array(groups) => groups
1880 .iter()
1881 .map(|group| {
1882 group.as_str().map(str::to_owned).ok_or_else(|| {
1883 GrammarError(
1884 "Grammar: options.parse.recover.syncGroups entries must be strings"
1885 .into(),
1886 )
1887 })
1888 })
1889 .collect::<Result<_, _>>()?,
1890 _ => {
1891 return Err(GrammarError(
1892 "Grammar: options.parse.recover.syncGroups must be an array or null"
1893 .into(),
1894 ))
1895 }
1896 };
1897 }
1898 if let Some(tokens) = recover.get("syncTokens") {
1899 options.parse.recover.sync_tokens = tokens
1900 .as_array()
1901 .ok_or_else(|| {
1902 GrammarError(
1903 "Grammar: options.parse.recover.syncTokens must be an array".into(),
1904 )
1905 })?
1906 .iter()
1907 .map(|token| {
1908 token.as_str().map(str::to_owned).ok_or_else(|| {
1909 GrammarError(
1910 "Grammar: options.parse.recover.syncTokens entries must be strings"
1911 .into(),
1912 )
1913 })
1914 })
1915 .collect::<Result<_, _>>()?;
1916 }
1917 for (name, target) in [
1918 ("maxSkip", &mut options.parse.recover.max_skip),
1919 ("maxRecoveries", &mut options.parse.recover.max_recoveries),
1920 ("suppress", &mut options.parse.recover.suppress),
1921 ] {
1922 if let Some(value) = recover.get(name) {
1923 *target = value
1924 .as_u64()
1925 .and_then(|value| usize::try_from(value).ok())
1926 .ok_or_else(|| {
1927 GrammarError(format!(
1928 "Grammar: options.parse.recover.{name} must be a non-negative integer"
1929 ))
1930 })?;
1931 }
1932 }
1933 }
1934 }
1935 if let Some(result) = map.get("result") {
1936 let result = object(result, "options.result")?;
1937 if let Some(fail) = result.get("fail") {
1938 options.result.fail = fail
1939 .as_array()
1940 .ok_or_else(|| {
1941 GrammarError("Grammar: options.result.fail must be an array".into())
1942 })?
1943 .iter()
1944 .map(Value::from_json)
1945 .collect();
1946 }
1947 }
1948 if let Some(debug) = map.get("debug") {
1949 let debug = object(debug, "options.debug")?;
1950 if let Some(maxlen) = debug.get("maxlen") {
1951 options.debug.maxlen = maxlen
1952 .as_u64()
1953 .and_then(|value| usize::try_from(value).ok())
1954 .ok_or_else(|| {
1955 GrammarError(
1956 "Grammar: options.debug.maxlen must be a non-negative integer".into(),
1957 )
1958 })?;
1959 }
1960 if let Some(print) = debug.get("print") {
1961 let print = object(print, "options.debug.print")?;
1962 set_bool(print, "config", &mut options.debug.print.config);
1963 }
1964 }
1965 if let Some(parser) = map.get("parser") {
1966 let parser = object(parser, "options.parser")?;
1967 if let Some(reference) = parser.get("start") {
1968 match reference {
1969 JsonValue::Null | JsonValue::Bool(false) => {
1970 options.parser.start = None;
1971 options.parser.start_with_instance = None;
1972 options.parser.start_with_context = None;
1973 }
1974 JsonValue::String(reference) => {
1975 options.parser.start = refs.parser_starts.get(reference).cloned();
1976 options.parser.start_with_instance =
1977 refs.parser_starts_with_instance.get(reference).cloned();
1978 options.parser.start_with_context =
1979 refs.parser_starts_with_context.get(reference).cloned();
1980 if options.parser.start.is_none()
1981 && options.parser.start_with_instance.is_none()
1982 && options.parser.start_with_context.is_none()
1983 {
1984 return Err(GrammarError(format!(
1985 "Grammar: unknown parser start function reference: {reference}"
1986 )));
1987 }
1988 }
1989 _ => {
1990 return Err(GrammarError(
1991 "Grammar: options.parser.start must be a function reference or null".into(),
1992 ))
1993 }
1994 }
1995 }
1996 }
1997 if let Some(fixed_options) = map.get("fixed") {
1998 let fixed_options = object(fixed_options, "options.fixed")?;
1999 set_bool(fixed_options, "lex", &mut options.fixed.lex);
2000 apply_lex_check(
2001 fixed_options,
2002 "options.fixed",
2003 &mut options.fixed.check,
2004 refs,
2005 )?;
2006 if let Some(tokens) = fixed_options.get("token") {
2007 for (name, source) in object(tokens, "options.fixed.token")? {
2008 let name = if name.starts_with('#') {
2009 name.clone()
2010 } else {
2011 format!("#{name}")
2012 };
2013 if source.is_null() {
2014 options.fixed.tokens.shift_remove(&name);
2015 continue;
2016 }
2017 let source = source.as_str().ok_or_else(|| {
2018 GrammarError(format!(
2019 "Grammar: options.fixed.token.{name} must be a string or null"
2020 ))
2021 })?;
2022 if matches!(
2023 name.as_str(),
2024 "#BD"
2025 | "#ZZ"
2026 | "#UK"
2027 | "#AA"
2028 | "#SP"
2029 | "#LN"
2030 | "#CM"
2031 | "#NR"
2032 | "#ST"
2033 | "#TX"
2034 | "#VL"
2035 ) {
2036 return Err(GrammarError(format!(
2037 "Grammar: {name} is produced by a lexer matcher and cannot be bound to a fixed literal"
2038 )));
2039 }
2040 let tin = options
2041 .fixed
2042 .tokens
2043 .get(&name)
2044 .map(|token| token.tin)
2045 .or_else(|| options.token(&name))
2046 .unwrap_or_else(|| options.next_tin());
2047 options.fixed.tokens.insert(
2048 name.clone(),
2049 crate::options::FixedToken {
2050 name,
2051 tin,
2052 source: source.to_string(),
2053 },
2054 );
2055 }
2056 }
2057 }
2058 if let Some(match_options) = map.get("match") {
2059 let match_options = object(match_options, "options.match")?;
2060 set_bool(match_options, "lex", &mut options.match_lex);
2061 apply_lex_check(
2062 match_options,
2063 "options.match",
2064 &mut options.match_check,
2065 refs,
2066 )?;
2067 if let Some(tokens) = match_options.get("token") {
2068 for (name, source) in object(tokens, "options.match.token")? {
2069 let name = if name.starts_with('#') {
2070 name.clone()
2071 } else {
2072 format!("#{name}")
2073 };
2074 if source.is_null() {
2075 options.match_tokens.shift_remove(&name);
2076 continue;
2077 }
2078 let source = source.as_str().ok_or_else(|| {
2079 GrammarError(format!(
2080 "Grammar: options.match.token.{name} must be a serialized regex"
2081 ))
2082 })?;
2083 let matcher = if serialized_regex(source).is_some() {
2084 let (regex, eager) = compile_serialized_regex(
2085 source,
2086 &format!("options.match.token.{name}"),
2087 true,
2088 )?;
2089 (crate::MatchTokenMatcher::Regex(regex), eager)
2090 } else if let Some((callback, eager)) = refs.match_tokens.get(source) {
2091 (crate::MatchTokenMatcher::Callback(callback.clone()), *eager)
2092 } else if source.starts_with('@') {
2093 return Err(GrammarError(format!(
2094 "Grammar: unknown token matcher function reference: {source}"
2095 )));
2096 } else {
2097 return Err(GrammarError(format!(
2098 "Grammar: options.match.token.{name} must use @/pattern/flags or a registered function reference"
2099 )));
2100 };
2101 let tin = options
2102 .match_tokens
2103 .get(&name)
2104 .map(|matcher| matcher.tin)
2105 .or_else(|| options.token(&name))
2106 .unwrap_or_else(|| options.next_tin());
2107 options.match_tokens.insert(
2108 name.clone(),
2109 crate::options::MatchToken {
2110 name,
2111 tin,
2112 matcher: matcher.0,
2113 eager: matcher.1,
2114 },
2115 );
2116 }
2117 }
2118 if let Some(values) = match_options.get("value") {
2119 for (name, value) in object(values, "options.match.value")? {
2120 if value.is_null() || value == &JsonValue::Bool(false) {
2121 options.match_values.shift_remove(name);
2122 continue;
2123 }
2124 let value = object(value, &format!("options.match.value.{name}"))?;
2125 let source = value
2126 .get("match")
2127 .and_then(JsonValue::as_str)
2128 .ok_or_else(|| {
2129 GrammarError(format!(
2130 "Grammar: options.match.value.{name}.match must be a serialized regex or function reference"
2131 ))
2132 })?;
2133 let matcher = if serialized_regex(source).is_some() {
2134 let (regex, _) = compile_serialized_regex(
2135 source,
2136 &format!("options.match.value.{name}.match"),
2137 true,
2138 )?;
2139 crate::MatchTokenMatcher::Regex(regex)
2140 } else if let Some(callback) = refs.match_values.get(source) {
2141 crate::MatchTokenMatcher::Callback(callback.clone())
2142 } else if source.starts_with('@') {
2143 return Err(GrammarError(format!(
2144 "Grammar: unknown value matcher function reference: {source}"
2145 )));
2146 } else {
2147 return Err(GrammarError(format!(
2148 "Grammar: options.match.value.{name}.match must use @/pattern/flags or a registered function reference"
2149 )));
2150 };
2151 let (val, transform) = resolve_value_producer(value.get("val"), refs);
2152 options.match_values.insert(
2153 name.clone(),
2154 crate::MatchValue {
2155 name: name.clone(),
2156 matcher,
2157 val,
2158 transform,
2159 },
2160 );
2161 }
2162 options.match_values.sort_keys();
2163 }
2164 }
2165 if let Some(token_sets) = map.get("tokenSet").or_else(|| map.get("token_set")) {
2166 for (name, members) in object(token_sets, "options.tokenSet")? {
2167 let members = members.as_array().ok_or_else(|| {
2175 GrammarError(format!("Grammar: options.tokenSet.{name} must be an array"))
2176 })?;
2177 let key = name.trim_start_matches('#').to_string();
2183 let existing = options.token_set.get(&key).cloned().unwrap_or_default();
2184 let mut tins = Vec::with_capacity(members.len().max(existing.len()));
2185 for member in members {
2186 if member.is_null() {
2187 continue;
2188 }
2189 let member = member.as_str().ok_or_else(|| {
2190 GrammarError(format!(
2191 "Grammar: options.tokenSet.{name} entries must be strings or null"
2192 ))
2193 })?;
2194 let tin = options
2195 .token(member)
2196 .unwrap_or_else(|| options.register_token(member));
2197 tins.push(tin);
2198 }
2199 tins.extend(existing.iter().skip(members.len()).copied());
2200 options.token_set.insert(key, tins);
2201 }
2202 }
2203
2204 if let Some(config) = map.get("config") {
2205 let config = object(config, "options.config")?;
2206 if let Some(modifiers) = config.get("modify") {
2207 for (name, reference) in object(modifiers, "options.config.modify")? {
2208 if reference.is_null() || reference == &JsonValue::Bool(false) {
2209 options.config_modify.shift_remove(name);
2210 continue;
2211 }
2212 let reference = reference.as_str().ok_or_else(|| {
2213 GrammarError(format!(
2214 "Grammar: options.config.modify.{name} must be a function reference or null"
2215 ))
2216 })?;
2217 let modifier = refs
2218 .config_modifiers
2219 .get(reference)
2220 .cloned()
2221 .ok_or_else(|| {
2222 GrammarError(format!(
2223 "Grammar: unknown config modifier function reference: {reference}"
2224 ))
2225 })?;
2226 options.config_modify.insert(name.clone(), modifier);
2227 }
2228 }
2229 }
2230
2231 Ok(())
2232}
2233
2234fn set_bool(map: &Map<String, JsonValue>, key: &str, target: &mut bool) {
2235 if let Some(value) = map.get(key).and_then(JsonValue::as_bool) {
2236 *target = value;
2237 }
2238}
2239
2240fn serialized_regex(source: &str) -> Option<(&str, &str, bool)> {
2241 let (body, eager) = source
2242 .strip_prefix("@~/")
2243 .map(|body| (body, true))
2244 .or_else(|| source.strip_prefix("@/").map(|body| (body, false)))?;
2245 let slash = body.rfind('/')?;
2246 Some((&body[..slash], &body[slash + 1..], eager))
2247}
2248
2249fn compile_serialized_regex(
2250 source: &str,
2251 label: &str,
2252 reject_empty: bool,
2253) -> Result<(regex::Regex, bool), GrammarError> {
2254 let (pattern, flags, eager) = serialized_regex(source)
2255 .ok_or_else(|| GrammarError(format!("Grammar: {label} must use @/pattern/flags")))?;
2256 if flags.contains('v')
2257 || flags
2258 .chars()
2259 .any(|flag| !matches!(flag, 'i' | 'm' | 's' | 'u' | 'g' | 'y' | 'd'))
2260 {
2261 return Err(GrammarError(format!(
2262 "Grammar: unsupported regex flags: {flags}"
2263 )));
2264 }
2265 let mut builder = RegexBuilder::new(pattern);
2266 builder
2267 .case_insensitive(flags.contains('i'))
2268 .multi_line(flags.contains('m'))
2269 .dot_matches_new_line(flags.contains('s'));
2270 let regex = builder
2271 .build()
2272 .map_err(|error| GrammarError(format!("Grammar: invalid regex for {label}: {error}")))?;
2273 if reject_empty && regex.is_match("") {
2274 return Err(GrammarError(format!(
2275 "Grammar: regex for {label} must not match empty input"
2276 )));
2277 }
2278 Ok((regex, eager))
2279}
2280
2281pub fn validate_grammar(rules: &IndexMap<String, RuleSpec>) -> Vec<String> {
2282 let mut problems = Vec::new();
2283 for (name, spec) in rules {
2284 for (state, alts) in [("open", &spec.open), ("close", &spec.close)] {
2285 for (index, alt) in alts.iter().enumerate() {
2286 for referred in [&alt.p, &alt.r].into_iter().flatten() {
2287 if !referred.starts_with('@') && !rules.contains_key(referred) {
2288 problems.push(format!(
2289 "{name}.{state} alt[{index}]: unknown rule: {referred}"
2290 ));
2291 }
2292 }
2293 }
2294 }
2295 }
2296 problems.sort_by(|left, right| left.encode_utf16().cmp(right.encode_utf16()));
2297 problems
2298}