use crate::ast::*; use crate::lexer::{LexError, Token, TokenKind}; use anyhow::{Result, anyhow}; use thiserror::Error; /// Parse errors for nftables configuration #[derive(Error, Debug)] pub enum ParseError { #[error( "Unexpected token at line {line}, column {column}: expected {expected}, found '{found}'" )] UnexpectedToken { line: usize, column: usize, expected: String, found: String, }, #[error("Missing token: expected {expected}")] MissingToken { expected: String }, #[error("Invalid expression: {message}")] InvalidExpression { message: String }, #[error("Invalid statement: {message}")] InvalidStatement { message: String }, #[error("Lexical error: {0}")] LexError(#[from] LexError), #[error("Semantic error: {message}")] SemanticError { message: String }, #[error("Anyhow error: {0}")] AnyhowError(#[from] anyhow::Error), } /// Result type for parsing operations pub type ParseResult = Result; /// Parsing context for maintaining state during parsing #[derive(Debug)] pub struct ParseContext { pub in_table: bool, pub in_chain: bool, pub in_rule: bool, pub current_table: Option, pub current_chain: Option, } impl ParseContext { fn new() -> Self { Self { in_table: false, in_chain: false, in_rule: false, current_table: None, current_chain: None, } } } /// Parser for nftables configuration files #[derive(Debug)] pub struct Parser { tokens: Vec, current: usize, context: ParseContext, } impl Parser { /// Create a new parser with the given tokens pub fn new(tokens: Vec) -> Self { Self { tokens, current: 0, context: ParseContext::new(), } } /// Parse the entire ruleset pub fn parse(&mut self) -> ParseResult { let mut ruleset = Ruleset::new(); // Skip initial whitespace and newlines self.skip_whitespace(); // Parse shebang if present if self.current_token_is(&TokenKind::Shebang(String::new())) { if let Some(token) = self.advance() { if let TokenKind::Shebang(content) = &token.kind { ruleset = ruleset.with_shebang(content.clone()); } } self.skip_whitespace(); } // Parse top-level statements while !self.is_at_end() { self.skip_whitespace(); if self.is_at_end() { break; } match self.peek().map(|t| &t.kind) { Some(TokenKind::Include) => { let include = self.parse_include()?; ruleset = ruleset.add_include(include); } Some(TokenKind::Define) => { let define = self.parse_define()?; ruleset = ruleset.add_define(define); } Some(TokenKind::Table) => { let table = self.parse_table()?; ruleset = ruleset.add_table(table); } Some(TokenKind::CommentLine(_)) => { if let Some(token) = self.advance() { if let TokenKind::CommentLine(comment_text) = &token.kind { ruleset = ruleset.add_comment(comment_text.clone()); } } } Some(TokenKind::Newline) => { self.advance(); } _ => { return Err(ParseError::UnexpectedToken { line: 1, column: 1, expected: "include, define, table, or comment".to_string(), found: self .peek() .map(|t| t.text.clone()) .unwrap_or("EOF".to_string()), }); } } } Ok(ruleset) } /// Parse the entire ruleset with error recovery pub fn parse_with_errors(source: &str) -> (Option, Vec) { let mut lexer = crate::lexer::NftablesLexer::new(source); let tokens = lexer.tokenize_with_errors(); // Use the error-recovery tokenizer let mut parser = Self::new(tokens); let mut errors = Vec::new(); match parser.parse() { Ok(ruleset) => (Some(ruleset), errors), Err(err) => { errors.push(err); (None, errors) } } } /// Parse an include statement fn parse_include(&mut self) -> ParseResult { self.consume(TokenKind::Include, "Expected 'include'")?; let path = match self.advance() { Some(token) if matches!(token.kind, TokenKind::StringLiteral(_)) => match &token.kind { TokenKind::StringLiteral(s) => s.clone(), _ => unreachable!(), }, None => { return Err(ParseError::MissingToken { expected: "string literal after 'include'".to_string(), }); } Some(token) => { return Err(ParseError::UnexpectedToken { line: 1, column: 1, expected: "string literal".to_string(), found: token.text.clone(), }); } }; Ok(Include { path }) } /// Parse a define statement fn parse_define(&mut self) -> ParseResult { self.consume(TokenKind::Define, "Expected 'define'")?; let name = match self.advance() { Some(token) if matches!(token.kind, TokenKind::Identifier(_)) => match &token.kind { TokenKind::Identifier(s) => s.clone(), _ => unreachable!(), }, None => { return Err(ParseError::MissingToken { expected: "identifier after 'define'".to_string(), }); } Some(token) => { return Err(ParseError::UnexpectedToken { line: 1, column: 1, expected: "identifier".to_string(), found: token.text.clone(), }); } }; self.consume(TokenKind::Assign, "Expected '=' after define name")?; let value = self.parse_expression()?; Ok(Define { name, value }) } /// Parse a table definition fn parse_table(&mut self) -> Result { self.consume(TokenKind::Table, "Expected 'table'")?; let family = self.parse_family()?; let name = self.parse_identifier_or_keyword()?; // Set parse context self.context.in_table = true; self.context.current_table = Some(name.clone()); let mut table = Table::new(family, name); self.consume(TokenKind::LeftBrace, "Expected '{' after table name")?; self.skip_whitespace(); while !self.current_token_is(&TokenKind::RightBrace) && !self.is_at_end() { match self.peek().map(|t| &t.kind) { Some(TokenKind::Chain) => { let chain = self.parse_chain()?; table = table.add_chain(chain); } Some(TokenKind::CommentLine(_)) => { self.advance(); } Some(TokenKind::Newline) => { self.advance(); } _ => { return Err(ParseError::SemanticError { message: format!( "Unexpected token in table: {}", self.peek().map(|t| t.text.as_str()).unwrap_or("EOF") ), } .into()); } } self.skip_whitespace(); } self.consume(TokenKind::RightBrace, "Expected '}' to close table")?; // Reset parse context self.context.in_table = false; self.context.current_table = None; Ok(table) } /// Parse a chain definition fn parse_chain(&mut self) -> Result { self.consume(TokenKind::Chain, "Expected 'chain'")?; let name = self.parse_identifier_or_keyword()?; // Set parse context self.context.in_chain = true; self.context.current_chain = Some(name.clone()); let mut chain = Chain::new(name); self.consume(TokenKind::LeftBrace, "Expected '{' after chain name")?; self.skip_whitespace(); // Parse chain properties and rules while !self.current_token_is(&TokenKind::RightBrace) && !self.is_at_end() { match self.peek().map(|t| &t.kind) { Some(TokenKind::Type) => { self.advance(); // consume 'type' let chain_type = self.parse_chain_type()?; chain = chain.with_type(chain_type); if self.current_token_is(&TokenKind::Hook) { self.advance(); // consume 'hook' let hook = self.parse_hook()?; chain = chain.with_hook(hook); } if self.current_token_is(&TokenKind::Priority) { self.advance(); // consume 'priority' let priority = self.parse_signed_number()? as i32; chain = chain.with_priority(priority); } // Check for device specification (for netdev family) if self.current_token_is(&TokenKind::Identifier("device".to_string())) { self.advance(); // consume 'device' let device = self.parse_string_or_identifier()?; chain = chain.with_device(device); } self.consume(TokenKind::Semicolon, "Expected ';' after chain properties")?; if self.current_token_is(&TokenKind::Policy) { self.advance(); // consume 'policy' let policy = self.parse_policy()?; chain = chain.with_policy(policy); } self.consume(TokenKind::Semicolon, "Expected ';' after policy")?; } Some(TokenKind::CommentLine(_)) => { self.advance(); } Some(TokenKind::Newline) => { self.advance(); } _ => { // Parse rule let rule = self.parse_rule()?; chain = chain.add_rule(rule); } } self.skip_whitespace(); } self.consume(TokenKind::RightBrace, "Expected '}' to close chain")?; // Reset parse context self.context.in_chain = false; self.context.current_chain = None; Ok(chain) } /// Parse a rule fn parse_rule(&mut self) -> Result { // Set parse context self.context.in_rule = true; let mut expressions = Vec::new(); let mut action = None; // Parse expressions and action while !self.current_token_is(&TokenKind::Newline) && !self.current_token_is(&TokenKind::RightBrace) && !self.is_at_end() { // Check for actions first match self.peek().map(|t| &t.kind) { Some(TokenKind::Accept) => { self.advance(); action = Some(Action::Accept); break; } Some(TokenKind::Drop) => { self.advance(); action = Some(Action::Drop); break; } Some(TokenKind::Reject) => { self.advance(); action = Some(Action::Reject); break; } Some(TokenKind::Return) => { self.advance(); action = Some(Action::Return); break; } Some(TokenKind::Jump) => { self.advance(); let target = self.parse_identifier()?; action = Some(Action::Jump(target)); break; } Some(TokenKind::Goto) => { self.advance(); let target = self.parse_identifier()?; action = Some(Action::Goto(target)); break; } Some(TokenKind::Continue) => { self.advance(); action = Some(Action::Continue); break; } Some(TokenKind::Log) => { self.advance(); // Parse optional log prefix and level let mut prefix = None; let mut level = None; // Check for "prefix" keyword if self.current_token_is(&TokenKind::Identifier("prefix".to_string())) { self.advance(); if let Some(TokenKind::StringLiteral(p)) = self.peek().map(|t| &t.kind) { prefix = Some(p.clone()); self.advance(); } } // Check for "level" keyword if self.current_token_is(&TokenKind::Identifier("level".to_string())) { self.advance(); if let Some(TokenKind::Identifier(l)) = self.peek().map(|t| &t.kind) { level = Some(l.clone()); self.advance(); } } action = Some(Action::Log { prefix, level }); break; } Some(TokenKind::Comment) => { self.advance(); if let Some(TokenKind::StringLiteral(text)) = self.peek().map(|t| &t.kind) { let text = text.clone(); self.advance(); action = Some(Action::Comment(text)); break; } else { return Err(ParseError::InvalidStatement { message: "Expected string literal after 'comment'".to_string(), } .into()); } } _ => { let expr = self.parse_expression()?; expressions.push(expr); } } } let action = action.unwrap_or(Action::Accept); // Reset parse context self.context.in_rule = false; let mut rule = Rule::new(expressions, action); // Check if we need to assign a handle (for demonstration purposes) // In a real implementation, this might come from the source or be generated if self.context.in_chain && rule.expressions.len() > 2 { // Add handle for complex rules (arbitrary example) rule = rule.with_handle(self.current.try_into().unwrap()); // Use current position as handle } Ok(rule) } /// Parse an expression with operator precedence fn parse_expression(&mut self) -> Result { self.parse_comparison_expression() } /// Parse comparison expressions (==, !=, <, <=, >, >=) fn parse_comparison_expression(&mut self) -> Result { let mut expr = self.parse_range_expression()?; while let Some(token) = self.peek() { let operator = match &token.kind { TokenKind::Eq => BinaryOperator::Eq, TokenKind::Ne => BinaryOperator::Ne, TokenKind::Lt => BinaryOperator::Lt, TokenKind::Le => BinaryOperator::Le, TokenKind::Gt => BinaryOperator::Gt, TokenKind::Ge => BinaryOperator::Ge, _ => break, }; self.advance(); // consume operator let right = self.parse_range_expression()?; expr = Expression::Binary { left: Box::new(expr), operator, right: Box::new(right), }; } Ok(expr) } /// Parse range expressions (e.g., 1-100, 192.168.1.0-192.168.1.255) fn parse_range_expression(&mut self) -> Result { let start = self.parse_primary_expression()?; // Check for range operator (dash/minus) if self.current_token_is(&TokenKind::Dash) { self.advance(); // consume '-' let end = self.parse_primary_expression()?; Ok(Expression::Range { start: Box::new(start), end: Box::new(end), }) } else { Ok(start) } } /// Parse primary expressions (literals, identifiers, etc.) fn parse_primary_expression(&mut self) -> Result { match self.peek().map(|t| &t.kind) { // Connection tracking Some(TokenKind::Ct) => { self.advance(); // consume 'ct' let field = self.parse_identifier_or_keyword()?; let value = Box::new(self.parse_ct_value()?); Ok(Expression::ConnTrack { field, value }) } // Interface matching Some(TokenKind::Iifname) => { self.advance(); // consume 'iifname' let interface_name = self.parse_string_or_identifier()?; Ok(Expression::Interface { direction: InterfaceDirection::Input, name: interface_name, }) } Some(TokenKind::Oifname) => { self.advance(); // consume 'oifname' let interface_name = self.parse_string_or_identifier()?; Ok(Expression::Interface { direction: InterfaceDirection::Output, name: interface_name, }) } // Port matching Some(TokenKind::Sport) => { self.advance(); // consume 'sport' let value = Box::new(self.parse_expression()?); Ok(Expression::Port { direction: PortDirection::Source, value, }) } Some(TokenKind::Dport) => { self.advance(); // consume 'dport' let value = Box::new(self.parse_expression()?); Ok(Expression::Port { direction: PortDirection::Destination, value, }) } // Address matching Some(TokenKind::Saddr) => { self.advance(); // consume 'saddr' let value = Box::new(self.parse_expression()?); Ok(Expression::Address { direction: AddressDirection::Source, value, }) } Some(TokenKind::Daddr) => { self.advance(); // consume 'daddr' let value = Box::new(self.parse_expression()?); Ok(Expression::Address { direction: AddressDirection::Destination, value, }) } // Protocol keywords as expressions Some(TokenKind::Ip) => { self.advance(); // Check if followed by 'protocol', 'saddr', or 'daddr' if self.current_token_is(&TokenKind::Protocol) { self.advance(); // consume 'protocol' let protocol = self.parse_identifier_or_keyword()?; Ok(Expression::Protocol(protocol)) } else if self.current_token_is(&TokenKind::Saddr) { self.advance(); // consume 'saddr' let value = Box::new(self.parse_expression()?); Ok(Expression::Address { direction: AddressDirection::Source, value, }) } else if self.current_token_is(&TokenKind::Daddr) { self.advance(); // consume 'daddr' let value = Box::new(self.parse_expression()?); Ok(Expression::Address { direction: AddressDirection::Destination, value, }) } else { Ok(Expression::Identifier("ip".to_string())) } } Some(TokenKind::Ip6) => { self.advance(); // Check if followed by 'nexthdr', 'saddr', or 'daddr' if self.current_token_is(&TokenKind::Nexthdr) { self.advance(); // consume 'nexthdr' let protocol = self.parse_identifier_or_keyword()?; Ok(Expression::Protocol(protocol)) } else if self.current_token_is(&TokenKind::Saddr) { self.advance(); // consume 'saddr' let value = Box::new(self.parse_expression()?); Ok(Expression::Address { direction: AddressDirection::Source, value, }) } else if self.current_token_is(&TokenKind::Daddr) { self.advance(); // consume 'daddr' let value = Box::new(self.parse_expression()?); Ok(Expression::Address { direction: AddressDirection::Destination, value, }) } else { Ok(Expression::Identifier("ip6".to_string())) } } Some(TokenKind::Tcp) => { self.advance(); Ok(Expression::Protocol("tcp".to_string())) } Some(TokenKind::Udp) => { self.advance(); Ok(Expression::Protocol("udp".to_string())) } Some(TokenKind::Icmp) => { self.advance(); Ok(Expression::Protocol("icmp".to_string())) } Some(TokenKind::Icmpv6) => { self.advance(); Ok(Expression::Protocol("icmpv6".to_string())) } // Connection states as expressions Some(TokenKind::Established) => { self.advance(); Ok(Expression::Identifier("established".to_string())) } Some(TokenKind::Related) => { self.advance(); Ok(Expression::Identifier("related".to_string())) } Some(TokenKind::Invalid) => { self.advance(); Ok(Expression::Identifier("invalid".to_string())) } Some(TokenKind::New) => { self.advance(); Ok(Expression::Identifier("new".to_string())) } Some(TokenKind::Identifier(name)) => { let name = name.clone(); self.advance(); // Check for special identifier patterns match name.as_str() { "ip" | "ip6" | "tcp" | "udp" | "icmp" | "icmpv6" => { if self.current_token_is(&TokenKind::Protocol) { self.advance(); // consume 'protocol' let protocol = self.parse_identifier()?; Ok(Expression::Protocol(protocol)) } else { Ok(Expression::Identifier(name)) } } _ => Ok(Expression::Identifier(name)), } } Some(TokenKind::StringLiteral(text)) => { let text = text.clone(); self.advance(); Ok(Expression::String(text)) } Some(TokenKind::NumberLiteral(_)) => { let num = self.parse_number()?; // Check for rate expression (e.g., 10/minute) if self.current_token_is(&TokenKind::Slash) { self.advance(); // consume '/' if let Some(token) = self.peek() { if matches!(token.kind, TokenKind::Identifier(_)) { let unit = self.advance().unwrap().text.clone(); Ok(Expression::String(format!("{}/{}", num, unit))) } else { Err(ParseError::InvalidExpression { message: "Expected identifier after '/' in rate expression" .to_string(), } .into()) } } else { Err(ParseError::InvalidExpression { message: "Expected identifier after '/' in rate expression".to_string(), } .into()) } } else { Ok(Expression::Number(num)) } } Some(TokenKind::IpAddress(_)) => { let addr = self.advance().unwrap().text.clone(); // Check for CIDR notation (e.g., 192.168.1.0/24) if self.current_token_is(&TokenKind::Slash) { self.advance(); // consume '/' if let Some(token) = self.peek() { if matches!(token.kind, TokenKind::NumberLiteral(_)) { let prefix_len = self.advance().unwrap().text.clone(); Ok(Expression::IpAddress(format!("{}/{}", addr, prefix_len))) } else { Err(ParseError::InvalidExpression { message: "Expected number after '/' in CIDR notation".to_string(), } .into()) } } else { Err(ParseError::InvalidExpression { message: "Expected number after '/' in CIDR notation".to_string(), } .into()) } // Check for port specification (e.g., 192.168.1.100:80) } else if self.current_token_is(&TokenKind::Colon) { self.advance(); // consume ':' if let Some(token) = self.peek() { if matches!(token.kind, TokenKind::NumberLiteral(_)) { let port = self.advance().unwrap().text.clone(); Ok(Expression::String(format!("{}:{}", addr, port))) } else { Err(ParseError::InvalidExpression { message: "Expected number after ':' in address:port specification" .to_string(), } .into()) } } else { Err(ParseError::InvalidExpression { message: "Expected number after ':' in address:port specification" .to_string(), } .into()) } } else { Ok(Expression::IpAddress(addr)) } } Some(TokenKind::Ipv6Address(_)) => { let addr = self.advance().unwrap().text.clone(); Ok(Expression::Ipv6Address(addr)) } Some(TokenKind::MacAddress(_)) => { let addr = self.advance().unwrap().text.clone(); Ok(Expression::MacAddress(addr)) } Some(TokenKind::LeftBrace) => { self.advance(); // consume '{' let mut elements = Vec::new(); while !self.current_token_is(&TokenKind::RightBrace) && !self.is_at_end() { if self.current_token_is(&TokenKind::Comma) { self.advance(); continue; } if self.current_token_is(&TokenKind::Newline) { self.advance(); continue; } let element = self.parse_expression()?; elements.push(element); } self.consume(TokenKind::RightBrace, "Expected '}' to close set")?; Ok(Expression::Set(elements)) } _ => Err(ParseError::InvalidExpression { message: format!( "Unexpected token in expression: {}", self.peek().map(|t| t.text.as_str()).unwrap_or("EOF") ), } .into()), } } /// Parse connection tracking values (handles comma-separated lists) fn parse_ct_value(&mut self) -> Result { let mut values = Vec::new(); loop { let value = match self.peek().map(|t| &t.kind) { Some(TokenKind::Established) => { self.advance(); Expression::Identifier("established".to_string()) } Some(TokenKind::Related) => { self.advance(); Expression::Identifier("related".to_string()) } Some(TokenKind::Invalid) => { self.advance(); Expression::Identifier("invalid".to_string()) } Some(TokenKind::New) => { self.advance(); Expression::Identifier("new".to_string()) } Some(TokenKind::Identifier(name)) => { let name = name.clone(); self.advance(); Expression::Identifier(name) } _ => return self.parse_expression(), }; values.push(value); // Check if there's a comma for more values if self.current_token_is(&TokenKind::Comma) { self.advance(); // consume comma continue; } else { break; } } if values.len() == 1 { Ok(values.into_iter().next().unwrap()) } else { Ok(Expression::Set(values)) } } /// Helper methods fn parse_family(&mut self) -> ParseResult { match self.advance() { Some(token) => match token.kind { TokenKind::Ip => Ok(Family::Ip), TokenKind::Ip6 => Ok(Family::Ip6), TokenKind::Inet => Ok(Family::Inet), TokenKind::Arp => Ok(Family::Arp), TokenKind::Bridge => Ok(Family::Bridge), TokenKind::Netdev => Ok(Family::Netdev), _ => { let token_clone = token.clone(); Err(self.unexpected_token_error_with_token( "family (ip, ip6, inet, arp, bridge, netdev)".to_string(), &token_clone, )) } }, None => Err(ParseError::MissingToken { expected: "family".to_string(), }), } } fn parse_chain_type(&mut self) -> ParseResult { match self.advance() { Some(token) => match token.kind { TokenKind::Filter => Ok(ChainType::Filter), TokenKind::Nat => Ok(ChainType::Nat), TokenKind::Route => Ok(ChainType::Route), _ => { let token_clone = token.clone(); Err(self.unexpected_token_error_with_token( "chain type (filter, nat, route)".to_string(), &token_clone, )) } }, None => Err(ParseError::MissingToken { expected: "chain type".to_string(), }), } } fn parse_hook(&mut self) -> ParseResult { match self.advance() { Some(token) => match token.kind { TokenKind::Input => Ok(Hook::Input), TokenKind::Output => Ok(Hook::Output), TokenKind::Forward => Ok(Hook::Forward), TokenKind::Prerouting => Ok(Hook::Prerouting), TokenKind::Postrouting => Ok(Hook::Postrouting), _ => { let token_clone = token.clone(); Err(self.unexpected_token_error_with_token( "hook (input, output, forward, prerouting, postrouting)".to_string(), &token_clone, )) } }, None => Err(ParseError::MissingToken { expected: "hook".to_string(), }), } } fn parse_policy(&mut self) -> Result { match self.advance() { Some(token) => match token.kind { TokenKind::Accept => Ok(Policy::Accept), TokenKind::Drop => Ok(Policy::Drop), _ => Err(anyhow!( "Expected policy (accept, drop), got: {}", token.text )), }, None => Err(anyhow!("Expected policy")), } } fn parse_identifier(&mut self) -> Result { match self.advance() { Some(token) if matches!(token.kind, TokenKind::Identifier(_)) => match &token.kind { TokenKind::Identifier(s) => Ok(s.clone()), _ => unreachable!(), }, Some(token) => Err(anyhow!("Expected identifier, got: {}", token.text)), None => Err(anyhow!("Expected identifier")), } } fn parse_identifier_or_keyword(&mut self) -> Result { match self.advance() { Some(token) => { // Accept identifiers or keywords that can be used as identifiers match &token.kind { TokenKind::Identifier(s) => Ok(s.clone()), // Allow keywords to be used as identifiers in certain contexts TokenKind::Filter | TokenKind::Nat | TokenKind::Route | TokenKind::Input | TokenKind::Output | TokenKind::Forward | TokenKind::Prerouting | TokenKind::Postrouting | TokenKind::Accept | TokenKind::Drop | TokenKind::Reject | TokenKind::State | TokenKind::Ct | TokenKind::Type | TokenKind::Hook | TokenKind::Priority | TokenKind::Policy | TokenKind::Tcp | TokenKind::Udp | TokenKind::Icmp | TokenKind::Icmpv6 | TokenKind::Ip | TokenKind::Ip6 => Ok(token.text.clone()), _ => Err(anyhow!( "Expected identifier or keyword, got: {}", token.text )), } } None => Err(anyhow!("Expected identifier or keyword")), } } fn parse_string_or_identifier(&mut self) -> Result { match self.advance() { Some(token) if matches!( token.kind, TokenKind::Identifier(_) | TokenKind::StringLiteral(_) ) => { match &token.kind { TokenKind::Identifier(s) => Ok(s.clone()), TokenKind::StringLiteral(s) => Ok(s.clone()), _ => unreachable!(), } } Some(token) => Err(anyhow!( "Expected string or identifier, got: {}", token.text )), None => Err(anyhow!("Expected string or identifier")), } } fn parse_number(&mut self) -> Result { match self.advance() { Some(token) if matches!(token.kind, TokenKind::NumberLiteral(_)) => match &token.kind { TokenKind::NumberLiteral(n) => Ok(*n), _ => unreachable!(), }, Some(token) => Err(anyhow!("Expected number, got: {}", token.text)), None => Err(anyhow!("Expected number")), } } fn parse_signed_number(&mut self) -> Result { // Check if we have a dash (negative sign) if self.current_token_is(&TokenKind::Dash) { self.advance(); // consume the dash match self.advance() { Some(token) if matches!(token.kind, TokenKind::NumberLiteral(_)) => { match &token.kind { TokenKind::NumberLiteral(n) => Ok(-(*n as i64)), _ => unreachable!(), } } Some(token) => Err(anyhow!("Expected number after '-', got: {}", token.text)), None => Err(anyhow!("Expected number after '-'")), } } else { // No dash, parse as positive number match self.advance() { Some(token) if matches!(token.kind, TokenKind::NumberLiteral(_)) => { match &token.kind { TokenKind::NumberLiteral(n) => Ok(*n as i64), _ => unreachable!(), } } Some(token) => Err(anyhow!("Expected number, got: {}", token.text)), None => Err(anyhow!("Expected number")), } } } // Navigation and utility methods fn advance(&mut self) -> Option<&Token> { if !self.is_at_end() { self.current += 1; } self.previous() } fn is_at_end(&self) -> bool { self.current >= self.tokens.len() } fn peek(&self) -> Option<&Token> { self.tokens.get(self.current) } fn previous(&self) -> Option<&Token> { if self.current > 0 { self.tokens.get(self.current - 1) } else { None } } fn current_token_is(&self, kind: &TokenKind) -> bool { match self.peek() { Some(token) => std::mem::discriminant(&token.kind) == std::mem::discriminant(kind), None => false, } } fn consume(&mut self, expected: TokenKind, message: &str) -> Result<()> { if self.current_token_is(&expected) { self.advance(); Ok(()) } else { Err(anyhow!("{}", message)) } } fn skip_whitespace(&mut self) { while let Some(token) = self.peek() { match token.kind { TokenKind::Newline => { self.advance(); } _ => break, } } } fn unexpected_token_error_with_token(&self, expected: String, token: &Token) -> ParseError { ParseError::UnexpectedToken { line: 1, // TODO: Calculate line from range column: token.range.start().into(), expected, found: token.text.clone(), } } }