parlex_calc/
token.rs

1//! # Calculator Tokens
2//!
3//! This module defines the concrete token value and token type used by the
4//! calculator parser. It provides:
5//!
6//! - [`TokenValue`]: the payload carried by lexical tokens (e.g., symbol-table
7//!   indices for identifiers or numeric literals),
8//! - [`CalcToken`]: a concrete token implementation that pairs a [`TokenID`],
9//!   a [`TokenValue`], and a source line number, and implements the core
10//!   [`parlex::Token`] trait.
11//!
12//! These types are produced by the lexer and consumed by later stages of the
13//! pipeline (e.g., the parser and semantic analysis).
14use crate::TokenID;
15use clap::value_parser;
16use parlex::{Span, Token};
17use smartstring::alias::String;
18
19/// The payload carried by a lexical token.
20///
21/// Tokens may or may not carry extra data depending on their kind. For example,
22/// identifiers and numbers store auxiliary information such as a symbol-table
23/// index or a literal integer value.
24///
25/// This payload is paired with a [`TokenID`] inside a [`CalcToken`].
26///
27/// # Variants
28///
29/// - [`TokenValue::None`]:
30///   No extra data (typical for punctuation or operators).
31///
32/// - [`TokenValue::Ident(usize)`]:
33///   The **symbol table index** for an identifier. The `usize` refers to an
34///   entry managed by the symbol table (see your crate’s `SymTab` type).
35///
36/// - [`TokenValue::Number(i64)`]:
37///   A parsed integer literal.
38///
39/// # Example
40/// ```rust
41/// # use parlex_calc::TokenValue;
42/// // Construct a token representing a number
43/// let token = TokenValue::Number(42);
44///
45/// // Ensure that it is a number, and extract its value
46/// let TokenValue::Number(n) = token else {
47///     panic!("Expected a numeric token");
48/// };
49///
50/// println!("Numeric literal: {n}");
51/// assert_eq!(n, 42);
52/// ```
53#[derive(Debug, Clone)]
54pub enum TokenValue {
55    /// No associated data (for symbols or keywords).
56    None,
57
58    /// Identifier token with an index into the symbol table.
59    Ident(usize),
60
61    /// Integer literal token.
62    Number(i64),
63
64    /// Comment.
65    Comment(String),
66
67    /// Statement
68    Stat {
69        comments: Vec<String>,
70        value: Option<i64>,
71    },
72}
73
74/// A concrete lexical token for the calculator frontend.
75///
76/// `CalcToken` is a lightweight container that implements [`parlex::Token`],
77/// exposing its identifier and source position. It groups:
78///
79/// - a token kind via [`TokenID`],
80/// - an optional payload via [`TokenValue`],
81/// - a 1-based source line number.
82///
83/// # Trait implementation
84///
85/// Implements [`Token`] with:
86/// - [`token_id`](Token::token_id): returns the token’s [`TokenID`],
87/// - [`line_no`](Token::line_no): returns the token’s source line number.
88///
89/// # Fields
90///
91/// - [`token_id`](#structfield.token_id): the token’s category (identifier, number, operator, …)
92/// - [`value`](#structfield.value): associated payload (symbol index or literal)
93/// - [`line_no`](#structfield.line_no): 1-based source line number
94///
95/// # Example
96/// ```rust
97/// # use parlex_calc::{CalcToken, TokenID, TokenValue};
98/// # use parlex::{Token, span};
99/// let tok = CalcToken {
100///     token_id: TokenID::Number,
101///     value: TokenValue::Number(99),
102///     span: span!(0, 0, 0, 2),
103/// };
104///
105/// assert_eq!(tok.token_id(), TokenID::Number);
106/// assert_eq!(tok.span(), span!(0, 0, 0, 2));
107/// ```
108#[derive(Debug, Clone)]
109pub struct CalcToken {
110    /// The token’s kind or category (e.g. identifier, operator, number).
111    pub token_id: TokenID,
112    /// The associated value for the token, if applicable.
113    pub value: TokenValue,
114    /// The line number in the input source where the token occurs.
115    pub span: Option<Span>,
116}
117
118impl CalcToken {
119    pub fn merge_span(&mut self, other_span: &Option<Span>) {
120        match other_span {
121            Some(other_span) => match &mut self.span {
122                Some(my_span) => {
123                    *my_span = my_span.merge(other_span);
124                }
125                None => {
126                    self.span = Some(*other_span);
127                }
128            },
129            None => (),
130        }
131    }
132
133    pub fn to_statement(&mut self, comment: Option<String>) {
134        self.token_id = TokenID::Stat;
135        match &mut self.value {
136            TokenValue::None => {
137                self.value = TokenValue::Stat {
138                    comments: if let Some(comment) = comment {
139                        vec![comment]
140                    } else {
141                        vec![]
142                    },
143                    value: None,
144                };
145            }
146            TokenValue::Comment(comment2) => {
147                self.value = TokenValue::Stat {
148                    comments: if let Some(comment) = comment {
149                        vec![comment, std::mem::take(comment2)]
150                    } else {
151                        vec![std::mem::take(comment2)]
152                    },
153                    value: None,
154                };
155            }
156            TokenValue::Ident(_) => panic!("unexpected token value in `to_statement`"),
157            TokenValue::Number(value) => {
158                self.value = TokenValue::Stat {
159                    comments: if let Some(comment) = comment {
160                        vec![comment]
161                    } else {
162                        vec![]
163                    },
164                    value: Some(*value),
165                };
166            }
167            TokenValue::Stat { comments, value } => {
168                let mut cs = std::mem::take(comments);
169                if let Some(comment) = comment {
170                    cs.insert(0, comment);
171                }
172                self.value = TokenValue::Stat {
173                    comments: cs,
174                    value: std::mem::take(value),
175                };
176            }
177        }
178    }
179}
180
181impl Token for CalcToken {
182    /// The associated identifier type used to classify this token.
183    type TokenID = TokenID;
184
185    /// Returns the token’s kind identifier.
186    fn token_id(&self) -> Self::TokenID {
187        self.token_id
188    }
189
190    /// Returns the line number where the token appears.
191    fn span(&self) -> Option<Span> {
192        self.span
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199    use parlex::{Position, span};
200
201    #[test]
202    fn token_value_number_extraction_with_let_else() {
203        let tok = TokenValue::Number(42);
204
205        // Ensure it's a number and extract the value
206        let TokenValue::Number(n) = tok else {
207            panic!("Expected a numeric token");
208        };
209
210        assert_eq!(n, 42);
211    }
212
213    #[test]
214    #[should_panic(expected = "Expected a numeric token")]
215    fn token_value_number_extraction_should_panic_if_not_number() {
216        let tok = TokenValue::Ident(5);
217
218        // This will panic due to pattern mismatch
219        let TokenValue::Number(_n) = tok else {
220            panic!("Expected a numeric token");
221        };
222    }
223
224    #[test]
225    fn token_value_ident_stores_symbol_index() {
226        let idx = 7usize;
227        let tok = TokenValue::Ident(idx);
228
229        if let TokenValue::Ident(i) = tok {
230            assert_eq!(i, idx);
231        } else {
232            panic!("Expected Ident token");
233        }
234    }
235
236    #[test]
237    fn token_value_none_matches() {
238        let tok = TokenValue::None;
239        assert!(matches!(tok, TokenValue::None));
240    }
241
242    #[test]
243    fn calc_token_trait_accessors_return_values() {
244        let t = CalcToken {
245            token_id: TokenID::Number,
246            value: TokenValue::Number(99),
247            span: span!(1, 2, 1, 10),
248        };
249
250        assert_eq!(t.token_id(), TokenID::Number);
251        assert_eq!(t.span().unwrap().start.column, 2);
252    }
253
254    #[test]
255    fn calc_token_with_identifier_round_trip() {
256        let t = CalcToken {
257            token_id: TokenID::Ident,
258            value: TokenValue::Ident(5),
259            span: span!(1, 2, 1, 10),
260        };
261
262        assert_eq!(t.token_id(), TokenID::Ident);
263
264        if let TokenValue::Ident(i) = t.value {
265            assert_eq!(i, 5);
266        } else {
267            panic!("Expected TokenValue::Ident");
268        }
269
270        assert_eq!(t.span().unwrap().display(), "span 1:2 to 1:10");
271    }
272
273    #[test]
274    fn calc_token_is_cloneable_and_debuggable() {
275        let t1 = CalcToken {
276            token_id: TokenID::Number,
277            value: TokenValue::Number(-1),
278            span: span!(10, 20, 12, 20),
279        };
280
281        let t2 = t1.clone();
282        assert_eq!(t2.token_id(), t1.token_id());
283        assert_eq!(t2.span().unwrap().display(), "span 10:20 to 12:20");
284
285        let dbg_out = format!("{t1:?}");
286        assert!(dbg_out.contains("CalcToken"));
287    }
288
289    #[test]
290    #[should_panic(expected = "Expected TokenValue::Ident")]
291    fn calc_token_with_identifier_should_panic_if_wrong_kind() {
292        let t = CalcToken {
293            token_id: TokenID::Number,
294            value: TokenValue::Number(0),
295            span: span!(10, 20, 12, 20),
296        };
297
298        if let TokenValue::Ident(_) = t.value {
299            // should never reach
300        } else {
301            panic!("Expected TokenValue::Ident");
302        }
303    }
304
305    fn sp(sl: usize, sc: usize, el: usize, ec: usize) -> Span {
306        Span::new(Position::new(sl, sc), Position::new(el, ec))
307    }
308
309    fn tok(token_id: TokenID, value: TokenValue, span: Option<Span>) -> CalcToken {
310        CalcToken {
311            token_id,
312            value,
313            span,
314        }
315    }
316
317    // 1) Existing span + other span => expand to cover both ends.
318    #[test]
319    fn merge_span_expands_existing_span_to_cover_both() {
320        let mut t = tok(
321            TokenID::Number,
322            TokenValue::Number(1),
323            Some(sp(0, 5, 0, 10)),
324        );
325        let other = Some(sp(0, 2, 0, 12));
326
327        t.merge_span(&other);
328
329        let m = t.span.unwrap();
330        assert_eq!(m.start, Position::new(0, 2));
331        assert_eq!(m.end, Position::new(0, 12));
332    }
333
334    // 2) self.span == None, other == Some(...) => set span.
335    #[test]
336    fn merge_span_sets_when_self_is_none() {
337        let mut t = tok(TokenID::Ident, TokenValue::Ident(0), None);
338        let new_span = Some(sp(1, 0, 1, 3));
339
340        t.merge_span(&new_span);
341
342        assert_eq!(t.span, new_span);
343    }
344
345    // 3) other == None => no-op (existing span preserved).
346    #[test]
347    fn merge_span_is_noop_when_other_is_none() {
348        let before = Some(sp(2, 4, 2, 9));
349        let mut t = tok(TokenID::Number, TokenValue::Number(0), before);
350
351        t.merge_span(&None);
352
353        assert_eq!(t.span, before);
354    }
355
356    // 4) self.span == None and other == None => remains None.
357    #[test]
358    fn merge_span_both_none_remains_none() {
359        let mut t = tok(TokenID::Number, TokenValue::Number(0), None);
360
361        t.merge_span(&None);
362
363        assert!(t.span.is_none());
364    }
365
366    // 5) other fully inside self => merged span unchanged.
367    #[test]
368    fn merge_span_other_within_self_no_change() {
369        let mut t = tok(
370            TokenID::Number,
371            TokenValue::Number(0),
372            Some(sp(5, 2, 5, 10)),
373        );
374        let inner = Some(sp(5, 4, 5, 7)); // strictly inside
375
376        t.merge_span(&inner);
377
378        assert_eq!(t.span, Some(sp(5, 2, 5, 10)));
379    }
380
381    // 6) cross-line merge: expands across lines correctly.
382    #[test]
383    fn merge_span_cross_line_expands() {
384        // self: [1:5 .. 2:3), other: [0:9 .. 3:1) => merged: [0:9 .. 3:1)
385        let mut t = tok(TokenID::Ident, TokenValue::Ident(1), Some(sp(1, 5, 2, 3)));
386        let other = Some(sp(0, 9, 3, 1));
387
388        t.merge_span(&other);
389
390        let m = t.span.unwrap();
391        assert_eq!(m.start, Position::new(0, 9));
392        assert_eq!(m.end, Position::new(3, 1));
393    }
394}