Skip to main content

syntaqlite_syntax/
util.rs

1// Copyright 2025 The syntaqlite Authors. All rights reserved.
2// Licensed under the Apache License, Version 2.0.
3
4// ── Public API ───────────────────────────────────────────────────────────────
5
6// `SqliteSyntaxFlag` is always available — ordinals are stable across all dialects.
7#[doc(inline)]
8pub use crate::sqlite::cflags::SqliteSyntaxFlag;
9
10/// Snapshot of C-parser compatibility flags for the `SQLite` grammar.
11///
12/// This type mirrors the `SyntaqliteCflags` C struct (3 bytes, 22 meaningful
13/// bits, compact indices 0–21) and is used to configure the C parser at
14/// runtime. It covers only the grammar-level parser flags defined in
15/// `include/syntaqlite/cflags.h`.
16///
17/// For the full set of `SQLite` compile-time flags — including non-parser flags
18/// like `SQLITE_ENABLE_MATH_FUNCTIONS` — use `syntaqlite::util::SqliteFlags`.
19#[derive(Debug, Clone, Copy, Default)]
20pub struct SqliteSyntaxFlags(pub(crate) ffi::CCflags);
21
22impl SqliteSyntaxFlags {
23    /// Returns `true` if parser flag `flag` is enabled.
24    #[inline]
25    pub fn has(&self, flag: SqliteSyntaxFlag) -> bool {
26        self.0.has(flag as u32)
27    }
28
29    /// Return a copy of these flags with `flag` enabled.
30    #[must_use]
31    pub fn with(mut self, flag: SqliteSyntaxFlag) -> Self {
32        self.0.set(flag as u32);
33        self
34    }
35}
36
37/// `SQLite` compatibility target used to select grammar behavior.
38///
39/// Pin this when your application needs to parse according to a specific
40/// `SQLite` release. Patch versions are intentionally ignored.
41#[expect(missing_docs)]
42#[non_exhaustive]
43#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
44pub enum SqliteVersion {
45    V3_12,
46    V3_13,
47    V3_14,
48    V3_15,
49    V3_16,
50    V3_17,
51    V3_18,
52    V3_19,
53    V3_20,
54    V3_21,
55    V3_22,
56    V3_23,
57    V3_24,
58    V3_25,
59    V3_26,
60    V3_27,
61    V3_28,
62    V3_29,
63    V3_30,
64    V3_31,
65    V3_32,
66    V3_33,
67    V3_34,
68    V3_35,
69    V3_36,
70    V3_37,
71    V3_38,
72    V3_39,
73    V3_40,
74    V3_41,
75    V3_42,
76    V3_43,
77    V3_44,
78    V3_45,
79    V3_46,
80    V3_47,
81    V3_48,
82    V3_49,
83    V3_50,
84    V3_51,
85    /// No version constraint — use the latest grammar rules.
86    Latest,
87}
88
89impl SqliteVersion {
90    /// Parse a version string or the literal `"latest"`.
91    ///
92    /// The string `"latest"` (case-insensitive) maps to [`SqliteVersion::Latest`].
93    /// All other inputs are forwarded to [`SqliteVersion::parse`].
94    ///
95    /// # Errors
96    /// Returns `Err` if the version string is not recognised.
97    pub fn parse_with_latest(s: &str) -> Result<Self, String> {
98        let s = s.trim();
99        if s.eq_ignore_ascii_case("latest") {
100            return Ok(Self::Latest);
101        }
102        Self::parse(s).ok_or_else(|| format!("unknown or unsupported SQLite version: '{s}'"))
103    }
104
105    /// Parse a version string, ignoring the patch component.
106    ///
107    /// Accepts `"3.35"`, `"3.35.0"`, `"3.35.5"`, etc.
108    /// Returns `None` if the version is not in the supported range.
109    pub fn parse(s: &str) -> Option<Self> {
110        let mut parts = s.splitn(3, '.');
111        let major: u32 = parts.next()?.parse().ok()?;
112        let minor: u32 = parts.next()?.parse().ok()?;
113        // patch component is ignored
114        if major != 3 {
115            return None;
116        }
117        Some(match minor {
118            12 => Self::V3_12,
119            13 => Self::V3_13,
120            14 => Self::V3_14,
121            15 => Self::V3_15,
122            16 => Self::V3_16,
123            17 => Self::V3_17,
124            18 => Self::V3_18,
125            19 => Self::V3_19,
126            20 => Self::V3_20,
127            21 => Self::V3_21,
128            22 => Self::V3_22,
129            23 => Self::V3_23,
130            24 => Self::V3_24,
131            25 => Self::V3_25,
132            26 => Self::V3_26,
133            27 => Self::V3_27,
134            28 => Self::V3_28,
135            29 => Self::V3_29,
136            30 => Self::V3_30,
137            31 => Self::V3_31,
138            32 => Self::V3_32,
139            33 => Self::V3_33,
140            34 => Self::V3_34,
141            35 => Self::V3_35,
142            36 => Self::V3_36,
143            37 => Self::V3_37,
144            38 => Self::V3_38,
145            39 => Self::V3_39,
146            40 => Self::V3_40,
147            41 => Self::V3_41,
148            42 => Self::V3_42,
149            43 => Self::V3_43,
150            44 => Self::V3_44,
151            45 => Self::V3_45,
152            46 => Self::V3_46,
153            47 => Self::V3_47,
154            48 => Self::V3_48,
155            49 => Self::V3_49,
156            50 => Self::V3_50,
157            51 => Self::V3_51,
158            _ => return None,
159        })
160    }
161}
162
163// ── Crate-internal ───────────────────────────────────────────────────────────
164
165impl SqliteVersion {
166    /// Convert from `SQLite`'s `SQLITE_VERSION_NUMBER` integer format.
167    ///
168    /// Returns `SqliteVersion::Latest` for `i32::MAX` and for any unrecognised
169    /// value (e.g. a version newer than the highest known variant).
170    pub fn from_int(v: i32) -> Self {
171        match v {
172            3_012_000 => Self::V3_12,
173            3_013_000 => Self::V3_13,
174            3_014_000 => Self::V3_14,
175            3_015_000 => Self::V3_15,
176            3_016_000 => Self::V3_16,
177            3_017_000 => Self::V3_17,
178            3_018_000 => Self::V3_18,
179            3_019_000 => Self::V3_19,
180            3_020_000 => Self::V3_20,
181            3_021_000 => Self::V3_21,
182            3_022_000 => Self::V3_22,
183            3_023_000 => Self::V3_23,
184            3_024_000 => Self::V3_24,
185            3_025_000 => Self::V3_25,
186            3_026_000 => Self::V3_26,
187            3_027_000 => Self::V3_27,
188            3_028_000 => Self::V3_28,
189            3_029_000 => Self::V3_29,
190            3_030_000 => Self::V3_30,
191            3_031_000 => Self::V3_31,
192            3_032_000 => Self::V3_32,
193            3_033_000 => Self::V3_33,
194            3_034_000 => Self::V3_34,
195            3_035_000 => Self::V3_35,
196            3_036_000 => Self::V3_36,
197            3_037_000 => Self::V3_37,
198            3_038_000 => Self::V3_38,
199            3_039_000 => Self::V3_39,
200            3_040_000 => Self::V3_40,
201            3_041_000 => Self::V3_41,
202            3_042_000 => Self::V3_42,
203            3_043_000 => Self::V3_43,
204            3_044_000 => Self::V3_44,
205            3_045_000 => Self::V3_45,
206            3_046_000 => Self::V3_46,
207            3_047_000 => Self::V3_47,
208            3_048_000 => Self::V3_48,
209            3_049_000 => Self::V3_49,
210            3_050_000 => Self::V3_50,
211            3_051_000 => Self::V3_51,
212            _ => Self::Latest,
213        }
214    }
215
216    /// Convert to `SQLite`'s `SQLITE_VERSION_NUMBER` integer format.
217    ///
218    /// Uses the formula `major * 1_000_000 + minor * 1_000`, matching the
219    /// `SQLITE_VERSION_NUMBER` C macro (e.g. `V3_35` → `3035000`).
220    pub fn as_int(self) -> i32 {
221        match self {
222            Self::V3_12 => 3_012_000,
223            Self::V3_13 => 3_013_000,
224            Self::V3_14 => 3_014_000,
225            Self::V3_15 => 3_015_000,
226            Self::V3_16 => 3_016_000,
227            Self::V3_17 => 3_017_000,
228            Self::V3_18 => 3_018_000,
229            Self::V3_19 => 3_019_000,
230            Self::V3_20 => 3_020_000,
231            Self::V3_21 => 3_021_000,
232            Self::V3_22 => 3_022_000,
233            Self::V3_23 => 3_023_000,
234            Self::V3_24 => 3_024_000,
235            Self::V3_25 => 3_025_000,
236            Self::V3_26 => 3_026_000,
237            Self::V3_27 => 3_027_000,
238            Self::V3_28 => 3_028_000,
239            Self::V3_29 => 3_029_000,
240            Self::V3_30 => 3_030_000,
241            Self::V3_31 => 3_031_000,
242            Self::V3_32 => 3_032_000,
243            Self::V3_33 => 3_033_000,
244            Self::V3_34 => 3_034_000,
245            Self::V3_35 => 3_035_000,
246            Self::V3_36 => 3_036_000,
247            Self::V3_37 => 3_037_000,
248            Self::V3_38 => 3_038_000,
249            Self::V3_39 => 3_039_000,
250            Self::V3_40 => 3_040_000,
251            Self::V3_41 => 3_041_000,
252            Self::V3_42 => 3_042_000,
253            Self::V3_43 => 3_043_000,
254            Self::V3_44 => 3_044_000,
255            Self::V3_45 => 3_045_000,
256            Self::V3_46 => 3_046_000,
257            Self::V3_47 => 3_047_000,
258            Self::V3_48 => 3_048_000,
259            Self::V3_49 => 3_049_000,
260            Self::V3_50 => 3_050_000,
261            Self::V3_51 => 3_051_000,
262            Self::Latest => i32::MAX,
263        }
264    }
265}
266
267// ── ffi ───────────────────────────────────────────────────────────────────────
268
269pub(crate) mod ffi {
270    /// Mirrors C `SyntaqliteCflags` from `include/syntaqlite/cflags.h`.
271    ///
272    /// A packed bitfield over the parser-group compile-time flags (22 flags,
273    /// packed into 3 bytes). Indices match the `SYNQ_CFLAG_IDX_*` C constants
274    /// and the generated [`crate::sqlite::cflags::SqliteSyntaxFlag`] Rust enum.
275    #[repr(C)]
276    #[derive(Clone, Copy, Default)]
277    pub(crate) struct CCflags {
278        pub(super) bytes: [u8; 3],
279    }
280
281    #[expect(dead_code)]
282    impl CCflags {
283        pub(crate) const fn new() -> Self {
284            Self { bytes: [0; 3] }
285        }
286
287        #[inline]
288        pub(crate) fn has(self, idx: u32) -> bool {
289            let byte = idx / 8;
290            let bit = idx % 8;
291            (byte < 3) && (self.bytes[byte as usize] >> bit) & 1 != 0
292        }
293
294        #[inline]
295        pub(crate) fn set(&mut self, idx: u32) {
296            let byte = idx / 8;
297            let bit = idx % 8;
298            if byte < 3 {
299                self.bytes[byte as usize] |= 1 << bit;
300            }
301        }
302
303        #[inline]
304        pub(crate) fn clear(&mut self, idx: u32) {
305            let byte = idx / 8;
306            let bit = idx % 8;
307            if byte < 3 {
308                self.bytes[byte as usize] &= !(1 << bit);
309            }
310        }
311
312        #[inline]
313        pub(crate) fn clear_all(&mut self) {
314            self.bytes = [0; 3];
315        }
316    }
317
318    impl std::fmt::Debug for CCflags {
319        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
320            write!(f, "Cflags({:02x?})", &self.bytes)
321        }
322    }
323}
324
325// ── Tests ─────────────────────────────────────────────────────────────────────
326
327/// Return `true` if `name` looks like a completable keyword symbol.
328///
329/// Keyword symbols use all-uppercase names with digits and underscores
330/// (e.g. `SELECT`, `LEFT_JOIN`). This is a pure naming-convention predicate
331/// and does not depend on grammar version or flags.
332pub fn is_suggestable_keyword(name: &str) -> bool {
333    !name.is_empty()
334        && name
335            .bytes()
336            .all(|b| b.is_ascii_uppercase() || b.is_ascii_digit() || b == b'_')
337}
338
339#[cfg(test)]
340mod tests {
341    use super::*;
342
343    #[test]
344    fn parse_major_minor() {
345        assert_eq!(SqliteVersion::parse("3.35"), Some(SqliteVersion::V3_35));
346    }
347
348    #[test]
349    fn parse_with_patch() {
350        assert_eq!(SqliteVersion::parse("3.35.5"), Some(SqliteVersion::V3_35));
351    }
352
353    #[test]
354    fn parse_unknown_minor() {
355        assert_eq!(SqliteVersion::parse("3.99"), None);
356    }
357
358    #[test]
359    fn as_int_spot_check() {
360        assert_eq!(SqliteVersion::V3_35.as_int(), 3_035_000);
361        assert_eq!(SqliteVersion::V3_51.as_int(), 3_051_000);
362        assert_eq!(SqliteVersion::Latest.as_int(), i32::MAX);
363    }
364}