online_dsl_forge/parser/
format.rs1use super::ast::{AstExpression, BinaryOp, ExprKind};
2
3pub fn format_expression(expression: &AstExpression) -> String {
4 format_with_parent(expression, 0, ChildSide::Root)
5}
6
7#[derive(Clone, Copy, Eq, PartialEq)]
8enum ChildSide {
9 Root,
10 Left,
11 Right,
12 Unary,
13 Receiver,
14}
15
16fn format_with_parent(
17 expression: &AstExpression,
18 parent_precedence: u8,
19 side: ChildSide,
20) -> String {
21 let own_precedence = precedence(expression);
22 let mut output = match &expression.kind {
23 ExprKind::Null => "null".to_string(),
24 ExprKind::Bool { value } => value.to_string(),
25 ExprKind::Int { value } => value.to_string(),
26 ExprKind::Float { value } => format_float(*value),
27 ExprKind::String { value } => format!("\"{}\"", escape_string(value)),
28 ExprKind::Array { items } => {
29 let items = items
30 .iter()
31 .map(format_expression)
32 .collect::<Vec<_>>()
33 .join(", ");
34 format!("[{items}]")
35 }
36 ExprKind::Identifier { name } => name.clone(),
37 ExprKind::Member { receiver, name } => {
38 format!(
39 "{}.{}",
40 format_with_parent(receiver, own_precedence, ChildSide::Receiver),
41 name
42 )
43 }
44 ExprKind::FunctionCall { name, args } => format!("{name}({})", format_args(args)),
45 ExprKind::MethodCall {
46 receiver,
47 name,
48 args,
49 } => format!(
50 "{}.{}({})",
51 format_with_parent(receiver, own_precedence, ChildSide::Receiver),
52 name,
53 format_args(args)
54 ),
55 ExprKind::Unary { op, expr } => {
56 format!(
57 "{}{}",
58 op.as_str(),
59 format_with_parent(expr, own_precedence, ChildSide::Unary)
60 )
61 }
62 ExprKind::Binary { left, op, right } => format!(
63 "{} {} {}",
64 format_with_parent(left, own_precedence, ChildSide::Left),
65 op.as_str(),
66 format_with_parent(right, own_precedence, ChildSide::Right)
67 ),
68 };
69
70 if needs_parentheses(own_precedence, parent_precedence, side) {
71 output = format!("({output})");
72 }
73 output
74}
75
76fn format_args(args: &[AstExpression]) -> String {
77 args
78 .iter()
79 .map(format_expression)
80 .collect::<Vec<_>>()
81 .join(", ")
82}
83
84fn precedence(expression: &AstExpression) -> u8 {
85 match &expression.kind {
86 ExprKind::Binary { op, .. } => binary_precedence(*op),
87 ExprKind::Unary { .. } => 7,
88 ExprKind::Member { .. } | ExprKind::FunctionCall { .. } | ExprKind::MethodCall { .. } => 8,
89 ExprKind::Null
90 | ExprKind::Bool { .. }
91 | ExprKind::Int { .. }
92 | ExprKind::Float { .. }
93 | ExprKind::String { .. }
94 | ExprKind::Array { .. }
95 | ExprKind::Identifier { .. } => 9,
96 }
97}
98
99fn binary_precedence(op: BinaryOp) -> u8 {
100 match op {
101 BinaryOp::Or => 1,
102 BinaryOp::And => 2,
103 BinaryOp::Eq | BinaryOp::Ne => 3,
104 BinaryOp::Lt | BinaryOp::Le | BinaryOp::Gt | BinaryOp::Ge => 4,
105 BinaryOp::Add | BinaryOp::Sub => 5,
106 BinaryOp::Mul | BinaryOp::Div | BinaryOp::Rem => 6,
107 }
108}
109
110fn needs_parentheses(own: u8, parent: u8, side: ChildSide) -> bool {
111 if matches!(side, ChildSide::Root) {
112 return false;
113 }
114 own < parent || (side == ChildSide::Right && own == parent)
115}
116
117fn escape_string(value: &str) -> String {
118 let mut escaped = String::new();
119 for ch in value.chars() {
120 match ch {
121 '\\' => escaped.push_str("\\\\"),
122 '"' => escaped.push_str("\\\""),
123 '\n' => escaped.push_str("\\n"),
124 '\r' => escaped.push_str("\\r"),
125 '\t' => escaped.push_str("\\t"),
126 other => escaped.push(other),
127 }
128 }
129 escaped
130}
131
132fn format_float(value: f64) -> String {
133 let mut output = value.to_string();
134 if value.is_finite() && !output.contains('.') && !output.contains('e') && !output.contains('E') {
135 output.push_str(".0");
136 }
137 output
138}
139
140#[cfg(test)]
141mod tests {
142 use crate::parse_expression;
143
144 use super::format_expression;
145
146 #[test]
147 fn preserves_right_nested_binary_shape() {
148 let ast = parse_expression("1 - (2 - 3)").expect("expression should parse");
149 assert_eq!(format_expression(&ast), "1 - (2 - 3)");
150 }
151
152 #[test]
153 fn normalizes_strings() {
154 let ast = parse_expression("'a\\nb'").expect("expression should parse");
155 assert_eq!(format_expression(&ast), "\"a\\nb\"");
156 }
157}