From 45dc99c8bc59fc3fa33f66d849b58a4927455f54 Mon Sep 17 00:00:00 2001 From: StreamDemon Date: Thu, 2 Jul 2026 19:11:30 +0800 Subject: [PATCH] Make Token and TokenKind Copy, drop parse-loop clones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the lexeme gone, a token is a payload-free kind plus a span — both trivially copyable. Deriving Copy lets every parse loop pass and return tokens by value, so the `.clone()` calls sprinkled through `at`, `eat`, `expect`, `bump`, `peek_kind`, and the recovery helpers all disappear. The PR #71 roadmap sketched this slot as "`at`/`eat`/`expect` take `&TokenKind`"; deriving Copy reaches the same goal (no clones in parse loops) with by-value call sites instead of reference threading, which only became possible after the span-slicing change landed. --- crates/sploosh-lexer/src/lib.rs | 4 ++-- crates/sploosh-parser/src/lib.rs | 33 ++++++++++++++------------------ 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/crates/sploosh-lexer/src/lib.rs b/crates/sploosh-lexer/src/lib.rs index b32f1ae..b8c0389 100644 --- a/crates/sploosh-lexer/src/lib.rs +++ b/crates/sploosh-lexer/src/lib.rs @@ -2,7 +2,7 @@ use sploosh_ast::Span; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Token { pub kind: TokenKind, pub span: Span, @@ -16,7 +16,7 @@ impl Token { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TokenKind { Ident, Keyword(Keyword), diff --git a/crates/sploosh-parser/src/lib.rs b/crates/sploosh-parser/src/lib.rs index bf53557..ae87b39 100644 --- a/crates/sploosh-parser/src/lib.rs +++ b/crates/sploosh-parser/src/lib.rs @@ -613,10 +613,7 @@ impl<'src> Parser<'src> { } else if self.eat_keyword(Keyword::Continue).is_some() { self.expect(TokenKind::Semi)?; statements.push(Stmt::Continue); - } else if self.at_ident_text("send") - && self - .peek_kind_at(1) - .is_some_and(|kind| can_begin_expr(&kind)) + } else if self.at_ident_text("send") && self.peek_kind_at(1).is_some_and(can_begin_expr) { // §2.7: `send` at statement head followed by any token that can // begin an expression always opens a send-statement; the operand @@ -830,7 +827,7 @@ impl<'src> Parser<'src> { } fn prefix(&mut self) -> Option { - let token = self.peek()?.clone(); + let token = *self.peek()?; match token.kind { TokenKind::IntLit | TokenKind::FloatLit | TokenKind::StringLit | TokenKind::CharLit => { self.bump(); @@ -1061,7 +1058,7 @@ impl<'src> Parser<'src> { fn args(&mut self, close: TokenKind) -> Option> { let mut args = Vec::new(); - while !self.at(close.clone()) && !self.eof() { + while !self.at(close) && !self.eof() { args.push(self.delimited_expr()?); if self.eat(TokenKind::Comma).is_none() { break; @@ -1120,7 +1117,7 @@ impl<'src> Parser<'src> { } fn ident(&mut self) -> Option { - let token = self.peek()?.clone(); + let token = *self.peek()?; match token.kind { TokenKind::Ident => { self.bump(); @@ -1197,9 +1194,9 @@ impl<'src> Parser<'src> { fn skip_balanced(&mut self, open: TokenKind, close: TokenKind) { let mut depth = 0usize; while !self.eof() { - if self.at(open.clone()) { + if self.at(open) { depth += 1; - } else if self.at(close.clone()) { + } else if self.at(close) { if depth == 0 { self.bump(); break; @@ -1218,9 +1215,9 @@ impl<'src> Parser<'src> { fn skip_balanced_after_open(&mut self, open: TokenKind, close: TokenKind) { let mut depth = 1usize; while !self.eof() { - if self.at(open.clone()) { + if self.at(open) { depth += 1; - } else if self.at(close.clone()) { + } else if self.at(close) { depth -= 1; self.bump(); if depth == 0 { @@ -1273,7 +1270,7 @@ impl<'src> Parser<'src> { } fn recover_until(&mut self, kinds: &[TokenKind]) { - while !self.eof() && !kinds.iter().any(|kind| self.at(kind.clone())) { + while !self.eof() && !kinds.iter().any(|kind| self.at(*kind)) { self.bump(); } } @@ -1315,7 +1312,7 @@ impl<'src> Parser<'src> { } fn expect(&mut self, kind: TokenKind) -> Option { - self.eat(kind.clone()).or_else(|| { + self.eat(kind).or_else(|| { self.error_here(format!("expected `{kind:?}`")); None }) @@ -1330,13 +1327,11 @@ impl<'src> Parser<'src> { } fn peek_kind(&self) -> Option { - self.peek().map(|token| token.kind.clone()) + self.peek().map(|token| token.kind) } fn peek_kind_at(&self, offset: usize) -> Option { - self.tokens - .get(self.pos + offset) - .map(|token| token.kind.clone()) + self.tokens.get(self.pos + offset).map(|token| token.kind) } fn can_start_path_segment_at(&self, offset: usize) -> bool { @@ -1361,7 +1356,7 @@ impl<'src> Parser<'src> { } fn bump(&mut self) -> Token { - let token = self.tokens[self.pos].clone(); + let token = self.tokens[self.pos]; self.pos += 1; token } @@ -1409,7 +1404,7 @@ fn is_assign_target(expr: &Expr) -> bool { } /// Tokens that can begin an expression — must stay in sync with `prefix()`. -fn can_begin_expr(kind: &TokenKind) -> bool { +fn can_begin_expr(kind: TokenKind) -> bool { matches!( kind, TokenKind::IntLit