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:
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod display_ast;
|
||||
Reference in New Issue
Block a user