formualizer_parse/limits.rs
1//! Immutable parser resource budgets.
2
3/// Resource budgets applied while tokenizing and parsing one formula.
4///
5/// Start from [`ParserLimits::default`] and adjust individual budgets with the
6/// `with_*` setters. Values are stored exactly as given: nothing is clamped,
7/// and a zero budget rejects every input that needs that resource.
8///
9/// ```
10/// use formualizer_parse::{Parser, ParserLimits};
11///
12/// let limits = ParserLimits::default().with_ast_nodes(64).with_tokens(128);
13/// assert_eq!(limits.ast_nodes(), 64);
14/// let ast = Parser::builder().limits(limits).parse("=SUM(A1:B2)").unwrap();
15/// # let _ = ast;
16/// ```
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct ParserLimits {
19 pub(crate) source_bytes: usize,
20 pub(crate) tokens: usize,
21 pub(crate) ast_nodes: usize,
22 pub(crate) pratt_frames: usize,
23 pub(crate) ast_height: usize,
24}
25impl Default for ParserLimits {
26 fn default() -> Self {
27 Self {
28 source_bytes: 65536,
29 tokens: 16384,
30 ast_nodes: 8192,
31 pratt_frames: 72,
32 ast_height: 256,
33 }
34 }
35}
36impl ParserLimits {
37 /// Maximum UTF-8 source length in bytes.
38 #[must_use]
39 pub const fn with_source_bytes(mut self, source_bytes: usize) -> Self {
40 self.source_bytes = source_bytes;
41 self
42 }
43 /// Maximum number of tokens, including retained whitespace tokens.
44 #[must_use]
45 pub const fn with_tokens(mut self, tokens: usize) -> Self {
46 self.tokens = tokens;
47 self
48 }
49 /// Maximum number of AST nodes, including omitted arguments.
50 #[must_use]
51 pub const fn with_ast_nodes(mut self, ast_nodes: usize) -> Self {
52 self.ast_nodes = ast_nodes;
53 self
54 }
55 /// Maximum active Pratt-parser frames (see [`Self::pratt_frames`]).
56 ///
57 /// Raising this above the default needs a correspondingly larger stack on
58 /// the parsing thread.
59 #[must_use]
60 pub const fn with_pratt_frames(mut self, pratt_frames: usize) -> Self {
61 self.pratt_frames = pratt_frames;
62 self
63 }
64 /// Maximum AST height, counting the root as one.
65 ///
66 /// The default (256) is tested on a 1 MiB thread stack in release builds
67 /// through parsing, workbook evaluation and XLSX cache recalculation.
68 /// Raising it needs about 2.5 KiB more stack per level (up to 4.2 KiB for
69 /// right-nested shapes; release, x86-64) on every thread that parses,
70 /// clones, drops or evaluates the tree.
71 #[must_use]
72 pub const fn with_ast_height(mut self, ast_height: usize) -> Self {
73 self.ast_height = ast_height;
74 self
75 }
76 pub const fn source_bytes(self) -> usize {
77 self.source_bytes
78 }
79 pub const fn tokens(self) -> usize {
80 self.tokens
81 }
82 pub const fn ast_nodes(self) -> usize {
83 self.ast_nodes
84 }
85 /// Active Pratt frames, not an exact Excel function-nesting limit. The
86 /// default leaves headroom for ordinary 64-level calls/parentheses and IF
87 /// conditions; right-nested parenthesized infix uses two frames per level.
88 pub const fn pratt_frames(self) -> usize {
89 self.pratt_frames
90 }
91 pub const fn ast_height(self) -> usize {
92 self.ast_height
93 }
94 pub(crate) fn check_source(self, source: &str) -> Result<(), crate::TokenizerError> {
95 if source.len() > self.source_bytes {
96 return Err(crate::TokenizerError {
97 message: format!(
98 "Formula source byte limit exceeded (max {})",
99 self.source_bytes
100 ),
101 pos: 0,
102 });
103 }
104 Ok(())
105 }
106}