From 113a683bebbaec3f7279b6a0ee2e766716f106c3 Mon Sep 17 00:00:00 2001 From: Giulio Agostini Date: Fri, 3 Oct 2025 19:40:25 +0200 Subject: [PATCH] Add while loop support and token pretty printing The commit adds while loop statements and improves token display formatting: - Implements while loop syntax and evaluation - Adds a new token pretty printing module with configurable formatting - Improves token display with colors and different output modes --- example.lox | 4 + src/backend/interpreter.rs | 11 + src/frontend/ast.rs | 28 +++ src/frontend/lexer.rs | 3 +- src/frontend/parser.rs | 14 ++ src/logging/display_ast.rs | 27 ++- src/logging/display_token.rs | 383 +++++++++++++++++++++++++++++++++++ src/logging/mod.rs | 1 + 8 files changed, 463 insertions(+), 8 deletions(-) diff --git a/example.lox b/example.lox index 0d3486a..244e8f2 100644 --- a/example.lox +++ b/example.lox @@ -14,6 +14,10 @@ do end do print "cavallo"; + while cavallo < 10 do + print "loop: " + cavallo; + cavallo = cavallo + 1; + end 89 end 99 diff --git a/src/backend/interpreter.rs b/src/backend/interpreter.rs index 4423a01..779760f 100644 --- a/src/backend/interpreter.rs +++ b/src/backend/interpreter.rs @@ -320,6 +320,17 @@ impl<'a> EvaluateInterpreter for Interpreter<'a> { }), } } + Stmt::While { condition, body } => { + while self.interpret(*condition.clone())?.is_truthy() { + self.interpret(*body.clone())?; + } + Ok(LiteralValue::Nil) + } + Stmt::For { + variable, + iterable, + body, + } => todo!(), } } } diff --git a/src/frontend/ast.rs b/src/frontend/ast.rs index cc4e125..1ac797b 100644 --- a/src/frontend/ast.rs +++ b/src/frontend/ast.rs @@ -19,6 +19,7 @@ use std::fmt::{Debug, Display}; * assignment_statement -> IDENTIFIER "=" expression ";" * block_statement -> "do" statement* "end" * if_statement -> "if" expression "then" statement ("elif" expression "then" statement)* ("else" statement)? "end" + * while_statement -> "while" expression "do" statement "end" * * expression -> assignment * assignment -> IDENTIFIER "=" assignment | logical_or @@ -145,6 +146,15 @@ pub enum Stmt { elif_branch: Vec<(Box, Box)>, else_branch: Option>, }, + While { + condition: Box, + body: Box, + }, + For { + variable: Box, + iterable: Box, + body: Box, + }, } impl Display for Stmt { @@ -182,6 +192,12 @@ impl Display for Stmt { .collect::>() .join("\n") ), + Stmt::While { condition, body } => todo!(), + Stmt::For { + variable, + iterable, + body, + } => todo!(), } } } @@ -221,6 +237,12 @@ impl Debug for Stmt { .collect::>() .join("\t\t\n") ), + Stmt::While { condition, body } => todo!(), + Stmt::For { + variable, + iterable, + body, + } => todo!(), } } } @@ -267,6 +289,12 @@ impl AstNodeKind for Stmt { } => "if statement", Stmt::Print { expression: _ } => "print statement", Stmt::Stmt { expression: _ } => "statement", + Stmt::While { condition, body } => todo!(), + Stmt::For { + variable, + iterable, + body, + } => todo!(), } } } diff --git a/src/frontend/lexer.rs b/src/frontend/lexer.rs index a1fd71e..45901af 100644 --- a/src/frontend/lexer.rs +++ b/src/frontend/lexer.rs @@ -1,5 +1,6 @@ use crate::frontend::source_registry::{SourceId, SourcePosition, SourceSlice}; use crate::frontend::tokens::{LiteralValue, Token, TokenType}; +use crate::logging::display_token::TokenPrettyPrintExt; use crate::result::{LoxError, LoxResult}; pub struct Lexer { @@ -74,7 +75,7 @@ impl Lexer { } tokens.push(self.make_token(TokenType::Eof)); println!("Tokens:"); - tokens.iter().for_each(|val| println!("{}", val)); + tokens.iter().for_each(|val| println!("{}", val.pretty())); Ok(tokens) } diff --git a/src/frontend/parser.rs b/src/frontend/parser.rs index aa099b9..611ed83 100644 --- a/src/frontend/parser.rs +++ b/src/frontend/parser.rs @@ -81,6 +81,10 @@ impl Parser { self.if_statement()?, self.peek().source_slice.clone(), )), + (TokenType::While, _) => Ok(AstNode::new( + self.while_statement()?, + self.peek().source_slice.clone(), + )), _ => Ok(AstNode::new( self.expression_statement()?, self.peek().source_slice.clone(), @@ -88,6 +92,16 @@ impl Parser { } } + fn while_statement(&mut self) -> LoxResult { + self.advance(); + let condition = self.expression()?; + let body = self.block_statement()?; + Ok(Stmt::While { + condition: Box::new(condition), + body: Box::new(body), + }) + } + fn if_statement(&mut self) -> LoxResult { self.advance(); let condition = self.expression()?; diff --git a/src/logging/display_ast.rs b/src/logging/display_ast.rs index 0c0e35b..d300d54 100644 --- a/src/logging/display_ast.rs +++ b/src/logging/display_ast.rs @@ -265,7 +265,6 @@ impl PrettyPrint for Stmt { write!(f, "{}}}", indent) } } - Stmt::Print { expression } => { if ctx.config.compact { let expr_str = @@ -279,7 +278,6 @@ impl PrettyPrint for Stmt { write!(f, "{}}}", indent) } } - Stmt::Var { name, initializer } => { if ctx.config.compact { match initializer { @@ -306,7 +304,6 @@ impl PrettyPrint for Stmt { write!(f, "{}}}", indent) } } - Stmt::Assign { name, value } => { if ctx.config.compact { let value_str = @@ -321,7 +318,6 @@ impl PrettyPrint for Stmt { write!(f, "{}}}", indent) } } - Stmt::Return { expression } => { if ctx.config.compact { let expr_str = @@ -335,7 +331,6 @@ impl PrettyPrint for Stmt { write!(f, "{}}}", indent) } } - Stmt::Block { statements } => { if ctx.config.compact { write!(f, "{{ ... ({} statements) }}", statements.len()) @@ -355,7 +350,6 @@ impl PrettyPrint for Stmt { write!(f, "{}}}", indent) } } - Stmt::If { condition, then_branch, @@ -410,8 +404,27 @@ impl PrettyPrint for Stmt { write!(f, "{}}}", indent) } } - Stmt::Stmt { expression } => expression.pretty_print(ctx, f), + Stmt::While { condition, body } => { + write!(f, "{}while (", ctx.child_context(true).indent())?; + condition.pretty_print(&ctx.child_context(true), f)?; + writeln!(f, ") {{")?; + body.pretty_print(&ctx.child_context(true), f)?; + writeln!(f, "{}}}", ctx.child_context(true).indent()) + } + Stmt::For { + variable, + iterable, + body, + } => { + write!(f, "{}for (", ctx.child_context(true).indent())?; + variable.pretty_print(&ctx.child_context(true), f)?; + write!(f, " in ")?; + iterable.pretty_print(&ctx.child_context(true), f)?; + writeln!(f, ") {{")?; + body.pretty_print(&ctx.child_context(true), f)?; + writeln!(f, "{}}}", ctx.child_context(true).indent()) + } } } } diff --git a/src/logging/display_token.rs b/src/logging/display_token.rs index e69de29..87fc1be 100644 --- a/src/logging/display_token.rs +++ b/src/logging/display_token.rs @@ -0,0 +1,383 @@ +//! Pretty printing module for Token objects +//! +//! This module provides elegant and functional printing capabilities for Token objects, +//! inspired by Rust's debug formatting but optimized for readability and analysis. + +use crate::frontend::{ + source_registry::SourceSlice, + tokens::{LiteralValue, Token, TokenType}, +}; +use std::fmt; + +/// Configuration for pretty printing tokens +#[derive(Clone, Debug)] +pub struct TokenPrettyConfig { + /// Show source position information + pub show_positions: bool, + /// Use compact single-line format + pub compact: bool, + /// Show literal values in detail + pub show_literals: bool, + /// Use colored output (for terminals) + pub colored: bool, + /// Show token indices in sequences + pub show_indices: bool, +} + +impl Default for TokenPrettyConfig { + fn default() -> Self { + Self { + show_positions: true, + compact: false, + show_literals: true, + colored: false, + show_indices: false, + } + } +} + +impl TokenPrettyConfig { + /// Create a compact configuration + pub fn compact() -> Self { + Self { + compact: true, + show_positions: false, + show_indices: false, + ..Default::default() + } + } + + /// Create a minimal configuration (just type and lexeme) + pub fn minimal() -> Self { + Self { + compact: true, + show_positions: false, + show_literals: false, + show_indices: false, + colored: false, + } + } + + /// Create a debug configuration with all details + pub fn debug() -> Self { + Self { + show_positions: true, + show_literals: true, + compact: false, + show_indices: true, + colored: false, + } + } + + /// Create a colored configuration for terminal output + pub fn colored() -> Self { + Self { + colored: true, + ..Default::default() + } + } +} + +/// Trait for pretty printing tokens +pub trait TokenPrettyPrint { + fn pretty_print_token(&self, config: &TokenPrettyConfig) -> String; +} + +impl TokenPrettyPrint for Token { + fn pretty_print_token(&self, config: &TokenPrettyConfig) -> String { + if config.compact { + self.format_compact(config) + } else { + self.format_full(config) + } + } +} + +impl Token { + fn format_compact(&self, config: &TokenPrettyConfig) -> String { + if config.show_literals { + match &self.literal { + Some(literal) => { + if config.colored { + format!("{}[{}]", self.colored_type(), literal) + } else { + format!("{}[{}]", self.token_type_symbol(), literal) + } + } + None => { + if config.colored { + format!("{}", self.colored_type()) + } else { + format!("{}", self.token_type_symbol()) + } + } + } + } else { + if config.colored { + format!("{}", self.colored_type()) + } else { + format!("{}", self.token_type_symbol()) + } + } + } + + fn format_full(&self, config: &TokenPrettyConfig) -> String { + let mut parts = Vec::new(); + + // Token type + if config.colored { + parts.push(format!("type: {}", self.colored_type())); + } else { + parts.push(format!("type: {:?}", self.token_type)); + } + + // Lexeme + parts.push(format!("lexeme: {:?}", self.lexeme)); + + // Literal value + if config.show_literals { + match &self.literal { + Some(literal) => parts.push(format!("literal: {:?}", literal)), + None => parts.push("literal: None".to_string()), + } + } + + // Position information + if config.show_positions { + parts.push(format!("pos: {:?}", self.source_slice)); + } + + format!("Token {{ {} }}", parts.join(", ")) + } + + fn token_type_symbol(&self) -> &'static str { + match self.token_type { + TokenType::LeftParen => "(", + TokenType::RightParen => ")", + TokenType::LeftBrace => "{", + TokenType::RightBrace => "}", + TokenType::LeftBracket => "[", + TokenType::RightBracket => "]", + TokenType::Comma => ",", + TokenType::Dot => ".", + TokenType::Minus => "-", + TokenType::Plus => "+", + TokenType::Semicolon => ";", + TokenType::Slash => "/", + TokenType::Star => "*", + TokenType::Percent => "%", + TokenType::Bang => "!", + TokenType::BangEqual => "!=", + TokenType::Equal => "=", + TokenType::EqualEqual => "==", + TokenType::Greater => ">", + TokenType::GreaterEqual => ">=", + TokenType::Less => "<", + TokenType::LessEqual => "<=", + TokenType::Identifier => "ID", + TokenType::String => "STR", + TokenType::Number => "NUM", + TokenType::And => "AND", + TokenType::Class => "CLASS", + TokenType::StartBlock => "DO", + TokenType::EndBlock => "END", + TokenType::False => "FALSE", + TokenType::True => "TRUE", + TokenType::Fun => "FUN", + TokenType::For => "FOR", + TokenType::While => "WHILE", + TokenType::If => "IF", + TokenType::Elif => "ELIF", + TokenType::Else => "ELSE", + TokenType::Nil => "NIL", + TokenType::Or => "OR", + TokenType::Print => "PRINT", + TokenType::Return => "RETURN", + TokenType::Super => "SUPER", + TokenType::This => "THIS", + TokenType::Var => "VAR", + TokenType::Val => "VAL", + TokenType::Eof => "EOF", + } + } + + fn colored_type(&self) -> String { + let (color_code, symbol) = match self.token_type { + // Operators - Red + TokenType::Plus + | TokenType::Minus + | TokenType::Star + | TokenType::Slash + | TokenType::Percent + | TokenType::Bang + | TokenType::BangEqual + | TokenType::Equal + | TokenType::EqualEqual + | TokenType::Greater + | TokenType::GreaterEqual + | TokenType::Less + | TokenType::LessEqual => ("\x1b[31m", self.token_type_symbol()), + + // Keywords - Blue + TokenType::And + | TokenType::Class + | TokenType::False + | TokenType::True + | TokenType::Fun + | TokenType::For + | TokenType::While + | TokenType::If + | TokenType::Elif + | TokenType::Else + | TokenType::Nil + | TokenType::Or + | TokenType::Print + | TokenType::Return + | TokenType::Super + | TokenType::This + | TokenType::Var + | TokenType::Val => ("\x1b[34m", self.token_type_symbol()), + + // Literals - Green + TokenType::Identifier | TokenType::String | TokenType::Number => { + ("\x1b[32m", self.token_type_symbol()) + } + + // Punctuation - Gray + TokenType::LeftParen + | TokenType::RightParen + | TokenType::LeftBrace + | TokenType::RightBrace + | TokenType::LeftBracket + | TokenType::RightBracket + | TokenType::Comma + | TokenType::Dot + | TokenType::Semicolon => ("\x1b[37m", self.token_type_symbol()), + + // Block delimiters - Magenta + TokenType::StartBlock | TokenType::EndBlock => ("\x1b[35m", self.token_type_symbol()), + + // EOF - Yellow + TokenType::Eof => ("\x1b[33m", self.token_type_symbol()), + }; + + format!("{}{}\x1b[0m", color_code, symbol) + } +} + +/// Pretty print a sequence of tokens +pub fn pretty_print_tokens(tokens: &[Token], config: &TokenPrettyConfig) -> String { + let mut result = String::new(); + + if config.compact { + let token_strs: Vec = tokens + .iter() + .enumerate() + .map(|(i, token)| { + if config.show_indices { + format!("{}:{}", i, token.pretty_print_token(config)) + } else { + token.pretty_print_token(config) + } + }) + .collect(); + result.push_str(&token_strs.join(" ")); + } else { + for (i, token) in tokens.iter().enumerate() { + if config.show_indices { + result.push_str(&format!("[{}] ", i)); + } + result.push_str(&token.pretty_print_token(config)); + result.push('\n'); + } + } + + result +} + +/// Extension trait for convenience methods +pub trait TokenPrettyPrintExt { + fn pretty(&self) -> String; + fn pretty_compact(&self) -> String; + fn pretty_minimal(&self) -> String; + fn pretty_colored(&self) -> String; + fn pretty_debug(&self) -> String; + fn pretty_with_config(&self, config: &TokenPrettyConfig) -> String; +} + +impl TokenPrettyPrintExt for Token { + fn pretty(&self) -> String { + self.pretty_print_token(&TokenPrettyConfig::default()) + } + + fn pretty_compact(&self) -> String { + self.pretty_print_token(&TokenPrettyConfig::compact()) + } + + fn pretty_minimal(&self) -> String { + self.pretty_print_token(&TokenPrettyConfig::minimal()) + } + + fn pretty_colored(&self) -> String { + self.pretty_print_token(&TokenPrettyConfig::colored()) + } + + fn pretty_debug(&self) -> String { + self.pretty_print_token(&TokenPrettyConfig::debug()) + } + + fn pretty_with_config(&self, config: &TokenPrettyConfig) -> String { + self.pretty_print_token(config) + } +} + +impl TokenPrettyPrintExt for Vec { + fn pretty(&self) -> String { + pretty_print_tokens(self, &TokenPrettyConfig::default()) + } + + fn pretty_compact(&self) -> String { + pretty_print_tokens(self, &TokenPrettyConfig::compact()) + } + + fn pretty_minimal(&self) -> String { + pretty_print_tokens(self, &TokenPrettyConfig::minimal()) + } + + fn pretty_colored(&self) -> String { + pretty_print_tokens(self, &TokenPrettyConfig::colored()) + } + + fn pretty_debug(&self) -> String { + pretty_print_tokens(self, &TokenPrettyConfig::debug()) + } + + fn pretty_with_config(&self, config: &TokenPrettyConfig) -> String { + pretty_print_tokens(self, config) + } +} + +/// Convenience functions for quick pretty printing +pub fn pretty_token(token: &Token) -> String { + token.pretty() +} + +pub fn pretty_token_compact(token: &Token) -> String { + token.pretty_compact() +} + +pub fn pretty_token_minimal(token: &Token) -> String { + token.pretty_minimal() +} + +pub fn pretty_tokens(tokens: &[Token]) -> String { + pretty_print_tokens(tokens, &TokenPrettyConfig::default()) +} + +pub fn pretty_tokens_compact(tokens: &[Token]) -> String { + pretty_print_tokens(tokens, &TokenPrettyConfig::compact()) +} + +pub fn pretty_tokens_colored(tokens: &[Token]) -> String { + pretty_print_tokens(tokens, &TokenPrettyConfig::colored()) +} diff --git a/src/logging/mod.rs b/src/logging/mod.rs index 2666c51..b2914e2 100644 --- a/src/logging/mod.rs +++ b/src/logging/mod.rs @@ -1 +1,2 @@ pub mod display_ast; +pub mod display_token;