Skip to main content

lykn_cli/
formatter.rs

1//! lykn formatter
2//!
3//! Pretty-prints s-expressions with consistent indentation.
4//! Follows common Lisp/Scheme formatting conventions.
5
6use crate::reader::SExpr;
7
8pub fn format_exprs(exprs: &[SExpr], indent: usize) -> String {
9    let mut out = String::new();
10    for (i, expr) in exprs.iter().enumerate() {
11        out.push_str(&format_expr(expr, indent));
12        if i + 1 < exprs.len() {
13            out.push('\n');
14            // Add blank line between top-level forms
15            if indent == 0 {
16                out.push('\n');
17            }
18        }
19    }
20    out.push('\n');
21    out
22}
23
24fn format_expr(expr: &SExpr, indent: usize) -> String {
25    match expr {
26        SExpr::Atom(s) => s.clone(),
27        SExpr::Str(s) => format!("\"{}\"", escape_string(s)),
28        SExpr::Num(n) => {
29            if *n == (*n as i64) as f64 {
30                format!("{}", *n as i64)
31            } else {
32                format!("{}", n)
33            }
34        }
35        SExpr::List(values) => format_list(values, indent),
36    }
37}
38
39fn format_list(values: &[SExpr], indent: usize) -> String {
40    if values.is_empty() {
41        return "()".to_string();
42    }
43
44    // Try single-line first
45    let single = format_single_line(values);
46    if single.len() + indent <= 80 && !single.contains('\n') {
47        return format!("({})", single);
48    }
49
50    // Multi-line: head on first line, rest indented
51    let head = format_expr(&values[0], 0);
52    let child_indent = indent + 2;
53    let child_prefix = " ".repeat(child_indent);
54
55    let mut out = format!("({}", head);
56    for val in &values[1..] {
57        let formatted = format_expr(val, child_indent);
58        out.push('\n');
59        out.push_str(&child_prefix);
60        out.push_str(&formatted);
61    }
62    out.push(')');
63    out
64}
65
66fn format_single_line(values: &[SExpr]) -> String {
67    values
68        .iter()
69        .map(|v| format_expr(v, 0))
70        .collect::<Vec<_>>()
71        .join(" ")
72}
73
74fn escape_string(s: &str) -> String {
75    s.replace('\\', "\\\\")
76        .replace('"', "\\\"")
77        .replace('\n', "\\n")
78        .replace('\t', "\\t")
79}