Skip to main content

online_dsl_forge/parser/
format.rs

1use 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}