1
//! Debug / display formatting for `Expr` and `RuntimeValue`.
2
//!
3
//! `format_expr` is the main entry. The `RuntimeValue` and bytes
4
//! literals route through internal helpers; `format_lambda_params`
5
//! renders parameter lists in the printed-lambda surface syntax.
6

            
7
use crate::ast::{Expr, LambdaParams};
8
use crate::runtime::Value;
9

            
10
45024
pub(crate) fn format_expr(expr: &Expr) -> String {
11
19263
    match expr {
12
85
        Expr::Nil => "nil".to_string(),
13
332
        Expr::T => "t".to_string(),
14
19263
        Expr::Number(n) if *n.denom() == 1 => n.numer().to_string(),
15
83
        Expr::Number(n) => format!("{}/{}", n.numer(), n.denom()),
16
5978
        Expr::String(s) => s.clone(),
17
9145
        Expr::Symbol(s) => s.clone(),
18
84
        Expr::Keyword(s) => format!(":{s}"),
19
        Expr::Bytes(b) => format_bytes_literal(b),
20
919
        Expr::Cons(car, cdr) => format_cons(car, cdr),
21
6892
        Expr::List(elems) if elems.is_empty() => "nil".to_string(),
22
6891
        Expr::List(elems) => {
23
6891
            let inner: Vec<_> = elems.iter().map(format_expr).collect();
24
6891
            format!("({})", inner.join(" "))
25
        }
26
997
        Expr::Quote(e) => format!("'{}", format_expr(e)),
27
84
        Expr::Quasiquote(e) => format!("`{}", format_expr(e)),
28
        Expr::Unquote(e) => format!(",{}", format_expr(e)),
29
        Expr::UnquoteSplicing(e) => format!(",@{}", format_expr(e)),
30
415
        Expr::Lambda(params, body) => {
31
415
            format!(
32
                "(LAMBDA ({}) {})",
33
415
                format_lambda_params(params),
34
415
                format_expr(body)
35
            )
36
        }
37
        Expr::RuntimeValue(val) => format_runtime_value(val),
38
166
        Expr::WasmRuntime(ty) => format!("#<wasm:{ty}>"),
39
664
        Expr::WasmLocal(idx, ty) => format!("#<local:{idx}:{ty}>"),
40
    }
41
45024
}
42

            
43
/// Prints a cons chain as a list, keeping the dot only before a non-list tail.
44
919
fn format_cons(car: &Expr, cdr: &Expr) -> String {
45
919
    let mut parts = vec![format_expr(car)];
46
919
    let mut tail = cdr;
47
921
    while let Expr::Cons(next_car, next_cdr) = tail {
48
2
        parts.push(format_expr(next_car));
49
2
        tail = next_cdr;
50
2
    }
51
919
    match tail {
52
252
        Expr::Nil => {}
53
249
        Expr::List(items) => parts.extend(items.iter().map(format_expr)),
54
418
        other => return format!("({} . {})", parts.join(" "), format_expr(other)),
55
    }
56
501
    format!("({})", parts.join(" "))
57
919
}
58

            
59
831
pub(super) fn format_runtime_value(val: &Value) -> String {
60
332
    match val {
61
84
        Value::Nil => "nil".to_string(),
62
83
        Value::T => "t".to_string(),
63
332
        Value::Number(n) if *n.denom() == 1 => n.numer().to_string(),
64
        Value::Number(n) => format!("{}/{}", n.numer(), n.denom()),
65
        Value::String(s) => format!("\"{s}\""),
66
        Value::Symbol(s) => s.clone(),
67
        Value::Bytes(b) => format_bytes_literal(b),
68
        Value::Pair(_) | Value::Vector(_) | Value::Closure(_) => {
69
            format!("#<{}>", val.type_name())
70
        }
71
332
        Value::Struct { name, fields } => {
72
332
            let field_strs: Vec<_> = fields.iter().map(format_runtime_value).collect();
73
332
            format!("#S({name} {})", field_strs.join(" "))
74
        }
75
        Value::Commodity {
76
            amount,
77
            commodity_id,
78
        } => {
79
            let amt = if *amount.denom() == 1 {
80
                amount.numer().to_string()
81
            } else {
82
                format!("{}/{}", amount.numer(), amount.denom())
83
            };
84
            format!("(:commodity {amt} :id \"{commodity_id}\")")
85
        }
86
    }
87
831
}
88

            
89
fn format_bytes_literal(bytes: &[u8]) -> String {
90
    let parts: Vec<String> = bytes.iter().map(u8::to_string).collect();
91
    format!("#u8({})", parts.join(" "))
92
}
93

            
94
415
fn format_lambda_params(params: &LambdaParams) -> String {
95
415
    let mut parts = Vec::new();
96

            
97
    // Required parameters
98
415
    parts.extend(params.required.iter().cloned());
99

            
100
    // Optional parameters
101
415
    if !params.optional.is_empty() {
102
83
        parts.push("&optional".to_string());
103
83
        for (name, default) in &params.optional {
104
83
            if let Some(default_expr) = default {
105
83
                parts.push(format!("({} {})", name, format_expr(default_expr)));
106
83
            } else {
107
                parts.push(name.clone());
108
            }
109
        }
110
332
    }
111

            
112
    // Rest parameter
113
415
    if let Some(rest) = &params.rest {
114
83
        parts.push("&rest".to_string());
115
83
        parts.push(rest.clone());
116
332
    }
117

            
118
    // Key parameters
119
415
    if !params.key.is_empty() {
120
        parts.push("&key".to_string());
121
        for (name, default) in &params.key {
122
            if let Some(default_expr) = default {
123
                parts.push(format!("({} {})", name, format_expr(default_expr)));
124
            } else {
125
                parts.push(name.clone());
126
            }
127
        }
128
415
    }
129

            
130
    // Aux parameters
131
415
    if !params.aux.is_empty() {
132
        parts.push("&aux".to_string());
133
        for (name, init) in &params.aux {
134
            if let Some(init_expr) = init {
135
                parts.push(format!("({} {})", name, format_expr(init_expr)));
136
            } else {
137
                parts.push(name.clone());
138
            }
139
        }
140
415
    }
141

            
142
415
    parts.join(" ")
143
415
}
144

            
145
#[cfg(test)]
146
#[path = "format_tests.rs"]
147
mod tests;