diff --git a/Cargo.lock b/Cargo.lock index aa72fe2b..01f98749 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3674,6 +3674,24 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "subtle" version = "2.6.1" @@ -4081,6 +4099,8 @@ version = "2.0.0" dependencies = [ "serde", "serde_json", + "strum", + "strum_macros", "techscript_common", ] diff --git a/compiler/syntax/Cargo.toml b/compiler/syntax/Cargo.toml index 251d67b8..b8ae747b 100644 --- a/compiler/syntax/Cargo.toml +++ b/compiler/syntax/Cargo.toml @@ -7,6 +7,8 @@ description = "Token definitions, keywords, and operator precedence for TechScri [dependencies] techscript_common = { path = "../common" } serde = { workspace = true } +strum = "0.28.0" +strum_macros = "0.28.0" [dev-dependencies] serde_json = { workspace = true } diff --git a/compiler/syntax/src/token_kind.rs b/compiler/syntax/src/token_kind.rs index 1092b8e8..42c33cb6 100644 --- a/compiler/syntax/src/token_kind.rs +++ b/compiler/syntax/src/token_kind.rs @@ -5,7 +5,9 @@ use serde::{Deserialize, Serialize}; use std::fmt; /// Classification of literal values in TechScript 2.0. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive( + Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, strum_macros::EnumIter, +)] pub enum LiteralKind { /// Integer literal (e.g., `42`). Int, @@ -50,7 +52,9 @@ impl fmt::Display for NumericLiteralKind { /// - **Canonical 2.0**: The one true spelling — no warnings emitted. /// - **Deprecated Alias**: Old spelling — still parsed, but emits a `TSW1xxx` warning. /// - **Reserved**: Recognised by the lexer but not yet active. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive( + Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, strum_macros::EnumIter, +)] pub enum TokenKind { // ── Canonical 2.0 Keywords ────────────────────────────────────────────────────── /// `do` — function/method declaration (replaces `build`, `fun`, `function`) diff --git a/compiler/syntax/tests/syntax_tests.rs b/compiler/syntax/tests/syntax_tests.rs index f895bfc5..f0bfcdac 100644 --- a/compiler/syntax/tests/syntax_tests.rs +++ b/compiler/syntax/tests/syntax_tests.rs @@ -378,114 +378,20 @@ fn test_serde_roundtrips() { #[test] fn test_token_uniqueness() { + use strum::IntoEnumIterator; // Ensure all TokenKind static_lexemes are unique let mut lexemes = HashSet::new(); - let kinds = [ - TokenKind::Do, - TokenKind::Send, - TokenKind::When, - TokenKind::Loop, - TokenKind::Repeat, - TokenKind::For, - TokenKind::In, - TokenKind::Match, - TokenKind::Case, - TokenKind::Default, - TokenKind::Try, - TokenKind::Catch, - TokenKind::Throw, - TokenKind::Use, - TokenKind::Class, - TokenKind::Struct, - TokenKind::Enum, - TokenKind::Trait, - TokenKind::Interface, - TokenKind::Const, - TokenKind::Null, - TokenKind::Say, - TokenKind::Ask, - TokenKind::Break, - TokenKind::Continue, - TokenKind::Else, - TokenKind::Async, - TokenKind::Await, - TokenKind::Parallel, - TokenKind::End, - TokenKind::Export, - TokenKind::New, - TokenKind::SelfKw, - TokenKind::True, - TokenKind::False, - TokenKind::Typeof, - TokenKind::With, - TokenKind::Build, - TokenKind::Make, - TokenKind::Return, - TokenKind::Model, - TokenKind::If, - TokenKind::Elif, - TokenKind::While, - TokenKind::Import, - TokenKind::From, - TokenKind::Let, - TokenKind::Var, - TokenKind::Fun, - TokenKind::Function, - TokenKind::Attempt, - TokenKind::None, - TokenKind::Keep, - TokenKind::Give, - TokenKind::Stop, - TokenKind::Skip, - TokenKind::Each, - TokenKind::Switch, - TokenKind::Be, - TokenKind::Equals, - TokenKind::Then, - TokenKind::Plus, - TokenKind::Minus, - TokenKind::Star, - TokenKind::Slash, - TokenKind::DoubleSlash, - TokenKind::Percent, - TokenKind::DoubleStar, - TokenKind::EqualEqual, - TokenKind::BangEqual, - TokenKind::TripleEqual, - TokenKind::BangEqualEqual, - TokenKind::Less, - TokenKind::Greater, - TokenKind::LessEqual, - TokenKind::GreaterEqual, - TokenKind::Equal, - TokenKind::PlusEqual, - TokenKind::MinusEqual, - TokenKind::StarEqual, - TokenKind::SlashEqual, - TokenKind::PercentEqual, - TokenKind::DotDot, - TokenKind::DotDotEqual, - TokenKind::QuestionDot, - TokenKind::QuestionQuestion, - TokenKind::Arrow, - TokenKind::LeftParen, - TokenKind::RightParen, - TokenKind::LeftBrace, - TokenKind::RightBrace, - TokenKind::LeftBracket, - TokenKind::RightBracket, - TokenKind::Comma, - TokenKind::Dot, - TokenKind::Colon, - TokenKind::Semicolon, - ]; - for kind in kinds { - let lexeme = kind.static_lexeme().unwrap(); - assert!( - lexemes.insert(lexeme), - "Duplicate static lexeme detected for: {:?}", - kind - ); + for kind in TokenKind::iter() { + if kind == TokenKind::FStringExprStart || kind == TokenKind::FStringExprEnd { + continue; // Overlaps with LeftBrace/RightBrace logically, but handled contextually + } + if let Some(lexeme) = kind.static_lexeme() { + assert!( + lexemes.insert(lexeme), + "Duplicate static lexeme detected for: {:?}", + kind + ); + } } }