Initialize Rust implementation of Lox interpreter

This initial commit sets up the basic structure for a Rust
implementation of the Lox interpreter, including:

- Lexer for tokenizing source code - Parser for building AST - Basic
interpreter functionality - Environment for variable storage

The core components include source tracking, error handling, and basic
expression evaluation.
This commit is contained in:
Giulio Agostini
2025-10-03 19:07:12 +02:00
commit a7df45dc72
18 changed files with 2710 additions and 0 deletions
+581
View File
@@ -0,0 +1,581 @@
//! Pretty printing module for AST nodes
//!
//! This module provides elegant and functional printing capabilities for AST objects,
//! inspired by Rust's debug formatting but optimized for readability and analysis.
use crate::frontend::{
ast::{AstNode, AstNodeKind, Expr, Stmt},
source_registry::SourceSlice,
};
use std::fmt::{self, Write};
/// Configuration for pretty printing output
#[derive(Clone, Debug)]
pub struct PrettyConfig {
/// Indentation string (default: " ")
pub indent: String,
/// Maximum depth before truncation (None = unlimited)
pub max_depth: Option<usize>,
/// Show source position information
pub show_positions: bool,
/// Use compact single-line format when possible
pub compact: bool,
/// Include type information
pub show_types: bool,
/// Use tree-style connectors
pub tree_style: bool,
}
impl Default for PrettyConfig {
fn default() -> Self {
Self {
indent: " ".to_string(),
max_depth: None,
show_positions: true,
compact: false,
show_types: true,
tree_style: false,
}
}
}
impl PrettyConfig {
/// Create a compact configuration
pub fn compact() -> Self {
Self {
compact: true,
show_positions: false,
..Default::default()
}
}
/// Create a tree-style configuration
pub fn tree() -> Self {
Self {
tree_style: true,
indent: "".to_string(),
..Default::default()
}
}
/// Create a debug configuration with all details
pub fn debug() -> Self {
Self {
show_positions: true,
show_types: true,
compact: false,
max_depth: None,
..Default::default()
}
}
}
/// Context for pretty printing, tracks depth and styling
pub struct PrettyContext {
config: PrettyConfig,
depth: usize,
is_last: Vec<bool>, // For tree-style formatting
}
impl PrettyContext {
pub fn new(config: PrettyConfig) -> Self {
Self {
config,
depth: 0,
is_last: Vec::new(),
}
}
fn indent(&self) -> String {
if self.config.tree_style {
self.tree_indent()
} else {
self.config.indent.repeat(self.depth)
}
}
fn tree_indent(&self) -> String {
let mut result = String::new();
for (i, &is_last) in self.is_last.iter().enumerate() {
if i == self.is_last.len() - 1 {
result.push_str(if is_last { "└─ " } else { "├─ " });
} else {
result.push_str(if is_last { " " } else { "" });
}
}
result
}
fn child_context(&self, is_last: bool) -> Self {
let mut new_is_last = self.is_last.clone();
new_is_last.push(is_last);
Self {
config: self.config.clone(),
depth: self.depth + 1,
is_last: new_is_last,
}
}
fn should_truncate(&self) -> bool {
self.config.max_depth.map_or(false, |max| self.depth >= max)
}
}
/// Trait for pretty printing AST nodes
pub trait PrettyPrint {
fn pretty_print(&self, ctx: &PrettyContext, f: &mut fmt::Formatter) -> fmt::Result;
}
/// Convenience functions to get pretty printed strings
pub fn pretty_print_with_config<T: PrettyPrint>(item: &T, config: &PrettyConfig) -> String {
let mut output = String::new();
let ctx = PrettyContext::new(config.clone());
write!(&mut output, "{}", PrettyWrapper { item, ctx: &ctx }).unwrap();
output
}
pub fn pretty_print<T: PrettyPrint>(item: &T) -> String {
pretty_print_with_config(item, &PrettyConfig::default())
}
/// Wrapper for pretty printing with Display trait
struct PrettyWrapper<'a, T: PrettyPrint> {
item: &'a T,
ctx: &'a PrettyContext,
}
impl<'a, T: PrettyPrint> fmt::Display for PrettyWrapper<'a, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.item.pretty_print(self.ctx, f)
}
}
impl PrettyPrint for Expr {
fn pretty_print(&self, ctx: &PrettyContext, f: &mut fmt::Formatter) -> fmt::Result {
if ctx.should_truncate() {
return write!(f, "{}...", ctx.indent());
}
let indent = ctx.indent();
match self {
Expr::Literal { value } => {
if ctx.config.compact {
write!(f, "{:?}", value)
} else {
write!(f, "{}Literal {{ value: {:?} }}", indent, value)
}
}
Expr::Binary {
left,
operator,
right,
} => {
if ctx.config.compact {
// For compact, we need to format recursively
let left_str =
pretty_print_with_config(left.as_ref(), &PrettyConfig::compact());
let right_str =
pretty_print_with_config(right.as_ref(), &PrettyConfig::compact());
write!(f, "({} {:?} {})", left_str, operator, right_str)
} else {
writeln!(f, "{}Binary {{", indent)?;
writeln!(
f,
"{}operator: {:?},",
ctx.child_context(false).indent(),
operator
)?;
write!(f, "{}left: ", ctx.child_context(false).indent())?;
left.pretty_print(&ctx.child_context(false), f)?;
writeln!(f)?;
write!(f, "{}right: ", ctx.child_context(true).indent())?;
right.pretty_print(&ctx.child_context(true), f)?;
writeln!(f)?;
write!(f, "{}}}", indent)
}
}
Expr::Unary { operator, operand } => {
if ctx.config.compact {
let operand_str =
pretty_print_with_config(operand.as_ref(), &PrettyConfig::compact());
write!(f, "({:?} {})", operator, operand_str)
} else {
writeln!(f, "{}Unary {{", indent)?;
writeln!(
f,
"{}operator: {:?},",
ctx.child_context(false).indent(),
operator
)?;
write!(f, "{}operand: ", ctx.child_context(true).indent())?;
operand.pretty_print(&ctx.child_context(true), f)?;
writeln!(f)?;
write!(f, "{}}}", indent)
}
}
Expr::Grouping { expression } => {
if ctx.config.compact {
let expr_str =
pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact());
write!(f, "({})", expr_str)
} else {
writeln!(f, "{}Grouping {{", indent)?;
write!(f, "{}expression: ", ctx.child_context(true).indent())?;
expression.pretty_print(&ctx.child_context(true), f)?;
writeln!(f)?;
write!(f, "{}}}", indent)
}
}
Expr::Variable { name } => {
if ctx.config.compact {
write!(f, "{}", name)
} else {
write!(f, "{}Variable {{ name: {:?} }}", indent, name)
}
}
}
}
}
impl PrettyPrint for Stmt {
fn pretty_print(&self, ctx: &PrettyContext, f: &mut fmt::Formatter) -> fmt::Result {
if ctx.should_truncate() {
return write!(f, "{}...", ctx.indent());
}
let indent = ctx.indent();
match self {
Stmt::Expression { expression } => {
if ctx.config.compact {
let expr_str =
pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact());
write!(f, "{};", expr_str)
} else {
writeln!(f, "{}Expression {{", indent)?;
write!(f, "{}expression: ", ctx.child_context(true).indent())?;
expression.pretty_print(&ctx.child_context(true), f)?;
writeln!(f)?;
write!(f, "{}}}", indent)
}
}
Stmt::Print { expression } => {
if ctx.config.compact {
let expr_str =
pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact());
write!(f, "print {};", expr_str)
} else {
writeln!(f, "{}Print {{", indent)?;
write!(f, "{}expression: ", ctx.child_context(true).indent())?;
expression.pretty_print(&ctx.child_context(true), f)?;
writeln!(f)?;
write!(f, "{}}}", indent)
}
}
Stmt::Var { name, initializer } => {
if ctx.config.compact {
match initializer {
Some(init) => {
let init_str =
pretty_print_with_config(init.as_ref(), &PrettyConfig::compact());
write!(f, "var {} = {};", name, init_str)
}
None => write!(f, "var {};", name),
}
} else {
writeln!(f, "{}Var {{", indent)?;
writeln!(f, "{}name: {:?},", ctx.child_context(false).indent(), name)?;
match initializer {
Some(init) => {
write!(f, "{}initializer: Some(", ctx.child_context(true).indent())?;
init.pretty_print(&ctx.child_context(true), f)?;
writeln!(f, "),")?;
}
None => {
writeln!(f, "{}initializer: None,", ctx.child_context(true).indent())?
}
}
write!(f, "{}}}", indent)
}
}
Stmt::Assign { name, value } => {
if ctx.config.compact {
let value_str =
pretty_print_with_config(value.as_ref(), &PrettyConfig::compact());
write!(f, "{} = {};", name, value_str)
} else {
writeln!(f, "{}Assign {{", indent)?;
writeln!(f, "{}name: {:?},", ctx.child_context(false).indent(), name)?;
write!(f, "{}value: ", ctx.child_context(true).indent())?;
value.pretty_print(&ctx.child_context(true), f)?;
writeln!(f)?;
write!(f, "{}}}", indent)
}
}
Stmt::Return { expression } => {
if ctx.config.compact {
let expr_str =
pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact());
write!(f, "return {};", expr_str)
} else {
writeln!(f, "{}Return {{", indent)?;
write!(f, "{}expression: ", ctx.child_context(true).indent())?;
expression.pretty_print(&ctx.child_context(true), f)?;
writeln!(f)?;
write!(f, "{}}}", indent)
}
}
Stmt::Block { statements } => {
if ctx.config.compact {
write!(f, "{{ ... ({} statements) }}", statements.len())
} else {
writeln!(f, "{}Block {{", indent)?;
writeln!(f, "{}statements: [", ctx.child_context(false).indent())?;
for (i, stmt) in statements.iter().enumerate() {
let is_last = i == statements.len() - 1;
stmt.pretty_print(&ctx.child_context(false).child_context(is_last), f)?;
if !is_last {
writeln!(f, ",")?;
} else {
writeln!(f)?;
}
}
writeln!(f, "{}]", ctx.child_context(false).indent())?;
write!(f, "{}}}", indent)
}
}
Stmt::If {
condition,
then_branch,
elif_branch,
else_branch,
} => {
if ctx.config.compact {
let cond_str =
pretty_print_with_config(condition.as_ref(), &PrettyConfig::compact());
write!(f, "if {} {{ ... }}", cond_str)
} else {
writeln!(f, "{}If {{", indent)?;
write!(f, "{}condition: ", ctx.child_context(false).indent())?;
condition.pretty_print(&ctx.child_context(false), f)?;
writeln!(f, ",")?;
write!(f, "{}then_branch: ", ctx.child_context(false).indent())?;
then_branch.pretty_print(&ctx.child_context(false), f)?;
writeln!(f, ",")?;
if !elif_branch.is_empty() {
writeln!(f, "{}elif_branches: [", ctx.child_context(false).indent())?;
for (i, (cond, branch)) in elif_branch.iter().enumerate() {
let is_last = i == elif_branch.len() - 1;
let child_ctx = ctx.child_context(false).child_context(is_last);
writeln!(f, "{}(", child_ctx.indent())?;
write!(f, "{}condition: ", child_ctx.child_context(false).indent())?;
cond.pretty_print(&child_ctx.child_context(false), f)?;
writeln!(f, ",")?;
write!(f, "{}branch: ", child_ctx.child_context(true).indent())?;
branch.pretty_print(&child_ctx.child_context(true), f)?;
writeln!(f)?;
write!(f, "{})", child_ctx.indent())?;
if !is_last {
writeln!(f, ",")?;
} else {
writeln!(f)?;
}
}
writeln!(f, "{}],", ctx.child_context(false).indent())?;
}
match else_branch {
Some(else_stmt) => {
write!(f, "{}else_branch: Some(", ctx.child_context(true).indent())?;
else_stmt.pretty_print(&ctx.child_context(true), f)?;
writeln!(f, "),")?;
}
None => {
writeln!(f, "{}else_branch: None,", ctx.child_context(true).indent())?
}
}
write!(f, "{}}}", indent)
}
}
Stmt::Stmt { expression } => expression.pretty_print(ctx, f),
}
}
}
impl<T: AstNodeKind + fmt::Debug + fmt::Display + PrettyPrint> PrettyPrint for AstNode<T> {
fn pretty_print(&self, ctx: &PrettyContext, f: &mut fmt::Formatter) -> fmt::Result {
let indent = ctx.indent();
if ctx.config.compact {
self.node.pretty_print(ctx, f)
} else {
writeln!(f, "{}AstNode {{", indent)?;
if ctx.config.show_types {
writeln!(
f,
"{}kind: {:?},",
ctx.child_context(false).indent(),
self.node.kind()
)?;
}
write!(
f,
"{}node: ",
ctx.child_context(!ctx.config.show_positions).indent()
)?;
self.node
.pretty_print(&ctx.child_context(!ctx.config.show_positions), f)?;
writeln!(f, ",")?;
if ctx.config.show_positions {
writeln!(
f,
"{}source_slice: {:?},",
ctx.child_context(true).indent(),
self.source_slice
)?;
}
write!(f, "{}}}", indent)
}
}
}
// Extension trait for convenience methods
pub trait PrettyPrintExt {
fn pretty(&self) -> String;
fn pretty_compact(&self) -> String;
fn pretty_tree(&self) -> String;
fn pretty_with_config(&self, config: &PrettyConfig) -> String;
}
impl PrettyPrintExt for Expr {
fn pretty(&self) -> String {
pretty_print(self)
}
fn pretty_compact(&self) -> String {
pretty_print_with_config(self, &PrettyConfig::compact())
}
fn pretty_tree(&self) -> String {
pretty_print_with_config(self, &PrettyConfig::tree())
}
fn pretty_with_config(&self, config: &PrettyConfig) -> String {
pretty_print_with_config(self, config)
}
}
impl PrettyPrintExt for Stmt {
fn pretty(&self) -> String {
pretty_print(self)
}
fn pretty_compact(&self) -> String {
pretty_print_with_config(self, &PrettyConfig::compact())
}
fn pretty_tree(&self) -> String {
pretty_print_with_config(self, &PrettyConfig::tree())
}
fn pretty_with_config(&self, config: &PrettyConfig) -> String {
pretty_print_with_config(self, config)
}
}
impl<T: AstNodeKind + fmt::Debug + fmt::Display + PrettyPrint> PrettyPrintExt for AstNode<T> {
fn pretty(&self) -> String {
pretty_print(self)
}
fn pretty_compact(&self) -> String {
pretty_print_with_config(self, &PrettyConfig::compact())
}
fn pretty_tree(&self) -> String {
pretty_print_with_config(self, &PrettyConfig::tree())
}
fn pretty_with_config(&self, config: &PrettyConfig) -> String {
pretty_print_with_config(self, config)
}
}
/// Convenience functions for quick pretty printing
pub fn pretty_expr(expr: &Expr) -> String {
pretty_print(expr)
}
pub fn pretty_stmt(stmt: &Stmt) -> String {
pretty_print(stmt)
}
pub fn pretty_expr_compact(expr: &Expr) -> String {
pretty_print_with_config(expr, &PrettyConfig::compact())
}
pub fn pretty_stmt_compact(stmt: &Stmt) -> String {
pretty_print_with_config(stmt, &PrettyConfig::compact())
}
pub fn pretty_tree<T: PrettyPrint>(item: &T) -> String {
pretty_print_with_config(item, &PrettyConfig::tree())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frontend::{
source_registry::{SourceId, SourcePosition, SourceSlice},
tokens::{LiteralValue, TokenType},
};
#[test]
fn test_pretty_expr_literal() {
let expr = Expr::Literal {
value: LiteralValue::Number(42.0),
};
let compact = expr.pretty_compact();
assert!(compact.contains("42"));
let full = expr.pretty();
assert!(full.contains("Literal"));
assert!(full.contains("42"));
}
#[test]
fn test_pretty_expr_binary() {
let expr = Expr::Binary {
left: Box::new(Expr::Literal {
value: LiteralValue::Number(1.0),
}),
operator: TokenType::Plus,
right: Box::new(Expr::Literal {
value: LiteralValue::Number(2.0),
}),
};
let compact = expr.pretty_compact();
assert!(compact.contains("1"));
assert!(compact.contains("2"));
}
}
View File
+1
View File
@@ -0,0 +1 @@
pub mod display_ast;