Skip to main content

nash_parse/
keyword.rs

1//! Keyword handling for Nash.
2//!
3//! Ported from Elm's `Parse/Keyword.hs` and `Parse/Variable.hs`.
4
5use crate::Parser;
6
7/// Reserved words that cannot be used as variable names.
8pub const RESERVED: &[&str] = &[
9    "if", "then", "else", "case", "of", "let", "in", "type", "module", "where", "import",
10    "exposing", "as", "port",
11];
12
13/// Check if a name is a reserved keyword.
14#[inline]
15pub fn is_reserved(name: &str) -> bool {
16    RESERVED.contains(&name)
17}
18
19use crate::{Col, Row};
20
21impl<'a> Parser<'a> {
22    /// Parse the `if` keyword.
23    ///
24    /// Mirrors Elm's `Keyword.if_`.
25    pub fn keyword_if<E>(&mut self, to_error: impl FnOnce(Row, Col) -> E) -> Result<(), E> {
26        self.keyword(b"if", to_error)
27    }
28
29    /// Parse the `then` keyword.
30    ///
31    /// Mirrors Elm's `Keyword.then_`.
32    pub fn keyword_then<E>(&mut self, to_error: impl FnOnce(Row, Col) -> E) -> Result<(), E> {
33        self.keyword(b"then", to_error)
34    }
35
36    /// Parse the `else` keyword.
37    ///
38    /// Mirrors Elm's `Keyword.else_`.
39    pub fn keyword_else<E>(&mut self, to_error: impl FnOnce(Row, Col) -> E) -> Result<(), E> {
40        self.keyword(b"else", to_error)
41    }
42
43    /// Parse the `case` keyword.
44    ///
45    /// Mirrors Elm's `Keyword.case_`.
46    pub fn keyword_case<E>(&mut self, to_error: impl FnOnce(Row, Col) -> E) -> Result<(), E> {
47        self.keyword(b"case", to_error)
48    }
49
50    /// Parse the `of` keyword.
51    ///
52    /// Mirrors Elm's `Keyword.of_`.
53    pub fn keyword_of<E>(&mut self, to_error: impl FnOnce(Row, Col) -> E) -> Result<(), E> {
54        self.keyword(b"of", to_error)
55    }
56
57    /// Parse the `let` keyword.
58    ///
59    /// Mirrors Elm's `Keyword.let_`.
60    pub fn keyword_let<E>(&mut self, to_error: impl FnOnce(Row, Col) -> E) -> Result<(), E> {
61        self.keyword(b"let", to_error)
62    }
63
64    /// Parse the `in` keyword.
65    ///
66    /// Mirrors Elm's `Keyword.in_`.
67    pub fn keyword_in<E>(&mut self, to_error: impl FnOnce(Row, Col) -> E) -> Result<(), E> {
68        self.keyword(b"in", to_error)
69    }
70
71    /// Parse the `type` keyword.
72    ///
73    /// Mirrors Elm's `Keyword.type_`.
74    pub fn keyword_type<E>(&mut self, to_error: impl FnOnce(Row, Col) -> E) -> Result<(), E> {
75        self.keyword(b"type", to_error)
76    }
77
78    /// Parse the `alias` keyword.
79    ///
80    /// Mirrors Elm's `Keyword.alias_`.
81    pub fn keyword_alias<E>(&mut self, to_error: impl FnOnce(Row, Col) -> E) -> Result<(), E> {
82        self.keyword(b"alias", to_error)
83    }
84
85    /// Parse the `infix` keyword.
86    ///
87    /// Mirrors Elm's `Keyword.infix_`.
88    pub fn keyword_infix<E>(&mut self, to_error: impl FnOnce(Row, Col) -> E) -> Result<(), E> {
89        self.keyword(b"infix", to_error)
90    }
91
92    /// Parse the `left` keyword (for infix associativity).
93    ///
94    /// Mirrors Elm's `Keyword.left_`.
95    pub fn keyword_left<E>(&mut self, to_error: impl FnOnce(Row, Col) -> E) -> Result<(), E> {
96        self.keyword(b"left", to_error)
97    }
98
99    /// Parse the `right` keyword (for infix associativity).
100    ///
101    /// Mirrors Elm's `Keyword.right_`.
102    pub fn keyword_right<E>(&mut self, to_error: impl FnOnce(Row, Col) -> E) -> Result<(), E> {
103        self.keyword(b"right", to_error)
104    }
105
106    /// Parse the `non` keyword (for infix associativity).
107    ///
108    /// Mirrors Elm's `Keyword.non_`.
109    pub fn keyword_non<E>(&mut self, to_error: impl FnOnce(Row, Col) -> E) -> Result<(), E> {
110        self.keyword(b"non", to_error)
111    }
112
113    /// Parse the `import` keyword.
114    ///
115    /// Mirrors Elm's `Keyword.import_`.
116    pub fn keyword_import<E>(&mut self, to_error: impl FnOnce(Row, Col) -> E) -> Result<(), E> {
117        self.keyword(b"import", to_error)
118    }
119
120    /// Parse the `as` keyword.
121    ///
122    /// Mirrors Elm's `Keyword.as_`.
123    pub fn keyword_as<E>(&mut self, to_error: impl FnOnce(Row, Col) -> E) -> Result<(), E> {
124        self.keyword(b"as", to_error)
125    }
126
127    /// Parse the `exposing` keyword.
128    ///
129    /// Mirrors Elm's `Keyword.exposing_`.
130    pub fn keyword_exposing<E>(&mut self, to_error: impl FnOnce(Row, Col) -> E) -> Result<(), E> {
131        self.keyword(b"exposing", to_error)
132    }
133
134    /// Parse the `module` keyword.
135    ///
136    /// Mirrors Elm's `Keyword.module_`.
137    pub fn keyword_module<E>(&mut self, to_error: impl FnOnce(Row, Col) -> E) -> Result<(), E> {
138        self.keyword(b"module", to_error)
139    }
140
141    /// Generic keyword parser that checks bytes match and no identifier continuation follows.
142    ///
143    /// Mirrors Elm's `k2`, `k3`, `k4` etc. but generalized.
144    fn keyword<E>(&mut self, kw: &[u8], to_error: impl FnOnce(Row, Col) -> E) -> Result<(), E> {
145        // Check if we have enough bytes
146        if self.pos + kw.len() > self.src.len() {
147            let (row, col) = self.position();
148            return Err(to_error(row, col));
149        }
150
151        // Check if bytes match
152        if &self.src[self.pos..self.pos + kw.len()] != kw {
153            let (row, col) = self.position();
154            return Err(to_error(row, col));
155        }
156
157        // Check that no identifier continuation follows (like Elm's getInnerWidth == 0)
158        let next_pos = self.pos + kw.len();
159        if next_pos < self.src.len() {
160            let next_byte = self.src[next_pos];
161            if is_inner_char(next_byte) {
162                let (row, col) = self.position();
163                return Err(to_error(row, col));
164            }
165        }
166
167        // Success - advance position and update column
168        for _ in 0..kw.len() {
169            self.advance();
170        }
171        Ok(())
172    }
173}
174
175/// Check if a byte is a valid identifier continuation character.
176///
177/// Matches: a-z, A-Z, 0-9, _
178#[inline]
179fn is_inner_char(b: u8) -> bool {
180    b.is_ascii_alphanumeric() || b == b'_'
181}