Skip to main content

sim_codec_algol/
encode.rs

1//! Encoding for the Algol codec: renders any `Expr` (or located expression
2//! tree) back to infix text, inserting parentheses according to operator
3//! binding power drawn from the Pratt table.
4
5use sim_codec::{Decoder, Encoder, Input, ReadCx, encode_string_literal};
6use sim_codec_lisp::{LispProcMacroDecoder, LispProcMacroEncoder};
7use sim_kernel::{
8    EncodeOptions, Expr, LocatedExprTree, NumberLiteral, Origin, PrattTable, Result, Symbol,
9    Trivia, WriteCx,
10};
11
12/// Renders an [`Expr`] back to Algol infix text using the operator `table`.
13///
14/// `parent_bp` is the binding power of the enclosing context; when an infix
15/// operator binds more loosely than its parent the rendered subexpression is
16/// wrapped in parentheses so the text reparses to the same tree. Forms outside
17/// the infix grammar (collections, quotes, and similar) are emitted through a
18/// `expr.lisp(...)` escape so the general-purpose codec still round-trips every
19/// expression the kernel can hold.
20///
21/// # Examples
22///
23/// ```
24/// use sim_codec_algol::{default_pratt_table, encode_algol};
25/// use sim_kernel::{EncodeOptions, Expr, Symbol, WriteCx};
26/// use sim_test_support::core_cx;
27///
28/// let mut cx = core_cx();
29/// let table = default_pratt_table();
30/// let expr = Expr::Infix {
31///     operator: Symbol::new("+"),
32///     left: Box::new(Expr::Symbol(Symbol::new("a"))),
33///     right: Box::new(Expr::Symbol(Symbol::new("b"))),
34/// };
35/// let mut write = WriteCx {
36///     cx: &mut cx,
37///     codec: sim_kernel::CodecId(0),
38///     options: EncodeOptions::default(),
39/// };
40/// assert_eq!(encode_algol(&expr, &table, 0, &mut write).unwrap(), "a + b");
41/// ```
42pub fn encode_algol(
43    expr: &Expr,
44    table: &PrattTable,
45    parent_bp: u16,
46    cx: &mut WriteCx<'_>,
47) -> Result<String> {
48    match expr {
49        Expr::Nil => Ok("nil".to_owned()),
50        Expr::Bool(true) => Ok("true".to_owned()),
51        Expr::Bool(false) => Ok("false".to_owned()),
52        Expr::Number(number) => encode_number_algol(number, table, cx),
53        Expr::Symbol(symbol) => encode_symbol_algol(symbol, table, cx),
54        Expr::String(value) => Ok(encode_string_literal(value)),
55        Expr::Infix {
56            operator,
57            left,
58            right,
59        } => {
60            let op = table.require_infix(operator)?;
61            let lhs = encode_algol(left, table, op.left_bp, cx)?;
62            let rhs = encode_algol(right, table, op.right_bp, cx)?;
63            let text = format!("{} {} {}", lhs, operator, rhs);
64            if op.left_bp < parent_bp {
65                Ok(format!("({})", text))
66            } else {
67                Ok(text)
68            }
69        }
70        Expr::Prefix { operator, arg } => {
71            let op = table.require_prefix(operator)?;
72            let inner = encode_algol(arg, table, op.right_bp, cx)?;
73            Ok(format!("{}{}", operator, inner))
74        }
75        Expr::Postfix { operator, arg } => {
76            let op = table.require_postfix(operator)?;
77            let inner = encode_algol(arg, table, op.left_bp, cx)?;
78            Ok(format!("{}{}", inner, operator))
79        }
80        Expr::Call { operator, args } => {
81            let op = encode_algol(operator, table, 110, cx)?;
82            let args = args
83                .iter()
84                .map(|arg| encode_algol(arg, table, 0, cx))
85                .collect::<Result<Vec<_>>>()?
86                .join(", ");
87            Ok(format!("{}({})", op, args))
88        }
89        other => encode_escape_algol(other, cx),
90    }
91}
92
93fn encode_number_algol(
94    number: &NumberLiteral,
95    table: &PrattTable,
96    cx: &mut WriteCx<'_>,
97) -> Result<String> {
98    let expr = Expr::Number(number.clone());
99    if algol_text_round_trips(&expr, &number.canonical, table, cx) {
100        return Ok(number.canonical.clone());
101    }
102    encode_escape_algol(&expr, cx)
103}
104
105fn encode_symbol_algol(
106    symbol: &Symbol,
107    table: &PrattTable,
108    cx: &mut WriteCx<'_>,
109) -> Result<String> {
110    let text = symbol.to_string();
111    let expr = Expr::Symbol(symbol.clone());
112    if algol_text_round_trips(&expr, &text, table, cx) {
113        return Ok(text);
114    }
115    encode_escape_algol(&expr, cx)
116}
117
118fn algol_text_round_trips(
119    expected: &Expr,
120    text: &str,
121    table: &PrattTable,
122    cx: &mut WriteCx<'_>,
123) -> bool {
124    crate::parse::parse_algol_expr_with_table(cx.cx, table.clone(), text)
125        .is_ok_and(|parsed| parsed.canonical_eq(expected))
126}
127
128pub(crate) fn encode_algol_tree(
129    tree: &LocatedExprTree,
130    table: &PrattTable,
131    parent_bp: u16,
132    cx: &mut WriteCx<'_>,
133) -> Result<String> {
134    let prefix = encode_trivia(&tree.origin);
135    let body = match &tree.expr {
136        Expr::Infix { operator, .. } if tree.children.len() == 2 => {
137            let op = table.require_infix(operator)?;
138            let lhs = encode_algol_tree(&tree.children[0], table, op.left_bp, cx)?;
139            let rhs = encode_algol_tree(&tree.children[1], table, op.right_bp, cx)?;
140            let text = format!("{} {} {}", lhs, operator, rhs);
141            if op.left_bp < parent_bp {
142                format!("({})", text)
143            } else {
144                text
145            }
146        }
147        Expr::Prefix { operator, .. } if tree.children.len() == 1 => {
148            let op = table.require_prefix(operator)?;
149            let inner = encode_algol_tree(&tree.children[0], table, op.right_bp, cx)?;
150            format!("{}{}", operator, inner)
151        }
152        Expr::Postfix { operator, .. } if tree.children.len() == 1 => {
153            let op = table.require_postfix(operator)?;
154            let inner = encode_algol_tree(&tree.children[0], table, op.left_bp, cx)?;
155            format!("{}{}", inner, operator)
156        }
157        Expr::Call { .. } if !tree.children.is_empty() => {
158            let operator = encode_algol_tree(&tree.children[0], table, 110, cx)?;
159            let args = tree.children[1..]
160                .iter()
161                .map(|arg| encode_algol_tree(arg, table, 0, cx))
162                .collect::<Result<Vec<_>>>()?
163                .join(", ");
164            format!("{}({})", operator, args)
165        }
166        _ => encode_algol(&tree.expr, table, parent_bp, cx)?,
167    };
168    Ok(format!("{prefix}{body}"))
169}
170
171fn encode_escape_algol(expr: &Expr, cx: &mut WriteCx<'_>) -> Result<String> {
172    let mut nested = WriteCx {
173        cx: &mut *cx.cx,
174        codec: cx.codec,
175        options: EncodeOptions::default(),
176    };
177    let text = LispProcMacroEncoder
178        .encode(&mut nested, expr)?
179        .into_text()?;
180    Ok(format!("expr.lisp({})", encode_string_literal(&text)))
181}
182
183fn encode_trivia(origin: &Option<Origin>) -> String {
184    origin
185        .as_ref()
186        .map(|origin| {
187            origin
188                .trivia
189                .iter()
190                .map(|item| match item {
191                    Trivia::Whitespace(text)
192                    | Trivia::LineComment(text)
193                    | Trivia::BlockComment(text) => text.clone(),
194                })
195                .collect::<Vec<_>>()
196                .join("")
197        })
198        .unwrap_or_default()
199}
200
201pub(crate) fn decode_escape(cx: &mut ReadCx<'_>, expr: Expr) -> Result<Expr> {
202    match expr {
203        Expr::Call { operator, args } => {
204            if matches!(operator.as_ref(), Expr::Symbol(symbol) if symbol == &Symbol::qualified("expr", "lisp"))
205            {
206                let [Expr::String(text)] = args.as_slice() else {
207                    return Err(sim_kernel::Error::Eval(
208                        "expr.lisp expects one string arg".to_owned(),
209                    ));
210                };
211                return LispProcMacroDecoder.decode(cx, Input::Text(text.clone()));
212            }
213
214            Ok(Expr::Call {
215                operator,
216                args: args
217                    .into_iter()
218                    .map(|arg| decode_escape(cx, arg))
219                    .collect::<Result<Vec<_>>>()?,
220            })
221        }
222        Expr::Infix {
223            operator,
224            left,
225            right,
226        } => Ok(Expr::Infix {
227            operator,
228            left: Box::new(decode_escape(cx, *left)?),
229            right: Box::new(decode_escape(cx, *right)?),
230        }),
231        Expr::Prefix { operator, arg } => Ok(Expr::Prefix {
232            operator,
233            arg: Box::new(decode_escape(cx, *arg)?),
234        }),
235        Expr::Postfix { operator, arg } => Ok(Expr::Postfix {
236            operator,
237            arg: Box::new(decode_escape(cx, *arg)?),
238        }),
239        Expr::List(items) => Ok(Expr::List(
240            items
241                .into_iter()
242                .map(|item| decode_escape(cx, item))
243                .collect::<Result<Vec<_>>>()?,
244        )),
245        Expr::Vector(items) => Ok(Expr::Vector(
246            items
247                .into_iter()
248                .map(|item| decode_escape(cx, item))
249                .collect::<Result<Vec<_>>>()?,
250        )),
251        Expr::Map(entries) => Ok(Expr::Map(
252            entries
253                .into_iter()
254                .map(|(key, value)| Ok((decode_escape(cx, key)?, decode_escape(cx, value)?)))
255                .collect::<Result<Vec<_>>>()?,
256        )),
257        Expr::Set(items) => Ok(Expr::Set(
258            items
259                .into_iter()
260                .map(|item| decode_escape(cx, item))
261                .collect::<Result<Vec<_>>>()?,
262        )),
263        Expr::Block(items) => Ok(Expr::Block(
264            items
265                .into_iter()
266                .map(|item| decode_escape(cx, item))
267                .collect::<Result<Vec<_>>>()?,
268        )),
269        Expr::Quote { mode, expr } => Ok(Expr::Quote { mode, expr }),
270        Expr::Annotated { expr, annotations } => Ok(Expr::Annotated {
271            expr: Box::new(decode_escape(cx, *expr)?),
272            annotations: annotations
273                .into_iter()
274                .map(|(name, value)| Ok((name, decode_escape(cx, value)?)))
275                .collect::<Result<Vec<_>>>()?,
276        }),
277        Expr::Extension { tag, payload } => Ok(Expr::Extension {
278            tag,
279            payload: Box::new(decode_escape(cx, *payload)?),
280        }),
281        other => Ok(other),
282    }
283}