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:
@@ -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