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:
@@ -14,6 +14,10 @@ do
|
||||
end
|
||||
do
|
||||
print "cavallo";
|
||||
while cavallo < 10 do
|
||||
print "loop: " + cavallo;
|
||||
cavallo = cavallo + 1;
|
||||
end
|
||||
89
|
||||
end
|
||||
99
|
||||
|
||||
@@ -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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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()?;
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 +1,2 @@
|
||||
pub mod display_ast;
|
||||
pub mod display_token;
|
||||
|
||||
Reference in New Issue
Block a user