Skip to main content

granit_parser/
options.rs

1/// Options controlling parser and scanner behavior and resource usage.
2///
3/// Construct this type with [`crate::options!`] so that code remains compatible when new options
4/// are added in future releases.
5///
6/// # Examples
7///
8/// ```rust
9/// let options = granit_parser::options! {
10///     strict_indentation: true,
11///     flow_nesting_limit: 512,
12///     block_nesting_limit: 256,
13/// };
14///
15/// assert!(options.strict_indentation);
16/// assert_eq!(options.flow_nesting_limit, 512);
17/// assert_eq!(options.block_nesting_limit, 256);
18/// ```
19#[non_exhaustive]
20#[derive(Clone, Debug, Eq, PartialEq)]
21#[allow(clippy::struct_excessive_bools)] // Independent, opt-in parsing policies.
22pub struct Options {
23    /// Whether to enforce strict YAML indentation rules for flow collections, scalar
24    /// continuations, and content following explicit-key anchor or tag properties.
25    ///
26    /// The default parsing is compatible with `PyYAML` and `ruamel.yaml`.
27    /// `true` sets stricter indentation rules as required by the YAML specification,
28    /// some non-compliant YAML that passes with the two libraries above will fail.
29    /// Default is false.
30    pub strict_indentation: bool,
31    /// Reject all YAML directives, including reserved directives.
32    ///
33    /// When enabled, directive syntax recognized by the scanner in any document reports
34    /// [`crate::ErrorKind::DirectiveDisallowed`] at the directive's `%` indicator.
35    /// Percent signs inside scalars and comments are unaffected.
36    /// Defaults to false.
37    pub disallow_directives: bool,
38    /// Emit unresolved alias nodes instead of stopping at an unknown anchor.
39    ///
40    /// Defaults to `false`. When omitted or explicitly set to `false`, an alias without a
41    /// preceding anchor definition in its document reports [`crate::ErrorKind::UnknownAnchor`]
42    /// and stops parsing, preserving the default behavior.
43    ///
44    /// When `true`, the parser emits [`crate::Event::UnresolvedAlias`] with the original name
45    /// (without `*`) and a span covering the complete alias token, then continues structural
46    /// parsing. Consumers must handle this event as a node, including in mapping-key and
47    /// mapping-value positions; silently ignoring it in a wildcard match can lose structure.
48    /// Successful parsing in this mode does not establish full YAML validity: unresolved aliases
49    /// remain invalid references, whose diagnostics the consumer can report separately.
50    ///
51    /// Resolved aliases still emit [`crate::Event::Alias`]. Each alias resolves against the most
52    /// recent preceding definition in the same document. An unresolved occurrence neither
53    /// allocates an anchor ID nor becomes resolved by a later definition. Repeated missing names
54    /// produce separate unresolved events, and anchor names do not carry across documents.
55    /// Scanning, grammar, input, and resource-limit errors still stop parsing normally.
56    /// This option has no effect on a scanner used directly.
57    ///
58    /// ```rust
59    /// use granit_parser::{Event, Parser};
60    ///
61    /// let options = granit_parser::options! { emit_unresolved_aliases: true };
62    /// let events = Parser::new_from_str_with_options("[*missing, value]", options)
63    ///     .collect::<Result<Vec<_>, _>>()?;
64    /// assert!(events.iter().any(|(event, _)| {
65    ///     matches!(event, Event::UnresolvedAlias(name) if name == "missing")
66    /// }));
67    /// assert!(matches!(events.last(), Some((Event::StreamEnd, _))));
68    /// # Ok::<(), granit_parser::ScanError>(())
69    /// ```
70    pub emit_unresolved_aliases: bool,
71    /// Emit anchor definition metadata immediately after its node event.
72    ///
73    /// Defaults to `false`, preserving the existing event stream. When enabled, each anchored
74    /// scalar or collection start is immediately followed by [`crate::Event::Anchor`] carrying
75    /// the assigned ID and original name (without `&`). Its companion span covers the complete
76    /// `&name` token. Collection anchors are emitted before their children, including aliases
77    /// referring to the collection itself. This adjacency guarantee applies to each
78    /// [`crate::Parser`]; see [`crate::ParserStack`] for ordering across stack pushes.
79    ///
80    /// The node event comes first so the parser never emits anchor metadata for a node that
81    /// failed before its event could be emitted, including when recovering to the next document.
82    ///
83    /// Anchor events are presentation metadata, not additional YAML nodes. Their spans refer
84    /// back to properties preceding the node, so event spans need not be in source order.
85    /// This option is independent of comment emission and has no effect on a standalone scanner.
86    pub emit_anchor_events: bool,
87    /// Whether scanners emit comment tokens and parsers emit comment events.
88    ///
89    /// The default is `true` with the `comments` Cargo feature and `false` without it.
90    /// When this is `false`, comments are still recognized and validated as YAML syntax, but their
91    /// text is not captured and no comment tokens or events are emitted.
92    /// Comment bytes are still consumed, so this is not an input-size or processing-time limit.
93    /// This option is always available, but has no effect without the `comments` Cargo feature.
94    #[cfg_attr(
95        feature = "comments",
96        doc = "[`Self::max_buffered_comment_events`] has no effect while comment emission is disabled."
97    )]
98    pub emit_comments: bool,
99    /// Maximum number of consecutive comment events buffered while resolving an ambiguous
100    /// collection entry or looking for the next document during recovery.
101    ///
102    /// The default is 96. A value of zero rejects the first comment that would need buffering.
103    /// This option is available only with the `comments` Cargo feature.
104    #[cfg(feature = "comments")]
105    pub max_buffered_comment_events: usize,
106    /// Maximum number of characters inspected while resolving a simple key.
107    ///
108    /// The default is 1024, matching YAML's simple-key length restriction. A key at exactly this
109    /// limit is accepted. Lower values impose a stricter resource limit; higher values relax that
110    /// YAML restriction.
111    pub simple_key_max_lookahead: usize,
112    /// Maximum number of simultaneously nested flow collections.
113    ///
114    /// The default is 255. A value of zero rejects the first flow collection opener.
115    pub flow_nesting_limit: usize,
116    /// Maximum number of simultaneously nested block collections parsed, or block indentation
117    /// levels retained by a scanner used directly.
118    ///
119    /// The default is 255. A value of zero rejects the first block sequence or mapping. This
120    /// bounds both indentation state retained while scanning and the number of closing tokens
121    /// queued when nested block collections end together. Parsers also count indentless block
122    /// sequences, which do not add scanner indentation state.
123    pub block_nesting_limit: usize,
124    /// Maximum number of UTF-8 source bytes retained for a directive name and payload.
125    ///
126    /// The default is 1024. Separating blanks before retained values count toward the limit.
127    /// `%YAML` version components instead have a fixed nine-digit limit and are stored as integers.
128    /// Real directives are far shorter than the default.
129    pub max_directive_bytes: usize,
130    /// Maximum number of parameters retained for a single reserved directive.
131    ///
132    /// The default is 16. The parser ignores reserved directives, so their parameters only reach
133    /// code driving [`crate::Scanner`] directly. A value of zero rejects the first parameter.
134    pub max_reserved_directive_params: usize,
135}
136
137impl Options {
138    /// Controls whether scanners emit comment tokens and parsers emit comment events.
139    ///
140    /// When emission is disabled, comments are still recognized and validated, but their text
141    /// is not captured. This sets [`Self::emit_comments`] in every build, but has no effect on
142    /// parsing or scanning without the `comments` Cargo feature.
143    ///
144    /// This method is always available so downstream crates can disable comment emission
145    /// even when another dependency enables the `comments` feature through feature unification.
146    pub fn set_emit_comments(&mut self, emit_comments: bool) {
147        self.emit_comments = emit_comments;
148    }
149}
150
151impl Default for Options {
152    fn default() -> Self {
153        Self {
154            strict_indentation: false,
155            disallow_directives: false,
156            emit_unresolved_aliases: false,
157            emit_anchor_events: false,
158            emit_comments: cfg!(feature = "comments"),
159            #[cfg(feature = "comments")]
160            max_buffered_comment_events: 96,
161            simple_key_max_lookahead: 1024,
162            flow_nesting_limit: 255,
163            block_nesting_limit: 255,
164            max_directive_bytes: 1024,
165            max_reserved_directive_params: 16,
166        }
167    }
168}