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}