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
This commit is contained in:
Giulio Agostini
2025-10-03 19:40:25 +02:00
parent a7df45dc72
commit 113a683beb
8 changed files with 463 additions and 8 deletions
+11
View File
@@ -320,6 +320,17 @@ impl<'a> EvaluateInterpreter<Stmt> 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!(),
}
}
}
+28
View File
@@ -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<Expr>, Box<Stmt>)>,
else_branch: Option<Box<Stmt>>,
},
While {
condition: Box<Expr>,
body: Box<Stmt>,
},
For {
variable: Box<Expr>,
iterable: Box<Expr>,
body: Box<Stmt>,
},
}
impl Display for Stmt {
@@ -182,6 +192,12 @@ impl Display for Stmt {
.collect::<Vec<_>>()
.join("\n")
),
Stmt::While { condition, body } => todo!(),
Stmt::For {
variable,
iterable,
body,
} => todo!(),
}
}
}
@@ -221,6 +237,12 @@ impl Debug for Stmt {
.collect::<Vec<_>>()
.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!(),
}
}
}
+2 -1
View File
@@ -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)
}
+14
View File
@@ -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<Stmt> {
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<Stmt> {
self.advance();
let condition = self.expression()?;
+20 -7
View File
@@ -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())
}
}
}
}
+383
View File
@@ -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<String> = 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<Token> {
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())
}
+1
View File
@@ -1 +1,2 @@
pub mod display_ast;
pub mod display_token;