Skip to main content

tabnas/
options.rs

1// Copyright (c) 2013-2026 Richard Rodger, MIT License
2
3use crate::token::{Tin, TIN_CM, TIN_LN, TIN_MAX, TIN_NR, TIN_SP, TIN_ST, TIN_TX, TIN_VL};
4use indexmap::IndexMap;
5use regex::Regex;
6use std::collections::HashMap;
7use std::fmt;
8use std::sync::Arc;
9
10use crate::context::Context;
11use crate::lexer::Lexer;
12use crate::rule::Rule;
13use crate::token::Token;
14
15type ConfigModifierCallback = dyn Fn(&mut Options, &Options) + Send + Sync;
16
17#[derive(Clone)]
18pub struct ConfigModifier {
19    callback: Arc<ConfigModifierCallback>,
20}
21
22impl ConfigModifier {
23    pub(crate) fn new(callback: impl Fn(&mut Options) + Send + Sync + 'static) -> Self {
24        Self {
25            callback: Arc::new(move |config, _options| callback(config)),
26        }
27    }
28
29    pub(crate) fn with_options(
30        callback: impl Fn(&mut Options, &Options) + Send + Sync + 'static,
31    ) -> Self {
32        Self {
33            callback: Arc::new(callback),
34        }
35    }
36
37    pub(crate) fn run(&self, config: &mut Options, options: &Options) {
38        (self.callback)(config, options);
39    }
40
41    pub(crate) fn same_callback(&self, other: &Self) -> bool {
42        Arc::ptr_eq(&self.callback, &other.callback)
43    }
44}
45
46impl fmt::Debug for ConfigModifier {
47    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
48        formatter.write_str("ConfigModifier(<function>)")
49    }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct ColorOptions {
54    pub active: bool,
55    pub reset: String,
56    pub hi: String,
57    pub lo: String,
58    pub line: String,
59}
60
61impl ColorOptions {
62    pub(crate) fn codes(&self) -> (&str, &str, &str, &str) {
63        if self.active {
64            (&self.hi, &self.lo, &self.line, &self.reset)
65        } else {
66            ("", "", "", "")
67        }
68    }
69}
70
71impl Default for ColorOptions {
72    fn default() -> Self {
73        Self {
74            active: true,
75            reset: "\x1b[0m".into(),
76            hi: "\x1b[91m".into(),
77            lo: "\x1b[2m".into(),
78            line: "\x1b[34m".into(),
79        }
80    }
81}
82
83#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct ErrorSuffixContext {
85    pub code: String,
86    pub source: String,
87    pub message: String,
88    pub hint: String,
89    pub pos: usize,
90    pub row: usize,
91    pub col: usize,
92    pub name: String,
93    pub tag: String,
94    pub rule: String,
95    pub rule_state: String,
96    pub token: String,
97    pub why: String,
98    pub plugins: Vec<String>,
99    pub color: ColorOptions,
100}
101
102pub type ErrorSuffixCallback = Arc<dyn Fn(&ErrorSuffixContext) -> String + Send + Sync>;
103
104#[derive(Clone)]
105pub enum ErrorSuffix {
106    Standard,
107    Disabled,
108    Text(String),
109    Callback(ErrorSuffixCallback),
110}
111
112impl fmt::Debug for ErrorSuffix {
113    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
114        match self {
115            Self::Standard => formatter.write_str("Standard"),
116            Self::Disabled => formatter.write_str("Disabled"),
117            Self::Text(text) => formatter.debug_tuple("Text").field(text).finish(),
118            Self::Callback(_) => formatter.write_str("Callback(<function>)"),
119        }
120    }
121}
122
123impl PartialEq for ErrorSuffix {
124    fn eq(&self, other: &Self) -> bool {
125        match (self, other) {
126            (Self::Standard, Self::Standard) | (Self::Disabled, Self::Disabled) => true,
127            (Self::Text(left), Self::Text(right)) => left == right,
128            (Self::Callback(left), Self::Callback(right)) => Arc::ptr_eq(left, right),
129            _ => false,
130        }
131    }
132}
133
134impl Eq for ErrorSuffix {}
135
136#[derive(Debug, Clone, PartialEq, Eq)]
137pub struct ErrMsgOptions {
138    pub name: String,
139    pub suffix: ErrorSuffix,
140    pub link: String,
141}
142
143impl Default for ErrMsgOptions {
144    fn default() -> Self {
145        Self {
146            name: "tabnas".into(),
147            suffix: ErrorSuffix::Standard,
148            link: String::new(),
149        }
150    }
151}
152
153#[derive(Debug, Clone, PartialEq)]
154pub struct LexCheckToken {
155    pub name: String,
156    pub tin: Tin,
157    pub source: String,
158    pub value: crate::Value,
159}
160
161impl LexCheckToken {
162    pub fn new(
163        name: impl Into<String>,
164        tin: Tin,
165        source: impl Into<String>,
166        value: crate::Value,
167    ) -> Self {
168        Self {
169            name: name.into(),
170            tin,
171            source: source.into(),
172            value,
173        }
174    }
175
176    /// Build a token effect whose numeric identity is resolved from its name
177    /// after the serialized grammar has declared that token.
178    pub fn named(name: impl Into<String>, source: impl Into<String>, value: crate::Value) -> Self {
179        let name = name.into();
180        Self {
181            name: if name.starts_with('#') {
182                name
183            } else {
184                format!("#{name}")
185            },
186            tin: -1,
187            source: source.into(),
188            value,
189        }
190    }
191}
192
193/// Effect returned by a matcher-family preflight hook.
194#[derive(Debug, Clone, PartialEq)]
195pub enum LexCheckResult {
196    /// Run the matcher's normal implementation.
197    Continue,
198    /// Skip this matcher and try the next matcher family.
199    Skip,
200    /// Emit an owned token while consuming its non-empty source prefix.
201    Token(Box<LexCheckToken>),
202    /// Return a token built by a live lexer callback. The callback owns all
203    /// cursor movement, matching the canonical `LexCheck(lex)` contract.
204    NativeToken(Box<Token>),
205}
206
207impl LexCheckResult {
208    pub fn token(token: LexCheckToken) -> Self {
209        Self::Token(Box::new(token))
210    }
211
212    pub fn native_token(token: Token) -> Self {
213        Self::NativeToken(Box::new(token))
214    }
215}
216
217pub type ImperativeLexCheck =
218    Arc<dyn for<'source> Fn(&mut Lexer<'source>) -> LexCheckResult + Send + Sync>;
219
220#[derive(Clone)]
221pub struct LexCheck {
222    callback: Option<Arc<LexCheckEffect>>,
223    imperative: Option<ImperativeLexCheck>,
224}
225
226type LexCheckEffect = dyn Fn(&str) -> LexCheckResult + Send + Sync;
227
228impl LexCheck {
229    pub(crate) fn new(callback: impl Fn(&str) -> LexCheckResult + Send + Sync + 'static) -> Self {
230        Self {
231            callback: Some(Arc::new(callback)),
232            imperative: None,
233        }
234    }
235
236    pub(crate) fn new_imperative(
237        callback: impl for<'source> Fn(&mut Lexer<'source>) -> LexCheckResult + Send + Sync + 'static,
238    ) -> Self {
239        Self {
240            callback: None,
241            imperative: Some(Arc::new(callback)),
242        }
243    }
244
245    pub(crate) fn run(&self, source: &str) -> Option<LexCheckResult> {
246        self.callback.as_ref().map(|callback| callback(source))
247    }
248
249    pub(crate) fn run_imperative(&self, lexer: &mut Lexer<'_>) -> Option<LexCheckResult> {
250        self.imperative.as_ref().map(|callback| callback(lexer))
251    }
252
253    pub(crate) fn same_callback(&self, other: &Self) -> bool {
254        match (
255            &self.callback,
256            &self.imperative,
257            &other.callback,
258            &other.imperative,
259        ) {
260            (Some(left), None, Some(right), None) => Arc::ptr_eq(left, right),
261            (None, Some(left), None, Some(right)) => Arc::ptr_eq(left, right),
262            _ => false,
263        }
264    }
265}
266
267impl fmt::Debug for LexCheck {
268    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
269        formatter.write_str(if self.imperative.is_some() {
270            "LexCheck(<live lexer function>)"
271        } else {
272            "LexCheck(<effect function>)"
273        })
274    }
275}
276
277#[derive(Debug, Clone)]
278pub struct FixedToken {
279    pub name: String,
280    pub tin: Tin,
281    pub source: String,
282}
283
284#[derive(Debug, Clone)]
285pub struct FixedOptions {
286    pub lex: bool,
287    pub tokens: IndexMap<String, FixedToken>,
288    pub check: Option<LexCheck>,
289}
290
291impl Default for FixedOptions {
292    fn default() -> Self {
293        let mut tokens = IndexMap::new();
294        for (name, tin, source) in [
295            ("#OB", crate::token::TIN_OB, "{"),
296            ("#CB", crate::token::TIN_CB, "}"),
297            ("#OS", crate::token::TIN_OS, "["),
298            ("#CS", crate::token::TIN_CS, "]"),
299            ("#CL", crate::token::TIN_CL, ":"),
300            ("#CA", crate::token::TIN_CA, ","),
301        ] {
302            tokens.insert(
303                name.to_string(),
304                FixedToken {
305                    name: name.to_string(),
306                    tin,
307                    source: source.to_string(),
308                },
309            );
310        }
311        Self {
312            lex: true,
313            tokens,
314            check: None,
315        }
316    }
317}
318
319pub type ValueTextModifier = Arc<dyn Fn(crate::Value) -> crate::Value + Send + Sync>;
320pub type ImperativeTextModifier = Arc<
321    dyn for<'source> Fn(
322            crate::Value,
323            &mut Lexer<'source>,
324            &mut Rule,
325            &mut Context,
326            &Options,
327        ) -> crate::Value
328        + Send
329        + Sync,
330>;
331
332/// One unquoted-text modifier in declaration order. The value-only form is
333/// convenient for pure serialized hooks; the imperative form exposes the
334/// same live lexer/config state as the canonical `ValModifier` and also hands
335/// Rust callers explicit rule/context references instead of hiding them on
336/// the lexer object.
337#[derive(Clone)]
338pub enum TextModifier {
339    Value(ValueTextModifier),
340    Imperative(ImperativeTextModifier),
341}
342
343impl TextModifier {
344    pub(crate) fn new(
345        modifier: impl Fn(crate::Value) -> crate::Value + Send + Sync + 'static,
346    ) -> Self {
347        Self::Value(Arc::new(modifier))
348    }
349
350    pub(crate) fn new_imperative(
351        modifier: impl for<'source> Fn(
352                crate::Value,
353                &mut Lexer<'source>,
354                &mut Rule,
355                &mut Context,
356                &Options,
357            ) -> crate::Value
358            + Send
359            + Sync
360            + 'static,
361    ) -> Self {
362        Self::Imperative(Arc::new(modifier))
363    }
364
365    pub(crate) fn run(
366        &self,
367        value: crate::Value,
368        lexer: &mut Lexer<'_>,
369        rule: &mut Rule,
370        context: &mut Context,
371        options: &Options,
372    ) -> crate::Value {
373        match self {
374            Self::Value(modifier) => modifier(value),
375            Self::Imperative(modifier) => modifier(value, lexer, rule, context, options),
376        }
377    }
378
379    pub(crate) fn same_callback(&self, other: &Self) -> bool {
380        match (self, other) {
381            (Self::Value(left), Self::Value(right)) => Arc::ptr_eq(left, right),
382            (Self::Imperative(left), Self::Imperative(right)) => Arc::ptr_eq(left, right),
383            _ => false,
384        }
385    }
386}
387
388impl fmt::Debug for TextModifier {
389    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
390        formatter.write_str(match self {
391            Self::Value(_) => "TextModifier::Value(<function>)",
392            Self::Imperative(_) => "TextModifier::Imperative(<function>)",
393        })
394    }
395}
396
397#[derive(Clone)]
398pub struct TextOptions {
399    pub lex: bool,
400    pub modify: Vec<TextModifier>,
401    pub check: Option<LexCheck>,
402}
403
404impl fmt::Debug for TextOptions {
405    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
406        formatter
407            .debug_struct("TextOptions")
408            .field("lex", &self.lex)
409            .field("modify", &self.modify.len())
410            .field("check", &self.check)
411            .finish()
412    }
413}
414
415impl Default for TextOptions {
416    fn default() -> Self {
417        TextOptions {
418            lex: true,
419            modify: Vec::new(),
420            check: None,
421        }
422    }
423}
424
425#[derive(Debug, Clone)]
426pub struct SpaceOptions {
427    pub lex: bool,
428    pub chars: String,
429    pub check: Option<LexCheck>,
430}
431
432impl Default for SpaceOptions {
433    fn default() -> Self {
434        Self {
435            lex: true,
436            chars: " \t".into(),
437            check: None,
438        }
439    }
440}
441
442#[derive(Debug, Clone)]
443pub struct NumberOptions {
444    pub lex: bool,
445    pub hex: bool,
446    pub oct: bool,
447    pub bin: bool,
448    pub sep: Option<String>,
449    pub exclude: Option<String>, // regex string e.g. "^00+"
450    pub check: Option<LexCheck>,
451}
452
453impl Default for NumberOptions {
454    fn default() -> Self {
455        NumberOptions {
456            lex: true,
457            hex: true,
458            oct: true,
459            bin: true,
460            sep: Some("_".to_string()),
461            exclude: None,
462            check: None,
463        }
464    }
465}
466
467#[derive(Debug, Clone)]
468pub struct StringOptions {
469    pub lex: bool,
470    pub chars: String,
471    pub multi_chars: String,
472    pub escape_char: char,
473    pub escape: HashMap<char, String>,
474    pub replace: HashMap<char, String>,
475    pub allow_unknown: bool,
476    pub escape_strict: bool,
477    pub allow_control: bool,
478    pub abandon: bool,
479    pub check: Option<LexCheck>,
480}
481
482impl Default for StringOptions {
483    fn default() -> Self {
484        let escape = [
485            ('b', "\u{0008}"),
486            ('f', "\u{000c}"),
487            ('n', "\n"),
488            ('r', "\r"),
489            ('t', "\t"),
490            ('v', "\u{000b}"),
491            ('"', "\""),
492            ('\'', "'"),
493            ('`', "`"),
494            ('\\', "\\"),
495            ('/', "/"),
496        ]
497        .into_iter()
498        .map(|(key, value)| (key, value.into()))
499        .collect();
500        StringOptions {
501            lex: true,
502            chars: "\"'`".to_string(),
503            multi_chars: "`".to_string(),
504            escape_char: '\\',
505            escape,
506            replace: HashMap::new(),
507            allow_unknown: true,
508            escape_strict: false,
509            allow_control: false,
510            abandon: false,
511            check: None,
512        }
513    }
514}
515
516#[derive(Debug, Clone)]
517pub struct LineOptions {
518    pub lex: bool,
519    pub chars: String,
520    pub row_chars: String,
521    pub single: bool,
522    /// Extra line terminators retained for compatibility with the first
523    /// Rust slice. Serialized grammars should prefer `line.chars`.
524    pub fixed: Vec<char>,
525    pub check: Option<LexCheck>,
526}
527
528impl Default for LineOptions {
529    fn default() -> Self {
530        Self {
531            lex: true,
532            chars: "\r\n".into(),
533            row_chars: "\n".into(),
534            single: false,
535            fixed: Vec::new(),
536            check: None,
537        }
538    }
539}
540
541#[derive(Debug, Clone)]
542pub struct CommentDef {
543    pub line: bool,
544    pub start: String,
545    pub end: String,
546    pub lex: bool,
547    pub suffixes: Vec<String>,
548    pub suffix_matcher: Option<CommentSuffixMatcher>,
549    pub eat_line: bool,
550}
551
552#[derive(Clone)]
553pub struct CommentSuffixMatcher {
554    callback: Option<Arc<CommentSuffixCallback>>,
555    imperative: Option<ImperativeCommentSuffixMatcher>,
556}
557
558type CommentSuffixCallback = dyn Fn(&str) -> Option<String> + Send + Sync;
559pub type ImperativeCommentSuffixMatcher =
560    Arc<dyn for<'source> Fn(&mut Lexer<'source>) -> Option<Token> + Send + Sync>;
561
562impl CommentSuffixMatcher {
563    pub(crate) fn new(callback: impl Fn(&str) -> Option<String> + Send + Sync + 'static) -> Self {
564        Self {
565            callback: Some(Arc::new(callback)),
566            imperative: None,
567        }
568    }
569
570    pub(crate) fn new_imperative(
571        callback: impl for<'source> Fn(&mut Lexer<'source>) -> Option<Token> + Send + Sync + 'static,
572    ) -> Self {
573        Self {
574            callback: None,
575            imperative: Some(Arc::new(callback)),
576        }
577    }
578
579    pub(crate) fn run(&self, source: &str) -> Option<String> {
580        self.callback.as_ref().and_then(|callback| callback(source))
581    }
582
583    pub(crate) fn run_imperative(&self, lexer: &mut Lexer<'_>) -> Option<Token> {
584        self.imperative
585            .as_ref()
586            .and_then(|callback| callback(lexer))
587    }
588
589    pub(crate) fn same_callback(&self, other: &Self) -> bool {
590        match (
591            &self.callback,
592            &self.imperative,
593            &other.callback,
594            &other.imperative,
595        ) {
596            (Some(left), None, Some(right), None) => Arc::ptr_eq(left, right),
597            (None, Some(left), None, Some(right)) => Arc::ptr_eq(left, right),
598            _ => false,
599        }
600    }
601}
602
603impl fmt::Debug for CommentSuffixMatcher {
604    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
605        formatter.write_str(if self.imperative.is_some() {
606            "CommentSuffixMatcher(<live lexer function>)"
607        } else {
608            "CommentSuffixMatcher(<effect function>)"
609        })
610    }
611}
612
613#[derive(Debug, Clone)]
614pub struct CommentOptions {
615    pub lex: bool,
616    pub definitions: IndexMap<String, CommentDef>,
617    pub check: Option<LexCheck>,
618}
619
620#[derive(Clone)]
621pub struct ValueDef {
622    pub val: Option<crate::Value>,
623    pub matcher: Option<Regex>,
624    pub transform: Option<ValueTransform>,
625    pub consume: bool,
626}
627
628pub type ValueTransform = Arc<dyn Fn(&[String]) -> crate::Value + Send + Sync>;
629
630impl fmt::Debug for ValueDef {
631    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
632        formatter
633            .debug_struct("ValueDef")
634            .field("val", &self.val)
635            .field("matcher", &self.matcher)
636            .field("transform", &self.transform.as_ref().map(|_| "<function>"))
637            .field("consume", &self.consume)
638            .finish()
639    }
640}
641
642#[derive(Debug, Clone)]
643pub struct ValueOptions {
644    pub lex: bool,
645    pub definitions: IndexMap<String, ValueDef>,
646}
647
648impl Default for ValueOptions {
649    fn default() -> Self {
650        let mut definitions = IndexMap::new();
651        for (source, value) in [
652            ("true", crate::Value::Bool(true)),
653            ("false", crate::Value::Bool(false)),
654            ("null", crate::Value::Null),
655        ] {
656            definitions.insert(
657                source.into(),
658                ValueDef {
659                    val: Some(value),
660                    matcher: None,
661                    transform: None,
662                    consume: false,
663                },
664            );
665        }
666        Self {
667            lex: true,
668            definitions,
669        }
670    }
671}
672
673impl Default for CommentOptions {
674    fn default() -> Self {
675        let mut definitions = IndexMap::new();
676        for (name, line, start, end) in [
677            ("hash", true, "#", ""),
678            ("slash", true, "//", ""),
679            ("multi", false, "/*", "*/"),
680        ] {
681            definitions.insert(
682                name.into(),
683                CommentDef {
684                    line,
685                    start: start.into(),
686                    end: end.into(),
687                    lex: true,
688                    suffixes: Vec::new(),
689                    suffix_matcher: None,
690                    eat_line: false,
691                },
692            );
693        }
694        CommentOptions {
695            lex: true,
696            definitions,
697            check: None,
698        }
699    }
700}
701
702#[derive(Debug, Clone)]
703pub struct SafeOptions {
704    pub key: bool,
705}
706
707impl Default for SafeOptions {
708    fn default() -> Self {
709        Self { key: true }
710    }
711}
712
713pub type MapMerge =
714    Arc<dyn Fn(crate::Value, crate::Value, &mut Rule, &mut Context) -> crate::Value + Send + Sync>;
715
716#[derive(Clone)]
717pub struct MapOptions {
718    pub extend: bool,
719    pub merge: Option<MapMerge>,
720    pub child: bool,
721    /// Rust's `IndexMap` always retains insertion order. This flag is kept so
722    /// serialized option overlays and plugin code observe the canonical
723    /// option surface even though enabling it requires no representation
724    /// change here.
725    pub ordered: bool,
726}
727
728impl fmt::Debug for MapOptions {
729    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
730        formatter
731            .debug_struct("MapOptions")
732            .field("extend", &self.extend)
733            .field("merge", &self.merge.as_ref().map(|_| "<function>"))
734            .field("child", &self.child)
735            .field("ordered", &self.ordered)
736            .finish()
737    }
738}
739
740impl Default for MapOptions {
741    fn default() -> Self {
742        MapOptions {
743            extend: true,
744            merge: None,
745            child: false,
746            ordered: false,
747        }
748    }
749}
750
751#[derive(Debug, Clone)]
752pub struct ListOptions {
753    pub property: bool,
754    pub pair: bool,
755    pub child: bool,
756}
757
758impl Default for ListOptions {
759    fn default() -> Self {
760        Self {
761            property: true,
762            pair: false,
763            child: false,
764        }
765    }
766}
767
768/// Controls typed metadata carriers on native parse results.
769///
770/// The wrappers serialize as their underlying JSON values, matching the
771/// TypeScript implementation's non-enumerable metadata marker.
772#[derive(Debug, Clone)]
773pub struct InfoOptions {
774    pub map: bool,
775    pub list: bool,
776    pub text: bool,
777    pub marker: String,
778}
779
780impl Default for InfoOptions {
781    fn default() -> Self {
782        Self {
783            map: false,
784            list: false,
785            text: false,
786            marker: "__info__".into(),
787        }
788    }
789}
790
791#[derive(Debug, Clone)]
792pub struct LexOptions {
793    pub empty: bool,
794    pub empty_result: crate::Value,
795    pub relex: bool,
796    pub matchers: IndexMap<String, LexMatcher>,
797}
798
799#[derive(Debug, Clone)]
800pub struct RewindOptions {
801    /// Maximum retained consumed-token history. `None` is unbounded,
802    /// the serialized `false`; `Some(0)` retains nothing; the default is
803    /// `Some(64)`, which a serialized `null` also means. `Some(0)` used
804    /// to be read as unbounded, the opposite of the canonical runtime
805    /// (#142), and `null` as unbounded rather than the default (#144).
806    pub history: Option<usize>,
807}
808
809impl Default for RewindOptions {
810    fn default() -> Self {
811        Self { history: Some(64) }
812    }
813}
814
815impl Default for LexOptions {
816    fn default() -> Self {
817        LexOptions {
818            empty: true,
819            empty_result: crate::Value::Undefined,
820            relex: false,
821            matchers: IndexMap::new(),
822        }
823    }
824}
825
826#[derive(Debug, Clone, Default)]
827pub struct ResultOptions {
828    pub fail: Vec<crate::Value>,
829}
830
831/// The largest `options.rule.history` a rule's links honour. Every
832/// replace and push copies up to `history` snapshots, so the bound is
833/// the cost of each link: capped, a parse stays linear in its length
834/// whatever the bound, where a bound as long as the sequence copied
835/// `1 + 2 + … + N` snapshots. Sixteen is five times the deepest walk the
836/// fleet reads (three hops), and the largest bound measured faster than
837/// no bound at all: over 300,000 array items a release build took 2.2 s
838/// at 16 and 2.8 s unbounded, 3.3 s at 32 and 6.2 s at 64
839/// (`doc/rule-history-bound.md`). A grammar that asks for more is
840/// refused; a larger value set on [`RuleOptions::history`] directly is
841/// read as this, and `Some(0)` as 1.
842pub const MAX_RULE_HISTORY: usize = 16;
843
844/// The bound a rule's links honour for `history`: 1 to
845/// [`MAX_RULE_HISTORY`], or `None` for every link. Read wherever the
846/// option decides something, the links themselves and a merge, so two
847/// values that parse alike are alike everywhere.
848pub(crate) fn effective_rule_history(history: Option<usize>) -> Option<usize> {
849    history.map(|history| history.clamp(1, MAX_RULE_HISTORY))
850}
851
852#[derive(Debug, Clone)]
853pub struct RuleOptions {
854    pub finish: bool,
855    pub maxmul: usize,
856    pub include: String,
857    pub exclude: String,
858    pub start: String,
859    /// How many predecessor snapshots a rule can reach through `prev`:
860    /// the `prev_rule` chain a replace or a push links is cut past this
861    /// many links, so a sequence's iterations are not all kept until its
862    /// container closes. `None` keeps every link, which is the canonical
863    /// behaviour and the default (`doc/rule-history-bound.md`). Read as
864    /// 1 to [`MAX_RULE_HISTORY`]: `Some(0)` as 1, a larger value as the
865    /// cap.
866    pub history: Option<usize>,
867}
868
869#[derive(Debug, Clone)]
870pub struct MatchToken {
871    pub name: String,
872    pub tin: Tin,
873    pub matcher: MatchTokenMatcher,
874    pub eager: bool,
875}
876
877#[derive(Clone)]
878pub struct MatchValue {
879    pub name: String,
880    pub matcher: MatchTokenMatcher,
881    pub val: Option<crate::Value>,
882    pub transform: Option<ValueTransform>,
883}
884
885impl fmt::Debug for MatchValue {
886    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
887        formatter
888            .debug_struct("MatchValue")
889            .field("name", &self.name)
890            .field("matcher", &self.matcher)
891            .field("val", &self.val)
892            .field("transform", &self.transform.as_ref().map(|_| "<function>"))
893            .finish()
894    }
895}
896
897#[derive(Debug, Clone, PartialEq)]
898pub struct MatchTokenResult {
899    /// Non-empty source prefix consumed by the match.
900    pub source: String,
901    /// Token value exposed to rule actions.
902    pub value: crate::Value,
903}
904
905impl MatchTokenResult {
906    pub fn new(source: impl Into<String>, value: crate::Value) -> Self {
907        Self {
908            source: source.into(),
909            value,
910        }
911    }
912}
913
914pub type MatchTokenCallback = Arc<dyn Fn(&str) -> Option<MatchTokenResult> + Send + Sync>;
915
916/// Effect-based custom lexer matcher used by serialized `options.lex.match`
917/// entries. The returned token must consume a non-empty prefix of the
918/// remaining source.
919pub type LexMatcherCallback = Arc<dyn Fn(&str) -> Option<LexCheckToken> + Send + Sync>;
920
921/// Full native custom matcher. Unlike the serialized effect callback, this
922/// form can inspect and mutate the live rule/context and advance the lexer.
923pub type ImperativeLexMatcher = Arc<
924    dyn for<'source> Fn(&mut Lexer<'source>, &mut Rule, &mut Context) -> Option<Token>
925        + Send
926        + Sync,
927>;
928
929/// Setup-time matcher constructor. Rust has one resolved typed option tree,
930/// so it serves the roles of both canonical `cfg` and raw `opts` arguments.
931pub type LexMatcherFactory = Arc<dyn Fn(&Options) -> Option<ImperativeLexMatcher> + Send + Sync>;
932
933#[derive(Clone)]
934pub struct LexMatcher {
935    pub name: String,
936    pub order: f64,
937    pub matcher: Option<LexMatcherCallback>,
938    pub imperative: Option<ImperativeLexMatcher>,
939    pub factory: Option<LexMatcherFactory>,
940}
941
942impl fmt::Debug for LexMatcher {
943    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
944        formatter
945            .debug_struct("LexMatcher")
946            .field("name", &self.name)
947            .field("order", &self.order)
948            .field("matcher", &self.matcher.as_ref().map(|_| "<function>"))
949            .field(
950                "imperative",
951                &self.imperative.as_ref().map(|_| "<function>"),
952            )
953            .field("factory", &self.factory.as_ref().map(|_| "<function>"))
954            .finish()
955    }
956}
957
958#[derive(Clone)]
959pub enum MatchTokenMatcher {
960    Regex(Regex),
961    Callback(MatchTokenCallback),
962}
963
964impl fmt::Debug for MatchTokenMatcher {
965    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
966        match self {
967            Self::Regex(regex) => formatter.debug_tuple("Regex").field(regex).finish(),
968            Self::Callback(_) => formatter.write_str("Callback(<function>)"),
969        }
970    }
971}
972
973impl Default for RuleOptions {
974    fn default() -> Self {
975        RuleOptions {
976            finish: true,
977            maxmul: 3,
978            include: String::new(),
979            exclude: String::new(),
980            start: "val".to_string(),
981            history: None,
982        }
983    }
984}
985
986pub type BudgetCheck = Arc<dyn Fn(&Context) -> bool + Send + Sync>;
987
988#[derive(Clone, Default)]
989pub struct BudgetOptions {
990    pub check_every_n: usize,
991    pub on_check: Option<BudgetCheck>,
992}
993
994impl fmt::Debug for BudgetOptions {
995    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
996        formatter
997            .debug_struct("BudgetOptions")
998            .field("check_every_n", &self.check_every_n)
999            .field("on_check", &self.on_check.as_ref().map(|_| "<callback>"))
1000            .finish()
1001    }
1002}
1003
1004#[derive(Debug, Clone)]
1005pub struct RecoverOptions {
1006    pub enabled: bool,
1007    pub sync_groups: Vec<String>,
1008    pub sync_tokens: Vec<String>,
1009    pub pop_until_valid: bool,
1010    pub max_skip: usize,
1011    pub max_recoveries: usize,
1012    pub suppress: usize,
1013}
1014
1015impl Default for RecoverOptions {
1016    fn default() -> Self {
1017        Self {
1018            enabled: false,
1019            sync_groups: vec!["close".into(), "comma".into(), "end".into()],
1020            sync_tokens: Vec::new(),
1021            pop_until_valid: true,
1022            max_skip: 64,
1023            max_recoveries: 32,
1024            suppress: 4,
1025        }
1026    }
1027}
1028
1029pub type ContextParsePrepare = Arc<dyn Fn(&mut Context) + Send + Sync>;
1030pub type ParsePrepareWithInstance =
1031    Arc<dyn Fn(&crate::Tabnas, &mut Context, &crate::Value) + Send + Sync>;
1032
1033/// Pre-parse hook. The context-only form preserves the original Rust API;
1034/// `WithInstance` exposes the owning parser and caller metadata carried by
1035/// the canonical callback contract.
1036#[derive(Clone)]
1037pub enum ParsePrepare {
1038    Context(ContextParsePrepare),
1039    WithInstance(ParsePrepareWithInstance),
1040}
1041
1042impl ParsePrepare {
1043    pub(crate) fn same_callback(&self, other: &Self) -> bool {
1044        match (self, other) {
1045            (Self::Context(left), Self::Context(right)) => Arc::ptr_eq(left, right),
1046            (Self::WithInstance(left), Self::WithInstance(right)) => Arc::ptr_eq(left, right),
1047            _ => false,
1048        }
1049    }
1050
1051    pub(crate) fn run(
1052        &self,
1053        owner: Option<&crate::Tabnas>,
1054        context: &mut Context,
1055        meta: &crate::Value,
1056    ) -> Result<(), &'static str> {
1057        match self {
1058            Self::Context(callback) => {
1059                callback(context);
1060                Ok(())
1061            }
1062            Self::WithInstance(callback) => {
1063                let owner = owner.ok_or(
1064                    "parse.prepare requires an owning Tabnas instance; call Tabnas::parse",
1065                )?;
1066                callback(owner, context, meta);
1067                Ok(())
1068            }
1069        }
1070    }
1071}
1072
1073impl fmt::Debug for ParsePrepare {
1074    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1075        match self {
1076            Self::Context(_) => formatter.write_str("ParsePrepare::Context(<function>)"),
1077            Self::WithInstance(_) => formatter.write_str("ParsePrepare::WithInstance(<function>)"),
1078        }
1079    }
1080}
1081
1082pub type ParserStart =
1083    Arc<dyn Fn(&str) -> Result<crate::Value, Box<crate::TabnasError>> + Send + Sync>;
1084pub type ParserStartWithInstance = Arc<
1085    dyn Fn(&str, &crate::Tabnas, &crate::Value) -> Result<crate::Value, Box<crate::TabnasError>>
1086        + Send
1087        + Sync,
1088>;
1089pub type ParserStartWithContext = Arc<
1090    dyn Fn(
1091            &str,
1092            &crate::Tabnas,
1093            &crate::Value,
1094            Option<&crate::ContextSeed>,
1095        ) -> Result<crate::Value, Box<crate::TabnasError>>
1096        + Send
1097        + Sync,
1098>;
1099
1100pub type DebugOutput = Arc<dyn Fn(&str) + Send + Sync>;
1101pub type DebugSourceFormatter = Arc<dyn Fn(&crate::Value) -> String + Send + Sync>;
1102
1103#[derive(Clone, Default)]
1104pub struct DebugPrintOptions {
1105    pub config: bool,
1106    pub source: Option<DebugSourceFormatter>,
1107}
1108
1109impl fmt::Debug for DebugPrintOptions {
1110    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1111        formatter
1112            .debug_struct("DebugPrintOptions")
1113            .field("config", &self.config)
1114            .field("source", &self.source.as_ref().map(|_| "<callback>"))
1115            .finish()
1116    }
1117}
1118
1119#[derive(Clone)]
1120pub struct DebugOptions {
1121    /// Maximum Unicode-scalar length of values rendered by the default
1122    /// source formatter.
1123    pub maxlen: usize,
1124    pub print: DebugPrintOptions,
1125    /// Rust equivalent of TypeScript's console provider. `None` writes trace
1126    /// and configuration output to stderr.
1127    pub output: Option<DebugOutput>,
1128}
1129
1130impl Default for DebugOptions {
1131    fn default() -> Self {
1132        Self {
1133            maxlen: 99,
1134            print: DebugPrintOptions::default(),
1135            output: None,
1136        }
1137    }
1138}
1139
1140impl fmt::Debug for DebugOptions {
1141    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1142        formatter
1143            .debug_struct("DebugOptions")
1144            .field("maxlen", &self.maxlen)
1145            .field("print", &self.print)
1146            .field("output", &self.output.as_ref().map(|_| "<callback>"))
1147            .finish()
1148    }
1149}
1150
1151impl DebugOptions {
1152    pub fn format_source(&self, value: &crate::Value) -> String {
1153        if let Some(formatter) = &self.print.source {
1154            return formatter(value);
1155        }
1156        let rendered = match value {
1157            crate::Value::Undefined | crate::Value::Null => String::new(),
1158            value => serde_json::to_string(&value.to_json()).unwrap_or_else(|_| value.to_string()),
1159        };
1160        let mut chars = rendered.chars();
1161        let prefix = chars.by_ref().take(self.maxlen).collect::<String>();
1162        if chars.next().is_some() {
1163            format!("{prefix}...")
1164        } else {
1165            prefix
1166        }
1167    }
1168
1169    pub fn write(&self, message: &str) {
1170        if let Some(output) = &self.output {
1171            // Debugging must not make construction or parsing less reliable.
1172            // Parse-time trace sinks are also protected by the subscriber
1173            // boundary, but configuration printing runs during setup.
1174            let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| output(message)));
1175        } else {
1176            eprintln!("{message}");
1177        }
1178    }
1179}
1180
1181#[derive(Clone, Default)]
1182pub struct ParserOptions {
1183    pub start: Option<ParserStart>,
1184    pub start_with_instance: Option<ParserStartWithInstance>,
1185    pub start_with_context: Option<ParserStartWithContext>,
1186}
1187
1188impl fmt::Debug for ParserOptions {
1189    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1190        formatter
1191            .debug_struct("ParserOptions")
1192            .field("start", &self.start.as_ref().map(|_| "<callback>"))
1193            .field(
1194                "start_with_instance",
1195                &self.start_with_instance.as_ref().map(|_| "<callback>"),
1196            )
1197            .field(
1198                "start_with_context",
1199                &self.start_with_context.as_ref().map(|_| "<callback>"),
1200            )
1201            .finish()
1202    }
1203}
1204
1205#[derive(Clone, Default)]
1206pub struct ParseOptions {
1207    pub prepare: Vec<ParsePrepare>,
1208    pub named_prepare: IndexMap<String, ParsePrepare>,
1209    pub budget: BudgetOptions,
1210    pub recover: RecoverOptions,
1211}
1212
1213impl fmt::Debug for ParseOptions {
1214    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1215        formatter
1216            .debug_struct("ParseOptions")
1217            .field("prepare", &self.prepare.len())
1218            .field("named_prepare", &self.named_prepare.len())
1219            .field("budget", &self.budget)
1220            .field("recover", &self.recover)
1221            .finish()
1222    }
1223}
1224
1225#[derive(Debug, Clone)]
1226pub struct Options {
1227    pub safe: SafeOptions,
1228    pub fixed: FixedOptions,
1229    pub space: SpaceOptions,
1230    pub text: TextOptions,
1231    pub number: NumberOptions,
1232    pub string: StringOptions,
1233    pub line: LineOptions,
1234    pub comment: CommentOptions,
1235    pub value: ValueOptions,
1236    pub ender: Vec<String>,
1237    pub map: MapOptions,
1238    pub list: ListOptions,
1239    pub info: InfoOptions,
1240    pub lex: LexOptions,
1241    pub rewind: RewindOptions,
1242    pub rule: RuleOptions,
1243    pub result: ResultOptions,
1244    pub parse: ParseOptions,
1245    pub parser: ParserOptions,
1246    pub debug: DebugOptions,
1247    /// Per-plugin accumulated option bags (`options.plugin`).
1248    pub plugin: IndexMap<String, crate::Value>,
1249    pub token_set: HashMap<String, Vec<Tin>>,
1250    /// Named token identities without an attached built-in, fixed, or match
1251    /// producer. Serialized rule slots allocate these on first reference.
1252    pub tokens: IndexMap<String, Tin>,
1253    /// Enable serialized/custom regexp token matching (`options.match.lex`).
1254    pub match_lex: bool,
1255    pub match_check: Option<LexCheck>,
1256    pub match_tokens: IndexMap<String, MatchToken>,
1257    pub match_values: IndexMap<String, MatchValue>,
1258    pub error: HashMap<String, String>,
1259    pub hint: HashMap<String, String>,
1260    pub errmsg: ErrMsgOptions,
1261    pub color: ColorOptions,
1262    pub config_modify: IndexMap<String, ConfigModifier>,
1263    pub tag: String,
1264}
1265
1266impl Default for Options {
1267    fn default() -> Self {
1268        let mut token_set = HashMap::new();
1269        token_set.insert("IGNORE".to_string(), vec![TIN_SP, TIN_LN, TIN_CM]);
1270        token_set.insert("VAL".to_string(), vec![TIN_TX, TIN_NR, TIN_ST, TIN_VL]);
1271        token_set.insert("KEY".to_string(), vec![TIN_TX, TIN_NR, TIN_ST, TIN_VL]);
1272
1273        Options {
1274            safe: SafeOptions::default(),
1275            fixed: FixedOptions::default(),
1276            space: SpaceOptions::default(),
1277            text: TextOptions::default(),
1278            number: NumberOptions::default(),
1279            string: StringOptions::default(),
1280            line: LineOptions::default(),
1281            comment: CommentOptions::default(),
1282            value: ValueOptions::default(),
1283            ender: Vec::new(),
1284            map: MapOptions::default(),
1285            list: ListOptions::default(),
1286            info: InfoOptions::default(),
1287            lex: LexOptions::default(),
1288            rewind: RewindOptions::default(),
1289            rule: RuleOptions::default(),
1290            result: ResultOptions::default(),
1291            parse: ParseOptions::default(),
1292            parser: ParserOptions::default(),
1293            debug: DebugOptions::default(),
1294            plugin: IndexMap::new(),
1295            token_set,
1296            tokens: IndexMap::new(),
1297            match_lex: true,
1298            match_check: None,
1299            match_tokens: IndexMap::new(),
1300            match_values: IndexMap::new(),
1301            error: crate::error::default_error_messages(),
1302            hint: crate::error::default_error_hints(),
1303            errmsg: ErrMsgOptions::default(),
1304            color: ColorOptions::default(),
1305            config_modify: IndexMap::new(),
1306            tag: "-".to_string(),
1307        }
1308    }
1309}
1310
1311impl Options {
1312    /// Rebuild the resolved configuration callbacks from this option tree.
1313    /// Config modifiers run before matcher factories, matching canonical
1314    /// `configure`: factories must observe the modifier's final values.
1315    pub fn refresh_configuration(&mut self) -> Result<(), String> {
1316        let raw_options = self.clone();
1317        let modifiers: Vec<_> = self
1318            .config_modify
1319            .iter()
1320            .map(|(name, modifier)| (name.clone(), modifier.clone()))
1321            .collect();
1322        for (name, modifier) in modifiers {
1323            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1324                modifier.run(self, &raw_options)
1325            }))
1326            .map_err(|_| format!("config modifier {name} panicked"))?;
1327        }
1328        self.refresh_lex_matchers()
1329    }
1330
1331    /// Rebuild setup-time lexer matchers from the fully resolved option tree.
1332    /// Factories run once per configuration/derivation, not once per parse.
1333    pub fn refresh_lex_matchers(&mut self) -> Result<(), String> {
1334        let factories: Vec<_> = self
1335            .lex
1336            .matchers
1337            .iter()
1338            .filter_map(|(name, matcher)| {
1339                matcher
1340                    .factory
1341                    .as_ref()
1342                    .map(|factory| (name.clone(), factory.clone()))
1343            })
1344            .collect();
1345        for (name, factory) in factories {
1346            let matcher = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| factory(self)))
1347                .map_err(|_| format!("lexer matcher factory {name} panicked"))?;
1348            if let Some(entry) = self.lex.matchers.get_mut(&name) {
1349                entry.imperative = matcher;
1350            }
1351        }
1352        Ok(())
1353    }
1354
1355    /// Baseline for `Tabnas::empty`: no standard token producers or token
1356    /// sets. Structural and diagnostic fields retain typed values so plugins
1357    /// can opt individual facilities back in without handling missing data.
1358    pub fn empty() -> Self {
1359        let mut options = Self::default();
1360        options.fixed.lex = false;
1361        options.fixed.tokens.clear();
1362        options.space.lex = false;
1363        options.text.lex = false;
1364        options.number.lex = false;
1365        options.string.lex = false;
1366        options.line.lex = false;
1367        options.comment.lex = false;
1368        options.comment.definitions.clear();
1369        options.value.lex = false;
1370        options.value.definitions.clear();
1371        options.match_lex = false;
1372        options.match_tokens.clear();
1373        options.match_values.clear();
1374        options.lex.matchers.clear();
1375        options.token_set.clear();
1376        options.tokens.clear();
1377        options
1378    }
1379
1380    /// Put the serialized matchers in the order the lexer evaluates
1381    /// them. TypeScript evaluates token matchers in tin order; keep
1382    /// that deterministic even when callers assembled `Options` by
1383    /// mutating the public maps directly. Done once per prepared
1384    /// configuration rather than once per lexer.
1385    pub(crate) fn sort_for_lexing(&mut self) {
1386        self.match_tokens
1387            .sort_by(|_, left, _, right| left.tin.cmp(&right.tin));
1388        self.match_values.sort_keys();
1389        self.lex.matchers.sort_by(|name_a, left, name_b, right| {
1390            left.order
1391                .total_cmp(&right.order)
1392                .then_with(|| name_a.cmp(name_b))
1393        });
1394    }
1395
1396    pub fn is_ignored(&self, tin: Tin) -> bool {
1397        self.token_set
1398            .get("IGNORE")
1399            .is_some_and(|ignored| ignored.contains(&tin))
1400    }
1401
1402    /// The ignore set, resolved once. `is_ignored` is asked for every
1403    /// lookahead token, and hashing the literal `"IGNORE"` each time
1404    /// cost more than the answer; the lexer and the parser each take a
1405    /// copy when they are built.
1406    pub(crate) fn ignore_tins(&self) -> Vec<Tin> {
1407        self.token_set.get("IGNORE").cloned().unwrap_or_default()
1408    }
1409
1410    /// The lexer's character classes, tabulated. Built alongside
1411    /// `ignore_tins` and carrying the same caveat: these answer from a
1412    /// snapshot of the options, so whatever holds them has to be rebuilt
1413    /// when the options change. The lexer is, once per parse.
1414    ///
1415    /// `line_ends` merges `line.chars` with `line.fixed`, which is how most
1416    /// sites ask. `line` is `line.chars` ALONE: string lexing decides what
1417    /// is unprintable without consulting `line.fixed`, and folding the two
1418    /// together there would change which characters a string rejects.
1419    pub(crate) fn char_sets(&self) -> crate::text::CharSets {
1420        crate::text::CharSets {
1421            space: crate::text::CharSet::new(&self.space.chars),
1422            line_ends: crate::text::CharSet::with_extra(&self.line.chars, &self.line.fixed),
1423            line: crate::text::CharSet::new(&self.line.chars),
1424            row: crate::text::CharSet::new(&self.line.row_chars),
1425            string: crate::text::CharSet::new(&self.string.chars),
1426        }
1427    }
1428
1429    pub fn token(&self, name: &str) -> Option<Tin> {
1430        crate::token::name_to_tin(name).or_else(|| {
1431            let name = if name.starts_with('#') {
1432                name.to_string()
1433            } else {
1434                format!("#{name}")
1435            };
1436            self.tokens
1437                .get(&name)
1438                .copied()
1439                .or_else(|| self.match_tokens.get(&name).map(|matcher| matcher.tin))
1440                .or_else(|| self.fixed.tokens.get(&name).map(|token| token.tin))
1441        })
1442    }
1443
1444    pub fn register_token(&mut self, name: impl Into<String>) -> Tin {
1445        let name = name.into();
1446        let name = if name.starts_with('#') {
1447            name
1448        } else {
1449            format!("#{name}")
1450        };
1451        if let Some(tin) = self.token(&name) {
1452            return tin;
1453        }
1454        let tin = self.next_tin();
1455        self.tokens.insert(name, tin);
1456        tin
1457    }
1458
1459    pub fn next_tin(&self) -> Tin {
1460        self.match_tokens
1461            .values()
1462            .map(|matcher| matcher.tin)
1463            .chain(self.fixed.tokens.values().map(|token| token.tin))
1464            .chain(self.tokens.values().copied())
1465            .max()
1466            .unwrap_or(TIN_MAX - 1)
1467            + 1
1468    }
1469
1470    pub fn token_name(&self, tin: Tin) -> String {
1471        self.match_tokens
1472            .values()
1473            .find(|matcher| matcher.tin == tin)
1474            .map(|matcher| matcher.name.clone())
1475            .or_else(|| {
1476                self.fixed
1477                    .tokens
1478                    .values()
1479                    .find(|token| token.tin == tin)
1480                    .map(|token| token.name.clone())
1481            })
1482            .or_else(|| {
1483                self.tokens
1484                    .iter()
1485                    .find(|(_, token_tin)| **token_tin == tin)
1486                    .map(|(name, _)| name.clone())
1487            })
1488            .unwrap_or_else(|| crate::token::tin_name(tin).to_string())
1489    }
1490}