Skip to main content

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}