Lines
55.68 %
Functions
20 %
Branches
100 %
//! Debug / display formatting for `Expr` and `RuntimeValue`.
//!
//! `format_expr` is the main entry. The `RuntimeValue` and bytes
//! literals route through internal helpers; `format_lambda_params`
//! renders parameter lists in the printed-lambda surface syntax.
use crate::ast::{Expr, LambdaParams};
use crate::runtime::Value;
pub(crate) fn format_expr(expr: &Expr) -> String {
match expr {
Expr::Nil => "NIL".to_string(),
Expr::Bool(true) => "#T".to_string(),
Expr::Bool(false) => "NIL".to_string(),
Expr::Number(n) if *n.denom() == 1 => n.numer().to_string(),
Expr::Number(n) => format!("{}/{}", n.numer(), n.denom()),
Expr::String(s) => s.clone(),
Expr::Symbol(s) => s.clone(),
Expr::Keyword(s) => format!(":{s}"),
Expr::Bytes(b) => format_bytes_literal(b),
Expr::Cons(car, cdr) => format!("({} . {})", format_expr(car), format_expr(cdr)),
Expr::List(elems) => {
let inner: Vec<_> = elems.iter().map(format_expr).collect();
format!("({})", inner.join(" "))
}
Expr::Quote(e) => format!("'{}", format_expr(e)),
Expr::Quasiquote(e) => format!("`{}", format_expr(e)),
Expr::Unquote(e) => format!(",{}", format_expr(e)),
Expr::UnquoteSplicing(e) => format!(",@{}", format_expr(e)),
Expr::Lambda(params, body) => {
format!(
"(LAMBDA ({}) {})",
format_lambda_params(params),
format_expr(body)
)
Expr::RuntimeValue(val) => format_runtime_value(val),
Expr::WasmRuntime(ty) => format!("#<wasm:{ty}>"),
Expr::WasmLocal(idx, ty) => format!("#<local:{idx}:{ty}>"),
pub(super) fn format_runtime_value(val: &Value) -> String {
match val {
Value::Nil => "NIL".to_string(),
Value::Bool(true) => "#T".to_string(),
Value::Bool(false) => "NIL".to_string(),
Value::Number(n) if *n.denom() == 1 => n.numer().to_string(),
Value::Number(n) => format!("{}/{}", n.numer(), n.denom()),
Value::String(s) => format!("\"{s}\""),
Value::Symbol(s) => s.clone(),
Value::Bytes(b) => format_bytes_literal(b),
Value::Pair(_) | Value::Vector(_) | Value::Closure(_) => {
format!("#<{}>", val.type_name())
Value::Struct { name, fields } => {
let field_strs: Vec<_> = fields.iter().map(format_runtime_value).collect();
format!("#S({name} {})", field_strs.join(" "))
Value::Commodity {
amount,
commodity_id,
} => {
let amt = if *amount.denom() == 1 {
amount.numer().to_string()
} else {
format!("{}/{}", amount.numer(), amount.denom())
};
format!("(:commodity {amt} :id \"{commodity_id}\")")
fn format_bytes_literal(bytes: &[u8]) -> String {
let parts: Vec<String> = bytes.iter().map(u8::to_string).collect();
format!("#u8({})", parts.join(" "))
fn format_lambda_params(params: &LambdaParams) -> String {
let mut parts = Vec::new();
// Required parameters
parts.extend(params.required.iter().cloned());
// Optional parameters
if !params.optional.is_empty() {
parts.push("&optional".to_string());
for (name, default) in ¶ms.optional {
if let Some(default_expr) = default {
parts.push(format!("({} {})", name, format_expr(default_expr)));
parts.push(name.clone());
// Rest parameter
if let Some(rest) = ¶ms.rest {
parts.push("&rest".to_string());
parts.push(rest.clone());
// Key parameters
if !params.key.is_empty() {
parts.push("&key".to_string());
for (name, default) in ¶ms.key {
// Aux parameters
if !params.aux.is_empty() {
parts.push("&aux".to_string());
for (name, init) in ¶ms.aux {
if let Some(init_expr) = init {
parts.push(format!("({} {})", name, format_expr(init_expr)));
parts.join(" ")