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
30745
pub(crate) fn format_expr(expr: &Expr) -> String {
11
14490
    match expr {
12
69
        Expr::Nil => "NIL".to_string(),
13
272
        Expr::Bool(true) => "#T".to_string(),
14
        Expr::Bool(false) => "NIL".to_string(),
15
14490
        Expr::Number(n) if *n.denom() == 1 => n.numer().to_string(),
16
68
        Expr::Number(n) => format!("{}/{}", n.numer(), n.denom()),
17
4898
        Expr::String(s) => s.clone(),
18
7004
        Expr::Symbol(s) => s.clone(),
19
        Expr::Keyword(s) => format!(":{s}"),
20
        Expr::Bytes(b) => format_bytes_literal(b),
21
68
        Expr::Cons(car, cdr) => format!("({} . {})", format_expr(car), format_expr(cdr)),
22
2176
        Expr::List(elems) => {
23
2176
            let inner: Vec<_> = elems.iter().map(format_expr).collect();
24
2176
            format!("({})", inner.join(" "))
25
        }
26
748
        Expr::Quote(e) => format!("'{}", format_expr(e)),
27
        Expr::Quasiquote(e) => format!("`{}", format_expr(e)),
28
        Expr::Unquote(e) => format!(",{}", format_expr(e)),
29
        Expr::UnquoteSplicing(e) => format!(",@{}", format_expr(e)),
30
340
        Expr::Lambda(params, body) => {
31
340
            format!(
32
                "(LAMBDA ({}) {})",
33
340
                format_lambda_params(params),
34
340
                format_expr(body)
35
            )
36
        }
37
        Expr::RuntimeValue(val) => format_runtime_value(val),
38
136
        Expr::WasmRuntime(ty) => format!("#<wasm:{ty}>"),
39
544
        Expr::WasmLocal(idx, ty) => format!("#<local:{idx}:{ty}>"),
40
    }
41
30745
}
42

            
43
680
pub(super) fn format_runtime_value(val: &Value) -> String {
44
272
    match val {
45
68
        Value::Nil => "NIL".to_string(),
46
68
        Value::Bool(true) => "#T".to_string(),
47
        Value::Bool(false) => "NIL".to_string(),
48
272
        Value::Number(n) if *n.denom() == 1 => n.numer().to_string(),
49
        Value::Number(n) => format!("{}/{}", n.numer(), n.denom()),
50
        Value::String(s) => format!("\"{s}\""),
51
        Value::Symbol(s) => s.clone(),
52
        Value::Bytes(b) => format_bytes_literal(b),
53
        Value::Pair(_) | Value::Vector(_) | Value::Closure(_) => {
54
            format!("#<{}>", val.type_name())
55
        }
56
272
        Value::Struct { name, fields } => {
57
272
            let field_strs: Vec<_> = fields.iter().map(format_runtime_value).collect();
58
272
            format!("#S({name} {})", field_strs.join(" "))
59
        }
60
        Value::Commodity {
61
            amount,
62
            commodity_id,
63
        } => {
64
            let amt = if *amount.denom() == 1 {
65
                amount.numer().to_string()
66
            } else {
67
                format!("{}/{}", amount.numer(), amount.denom())
68
            };
69
            format!("(:commodity {amt} :id \"{commodity_id}\")")
70
        }
71
    }
72
680
}
73

            
74
fn format_bytes_literal(bytes: &[u8]) -> String {
75
    let parts: Vec<String> = bytes.iter().map(u8::to_string).collect();
76
    format!("#u8({})", parts.join(" "))
77
}
78

            
79
340
fn format_lambda_params(params: &LambdaParams) -> String {
80
340
    let mut parts = Vec::new();
81

            
82
    // Required parameters
83
340
    parts.extend(params.required.iter().cloned());
84

            
85
    // Optional parameters
86
340
    if !params.optional.is_empty() {
87
68
        parts.push("&optional".to_string());
88
68
        for (name, default) in &params.optional {
89
68
            if let Some(default_expr) = default {
90
68
                parts.push(format!("({} {})", name, format_expr(default_expr)));
91
68
            } else {
92
                parts.push(name.clone());
93
            }
94
        }
95
272
    }
96

            
97
    // Rest parameter
98
340
    if let Some(rest) = &params.rest {
99
68
        parts.push("&rest".to_string());
100
68
        parts.push(rest.clone());
101
272
    }
102

            
103
    // Key parameters
104
340
    if !params.key.is_empty() {
105
        parts.push("&key".to_string());
106
        for (name, default) in &params.key {
107
            if let Some(default_expr) = default {
108
                parts.push(format!("({} {})", name, format_expr(default_expr)));
109
            } else {
110
                parts.push(name.clone());
111
            }
112
        }
113
340
    }
114

            
115
    // Aux parameters
116
340
    if !params.aux.is_empty() {
117
        parts.push("&aux".to_string());
118
        for (name, init) in &params.aux {
119
            if let Some(init_expr) = init {
120
                parts.push(format!("({} {})", name, format_expr(init_expr)));
121
            } else {
122
                parts.push(name.clone());
123
            }
124
        }
125
340
    }
126

            
127
340
    parts.join(" ")
128
340
}