diff --git a/TODO.md b/TODO.md index 7c846b8..685c3a7 100644 --- a/TODO.md +++ b/TODO.md @@ -11,12 +11,12 @@ Snapshot: 2026-07-28. - `cargo fmt --all -- --check` passes and is enforced for pull requests. - `cargo check --all-targets` passes. - `cargo test` passes and is enforced for pull requests, including all 48 integration specifications and the library doctest. +- `cargo clippy --all-targets --all-features -- -D warnings` passes locally. - The crate exposes checked file/source execution APIs and typed I/O, parse, and runtime error categories. - Lexer, parser, interpreter, type-inference, examples, mdBook documentation, and cargo-dist release assets exist. ### Quality gaps -- Strict Clippy reports 16 errors across the lexer, parser, type inference, and symbol table. - Panic recovery wraps rather than removes many panic, `unwrap`, and `expect` paths; the interpreter alone contains roughly 90. - `parser.rs` and `interpreter.rs` are approximately 975 and 1,217 lines and mix several responsibilities. - Checked and unchecked parsing/execution paths duplicate logic. @@ -72,7 +72,7 @@ Snapshot: 2026-07-28. - [x] Open and complete an issue that applies rustfmt, adds `cargo fmt --all -- --check` to CI, and changes no behavior. - [x] Fix the library doctest against the v0.1 API, make `cargo test` green, and require it in CI. - [x] Resolve strict Clippy findings in the interpreter without lint suppressions or behavior changes. -- [ ] Resolve the remaining strict Clippy findings in the lexer, parser, type inference, and symbol table. +- [x] Resolve the remaining strict Clippy findings in the lexer, parser, type inference, and symbol table. - [ ] Require `cargo clippy --all-targets --all-features -- -D warnings` in pull-request CI. - [ ] Separate generated mdBook output from sources and define one reproducible documentation build command. - [ ] Reconcile README commands, branch names, CI claims, supported features, and examples with executable behavior. diff --git a/src/lexer.rs b/src/lexer.rs index ce0191d..38ff670 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -144,7 +144,7 @@ impl Lexer { '0'..='9' => { let mut num_str: String = self.input[self.pos..] .chars() - .take_while(|c| c.is_digit(10)) + .take_while(|c| c.is_ascii_digit()) .collect(); self.pos += num_str.len(); @@ -157,7 +157,7 @@ impl Lexer { // It's a float let num_str2: String = self.input[self.pos..] .chars() - .take_while(|c| c.is_digit(10)) + .take_while(|c| c.is_ascii_digit()) .collect(); self.pos += num_str2.len(); num_str.push('.'); diff --git a/src/parser.rs b/src/parser.rs index 750392f..c1321f3 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -151,7 +151,7 @@ impl Parser { } } Token::Char(value) => { - let expr = Expr::Char(value.clone()); + let expr = Expr::Char(*value); self.advance(); Ok(expr) } @@ -225,7 +225,7 @@ impl Parser { fn parse_struct_compound_checked(&mut self, struct_name: String) -> Result { self.eat_checked(Token::LeftCurly)?; let mut props_exprs = HashMap::new(); - while &self.current_token != &Token::RightCurly { + while self.current_token != Token::RightCurly { if let Token::Identifier(key) = self.current_token.clone() { self.advance(); self.eat_checked(Token::Colon)?; @@ -505,10 +505,7 @@ impl Parser { while self.current_token != Token::RightCurly { body.push(self.parse_statement_checked()?); } - let is_returning = &body.iter().any(|s| match s { - Stmt::Return(_) => true, - _ => false, - }); + let is_returning = body.iter().any(|s| matches!(s, Stmt::Return(_))); if !is_returning { body.push(Stmt::Return(None)) } @@ -553,13 +550,11 @@ impl Parser { ))); } } - Stmt::Return(None) => { - if *expected_type != Type::Void { - return Err(LangError::parse(format!( - "Expected return type {:?}, but function returned nothing", - expected_type - ))); - } + Stmt::Return(None) if *expected_type != Type::Void => { + return Err(LangError::parse(format!( + "Expected return type {:?}, but function returned nothing", + expected_type + ))); } _ => {} } @@ -700,8 +695,7 @@ impl Parser { let step = if self.current_token == Token::Step { self.advance(); - let step_size = self.parse_expr_checked()?; - step_size + self.parse_expr_checked()? } else { Expr::Int(1) }; @@ -755,7 +749,7 @@ impl Parser { } else if let Token::Identifier(_) = &self.current_token { let mut final_module_part = String::new(); while let Token::Identifier(sub_part) = &self.current_token { - final_module_part.push_str(&sub_part); + final_module_part.push_str(sub_part); self.advance(); if let Token::DoubleColon = &self.current_token { final_module_part.push_str("::"); @@ -854,7 +848,7 @@ impl Parser { }; self.eat_checked(Token::LeftCurly)?; let mut impl_methods = Vec::new(); - while &self.current_token != &Token::RightCurly { + while self.current_token != Token::RightCurly { let impl_method = self.parse_function_declaration_checked()?; impl_methods.push(impl_method); } @@ -897,6 +891,8 @@ impl Parser { // Parse any statement fn parse_statement(&mut self) -> Stmt { + let identifier_is_reassignment = matches!(self.current_token, Token::Identifier(_)) + && matches!(self.peek_token(), Token::Assignment | Token::LeftBracket); match &self.current_token { Token::Use => self.parse_use(), Token::Struct => self.parse_struct(), @@ -906,16 +902,11 @@ impl Parser { Token::For => self.parse_for_in_range(), Token::Return => self.parse_return(), Token::Poo | Token::Mut => self.parse_assignment(), + Token::Identifier(_) if identifier_is_reassignment => self.parse_reassignment(), Token::Identifier(_) => { - if self.peek_token() == Token::Assignment || self.peek_token() == Token::LeftBracket - // vector index reassingment - { - self.parse_reassignment() - } else { - let expr = self.parse_expr(); - self.eat(Token::SemiColon); - Stmt::Expression(expr) - } + let expr = self.parse_expr(); + self.eat(Token::SemiColon); + Stmt::Expression(expr) } _ => { let expr = self.parse_expr(); @@ -927,6 +918,8 @@ impl Parser { // First checked parsing slice: expression statements use checked token consumption. fn parse_statement_checked(&mut self) -> Result { + let identifier_is_reassignment = matches!(self.current_token, Token::Identifier(_)) + && matches!(self.peek_token(), Token::Assignment | Token::LeftBracket); match &self.current_token { Token::Use => self.parse_use_checked(), Token::Struct => self.parse_struct_checked(), @@ -936,16 +929,11 @@ impl Parser { Token::For => self.parse_for_in_range_checked(), Token::Return => self.parse_return_checked(), Token::Poo | Token::Mut => self.parse_assignment_checked(), + Token::Identifier(_) if identifier_is_reassignment => self.parse_reassignment_checked(), Token::Identifier(_) => { - if self.peek_token() == Token::Assignment || self.peek_token() == Token::LeftBracket - // vector index reassignment - { - self.parse_reassignment_checked() - } else { - let expr = self.parse_expr_checked()?; - self.eat_checked(Token::SemiColon)?; - Ok(Stmt::Expression(expr)) - } + let expr = self.parse_expr_checked()?; + self.eat_checked(Token::SemiColon)?; + Ok(Stmt::Expression(expr)) } _ => { let expr = self.parse_expr_checked()?; diff --git a/src/type_inference.rs b/src/type_inference.rs index 6d7a656..5e21825 100644 --- a/src/type_inference.rs +++ b/src/type_inference.rs @@ -13,7 +13,7 @@ pub fn infer_expr_type(expr: &Expr, symbol_table: &ScopedSymbolTable) -> Type { Expr::String(_) => Type::String, Expr::Boolean(_) => Type::Bool, Expr::Vector(v, _) => { - if let Some(a) = v.get(0) { + if let Some(a) = v.first() { infer_expr_type(a, symbol_table) } else { Type::Vector(Box::new(Type::Void)) @@ -31,7 +31,7 @@ pub fn infer_expr_type(expr: &Expr, symbol_table: &ScopedSymbolTable) -> Type { Expr::Identifier(name) => symbol_table .get(name) .cloned() - .expect(&format!("Undefined variable: {}", name)), + .unwrap_or_else(|| panic!("Undefined variable: {}", name)), Expr::UnaryOp(_, _) => Type::Bool, Expr::BinaryOp(lhs, op, rhs) => { let lhs_type = infer_expr_type(lhs, symbol_table); @@ -114,7 +114,7 @@ pub fn infer_expr_type(expr: &Expr, symbol_table: &ScopedSymbolTable) -> Type { let func_type = symbol_table .get(name) .cloned() - .expect(&format!("Undefined function: {}", name)); + .unwrap_or_else(|| panic!("Undefined function: {}", name)); match func_type { Type::Function(params, return_type) => { @@ -164,7 +164,7 @@ pub fn infer_stmt_types(stmt: &Stmt, symbol_table: &mut ScopedSymbolTable) { let expr_type = infer_expr_type(expr, symbol_table); let var_type = symbol_table .get(name) - .expect(&format!("Variable '{}' used before declaration", name)); + .unwrap_or_else(|| panic!("Variable '{}' used before declaration", name)); if var_type != &expr_type { panic!( diff --git a/src/visitor.rs b/src/visitor.rs index 4295cc7..4a3b48d 100644 --- a/src/visitor.rs +++ b/src/visitor.rs @@ -6,6 +6,12 @@ pub struct ScopedSymbolTable { scopes: Vec>, } +impl Default for ScopedSymbolTable { + fn default() -> Self { + Self::new() + } +} + impl ScopedSymbolTable { pub fn new() -> Self { Self {