Skip to main content

oxc_parser/
config.rs

1// All methods are `#[inline(always)]` to ensure compiler removes dead code resulting from static values
2#![expect(clippy::inline_always)]
3
4use std::ops::Index;
5
6use crate::lexer::{ByteHandler, ByteHandlers, byte_handler_tables};
7
8/// Parser config.
9///
10/// The purpose of parser config (as opposed to `ParseOptions`) is to allow setting options at either
11/// compile time or runtime.
12///
13/// 3 configs are provided:
14/// * [`NoTokensParserConfig`]: Parse without tokens, static (default)
15/// * [`TokensParserConfig`]: Parse with tokens, static
16/// * [`RuntimeParserConfig`]: Parse with or without tokens, decided at runtime
17///
18/// The trade-off is:
19///
20/// * The 2 static configs will produce better performance, because compiler can remove code that relates
21///   to the other option as dead code, and remove branches.
22///
23/// * The runtime config will produce a smaller binary than using 2 different configs in the same application,
24///   which would cause 2 polymorphic variants of the parser to be compiled.
25///
26/// Advised usage:
27/// * If your application uses only a specific set of options, use a static config.
28/// * If your application uses multiple sets of options, probably a runtime config is preferable.
29///
30/// At present the only option controlled by `ParserConfig` is whether to parse with or without tokens.
31/// Other options will be added in future.
32///
33/// You can also create your own config by implementing [`ParserConfig`] on a type.
34pub trait ParserConfig: Default {
35    /// Type of [`LexerConfig`] this [`ParserConfig`] uses.
36    type LexerConfig: LexerConfig;
37
38    /// Get [`LexerConfig`] for this [`ParserConfig`].
39    fn lexer_config(&self) -> Self::LexerConfig;
40}
41
42/// Parser config for parsing without tokens (default).
43///
44/// See [`ParserConfig`] for more details.
45#[derive(Copy, Clone, Default)]
46pub struct NoTokensParserConfig;
47
48impl ParserConfig for NoTokensParserConfig {
49    type LexerConfig = NoTokensLexerConfig;
50
51    #[inline(always)]
52    fn lexer_config(&self) -> NoTokensLexerConfig {
53        NoTokensLexerConfig
54    }
55}
56
57/// Parser config for parsing with tokens.
58///
59/// See [`ParserConfig`] for more details.
60#[derive(Copy, Clone, Default)]
61pub struct TokensParserConfig;
62
63impl ParserConfig for TokensParserConfig {
64    type LexerConfig = TokensLexerConfig;
65
66    #[inline(always)]
67    fn lexer_config(&self) -> TokensLexerConfig {
68        TokensLexerConfig
69    }
70}
71
72/// Parser config for parsing with / without tokens, decided at runtime.
73///
74/// See [`ParserConfig`] for more details.
75#[derive(Copy, Clone, Default)]
76#[repr(transparent)]
77pub struct RuntimeParserConfig {
78    lexer_config: RuntimeLexerConfig,
79}
80
81impl RuntimeParserConfig {
82    #[inline(always)]
83    pub fn new(tokens: bool) -> Self {
84        Self { lexer_config: RuntimeLexerConfig::new(tokens) }
85    }
86}
87
88impl ParserConfig for RuntimeParserConfig {
89    type LexerConfig = RuntimeLexerConfig;
90
91    #[inline(always)]
92    fn lexer_config(&self) -> RuntimeLexerConfig {
93        self.lexer_config
94    }
95}
96
97/// Lexer config.
98///
99/// See [`ParserConfig`] for more details.
100///
101/// We have to define a different byte handler table for each config, as byte handler functions
102/// are generic over the [`LexerConfig`].
103///
104/// Byte handler tables are defined within lexer (`lexer/byte_handlers.rs`).
105/// We need them to be defined as `static` items, for performance.
106/// 1. `static` ensures that only 1 copy of each byte handler table appears in the binary
107///    (even if `Lexer::handle_byte` is inlined in multiple places).
108/// 2. Indexing into byte handlers table is an extremely hot path, and even 1 extra indirection (pointer chasing)
109///    has a sizeable impact on performance.
110///
111/// An associated type and `byte_handlers` method is the only way to make this work with Rust's type system
112/// and borrow checker.
113pub trait LexerConfig: Default {
114    /// Byte handlers table type.
115    type ByteHandlers: Index<usize, Output = ByteHandler<Self>>;
116
117    /// `true` if [`tokens`] method returns a static value.
118    ///
119    /// This const is useful in situations where code can be more efficient
120    /// when the return value of [`tokens`] method is known at compile time.
121    ///
122    /// Implementors of this trait MUST only set this const to `true` if [`tokens`] method returns a static value.
123    ///
124    /// [`tokens`]: LexerConfig::tokens
125    const TOKENS_METHOD_IS_STATIC: bool;
126
127    /// Returns `true` if tokens are enabled.
128    fn tokens(&self) -> bool;
129
130    /// Get byte handlers table to use in lexer with this config.
131    fn byte_handlers(&self) -> &Self::ByteHandlers;
132}
133
134/// Lexer config for lexing without tokens.
135#[derive(Copy, Clone, Default)]
136pub struct NoTokensLexerConfig;
137
138impl LexerConfig for NoTokensLexerConfig {
139    type ByteHandlers = ByteHandlers<Self>;
140
141    const TOKENS_METHOD_IS_STATIC: bool = true;
142
143    #[inline(always)]
144    fn tokens(&self) -> bool {
145        false
146    }
147
148    #[inline(always)]
149    fn byte_handlers(&self) -> &Self::ByteHandlers {
150        &byte_handler_tables::NO_TOKENS
151    }
152}
153
154/// Lexer config for parsing with tokens.
155#[derive(Copy, Clone, Default)]
156pub struct TokensLexerConfig;
157
158impl LexerConfig for TokensLexerConfig {
159    type ByteHandlers = ByteHandlers<Self>;
160
161    const TOKENS_METHOD_IS_STATIC: bool = true;
162
163    #[inline(always)]
164    fn tokens(&self) -> bool {
165        true
166    }
167
168    #[inline(always)]
169    fn byte_handlers(&self) -> &Self::ByteHandlers {
170        &byte_handler_tables::WITH_TOKENS
171    }
172}
173
174/// Lexer config for lexing with / without tokens, decided at runtime.
175#[derive(Copy, Clone, Default)]
176#[repr(transparent)]
177pub struct RuntimeLexerConfig {
178    tokens: bool,
179}
180
181impl RuntimeLexerConfig {
182    #[inline(always)]
183    pub fn new(tokens: bool) -> Self {
184        Self { tokens }
185    }
186}
187
188impl LexerConfig for RuntimeLexerConfig {
189    type ByteHandlers = ByteHandlers<Self>;
190
191    const TOKENS_METHOD_IS_STATIC: bool = false;
192
193    #[inline(always)]
194    fn tokens(&self) -> bool {
195        self.tokens
196    }
197
198    #[inline(always)]
199    fn byte_handlers(&self) -> &Self::ByteHandlers {
200        &byte_handler_tables::RUNTIME_TOKENS
201    }
202}