1#![no_std]
2#![warn(clippy::pedantic)]
3extern crate alloc;
4use alloc::string::String;
5use alloc::vec::Vec;
6
7#[cfg(feature = "wasm")]
8use wasm_bindgen::prelude::*;
9
10#[cfg(feature = "wasm")]
11#[global_allocator]
12static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
13
14enum Token {
15 LParen,
16 RParen,
17 Atom(String),
18}
19
20fn tokenize(input: &str) -> Vec<Token> {
21 let mut tokens = Vec::new();
22 let mut chars = input.chars().peekable();
23
24 while let Some(c) = chars.next() {
25 if c.is_whitespace() {
26 continue;
27 }
28 if c == '(' {
29 tokens.push(Token::LParen);
30 } else if c == ')' {
31 tokens.push(Token::RParen);
32 } else if c == '"' {
33 let mut s = String::new();
34 s.push('"');
35 while let Some(&next) = chars.peek() {
36 s.push(next);
37 chars.next();
38 if next == '"' {
39 break;
40 }
41 }
42 tokens.push(Token::Atom(s));
43 } else {
44 let mut s = String::new();
45 s.push(c);
46 while let Some(&next) = chars.peek() {
47 if next.is_whitespace() || next == '(' || next == ')' {
48 break;
49 }
50 s.push(next);
51 chars.next();
52 }
53 tokens.push(Token::Atom(s));
54 }
55 }
56
57 tokens
58}
59
60enum Node {
61 Atom(String),
62 List(Vec<Node>),
63}
64
65fn parse_node(tokens: &[Token], mut i: usize) -> (Node, usize) {
66 if i >= tokens.len() {
67 return (Node::Atom(String::new()), i);
68 }
69 match &tokens[i] {
70 Token::LParen => {
71 i += 1;
72 let mut children = Vec::new();
73 while i < tokens.len() {
74 if let Token::RParen = tokens[i] {
75 i += 1; break;
77 }
78 let (child, new_i) = parse_node(tokens, i);
79 children.push(child);
80 i = new_i;
81 }
82 (Node::List(children), i)
83 }
84 Token::RParen => (Node::Atom(String::from(")")), i + 1),
85 Token::Atom(ref s) => (Node::Atom(s.clone()), i + 1),
86 }
87}
88
89fn parse_all(tokens: &[Token]) -> Vec<Node> {
90 let mut nodes = Vec::new();
91 let mut i = 0;
92 while i < tokens.len() {
93 let (node, new_i) = parse_node(tokens, i);
94 nodes.push(node);
95 i = new_i;
96 }
97 nodes
98}
99
100fn indent_str(indent: usize) -> String {
101 let mut s = String::new();
102 for _ in 0..indent {
103 s.push_str(" ");
104 }
105 s
106}
107
108fn is_flat_node(node: &Node) -> bool {
110 match node {
111 Node::Atom(_) => true,
112 Node::List(children) => children.iter().all(is_flat_node),
113 }
114}
115
116fn is_flat_list(nodes: &[Node]) -> bool {
117 nodes.iter().all(is_flat_node)
118}
119
120fn format_node_inline(node: &Node) -> String {
122 match node {
123 Node::Atom(s) => s.clone(),
124 Node::List(children) => {
125 let mut s = String::new();
126 s.push('(');
127 let mut first = true;
128 for child in children {
129 if !first {
130 s.push(' ');
131 }
132 s.push_str(&format_node_inline(child));
133 first = false;
134 }
135 s.push(')');
136 s
137 }
138 }
139}
140
141#[allow(dead_code)]
143fn format_inline(node: &Node) -> Option<String> {
144 if is_flat_node(node) {
145 Some(format_node_inline(node))
146 } else {
147 None
148 }
149}
150
151fn is_inline_signature(node: &Node) -> bool {
153 if let Node::List(children) = node {
154 if let Some(Node::Atom(ref keyword)) = children.first() {
155 return keyword == "export" || keyword == "param" || keyword == "result";
156 }
157 }
158 false
159}
160
161fn is_opcode(token: &str) -> bool {
163 if token.starts_with('$') || token.starts_with('"') {
164 return false;
165 }
166 let mut chars = token.chars();
167 if let Some(first) = chars.next() {
168 if (first == '-' || first == '+') && chars.clone().all(|c| c.is_ascii_digit()) {
169 return false;
170 }
171 if first.is_ascii_digit() && token.chars().all(|c| c.is_ascii_digit()) {
172 return false;
173 }
174 }
175 true
176}
177
178fn format_instructions(nodes: &[Node], base_indent: usize) -> String {
180 let mut result = String::new();
181 let mut current_indent = base_indent;
182 let mut i = 0;
183 while i < nodes.len() {
184 match &nodes[i] {
185 Node::Atom(token) => {
186 if token == "if" {
187 result.push('\n');
188 result.push_str(&indent_str(current_indent));
189 result.push_str("if");
190 current_indent += 1;
191 i += 1;
192 } else if token == "else" {
193 current_indent -= 1;
195 result.push('\n');
196 result.push_str(&indent_str(current_indent));
197 result.push_str("else");
198 current_indent += 1;
200 i += 1;
201 } else if token == "end" {
202 current_indent = current_indent.saturating_sub(1);
203 result.push('\n');
204 result.push_str(&indent_str(current_indent));
205 result.push_str("end");
206 i += 1;
207 } else if is_opcode(token) {
208 let mut line = token.clone();
210 i += 1;
211 while i < nodes.len() {
212 if let Node::Atom(next_token) = &nodes[i] {
213 if is_opcode(next_token)
214 || next_token == "if"
215 || next_token == "else"
216 || next_token == "end"
217 {
218 break;
219 }
220 line.push(' ');
221 line.push_str(next_token);
222 i += 1;
223 } else {
224 break;
225 }
226 }
227 result.push('\n');
228 result.push_str(&indent_str(current_indent));
229 result.push_str(&line);
230 } else {
231 result.push('\n');
233 result.push_str(&indent_str(current_indent));
234 result.push_str(token);
235 i += 1;
236 }
237 }
238 Node::List(_) => {
239 result.push('\n');
240 result.push_str(&indent_str(current_indent));
241 result.push_str(&format_node(&nodes[i], current_indent));
242 i += 1;
243 }
244 }
245 }
246 result
247}
248
249fn format_node(node: &Node, indent: usize) -> String {
251 match node {
252 Node::Atom(s) => s.clone(),
253 Node::List(children) => {
254 if children.is_empty() {
255 return String::from("()");
256 }
257 if let Some(Node::Atom(ref ident)) = children.first() {
259 if ident == "module" {
260 let mut s = String::new();
261 s.push('(');
262 s.push_str(ident);
263 for child in children.iter().skip(1) {
264 s.push('\n');
265 s.push_str(&indent_str(indent + 1));
266 s.push_str(&format_node(child, indent + 1));
267 }
268 s.push('\n');
269 s.push_str(&indent_str(indent));
270 s.push(')');
271 return s;
272 } else if ident == "func" {
273 let mut s = String::new();
274 s.push('(');
275 s.push_str(&format_node_inline(&children[0]));
277 let mut i = 1;
278 while i < children.len() {
280 if let Node::Atom(ref tok) = children[i] {
283 if is_opcode(tok) {
284 break;
285 }
286 }
287 if let Node::List(_) = children[i] {
288 if !is_inline_signature(&children[i]) {
289 break;
290 }
291 }
292 s.push(' ');
293 s.push_str(&format_node_inline(&children[i]));
294 i += 1;
295 }
296 s.push_str(&format_instructions(&children[i..], indent + 1));
298 s.push('\n');
299 s.push_str(&indent_str(indent));
300 s.push(')');
301 return s;
302 } else if ["forall", "exists", "assume", "unique"].contains(&ident.as_str()) {
303 let mut s = String::new();
304 s.push('(');
305 s.push_str(ident);
306 s.push_str(&format_instructions(&children[1..], indent + 1));
307 s.push('\n');
308 s.push_str(&indent_str(indent));
309 s.push(')');
310 return s;
311 }
312 }
313 if is_flat_list(children) {
315 format_node_inline(node)
316 } else {
317 let mut s = String::new();
318 s.push('(');
319 let mut first = true;
320 for child in children {
321 if first {
322 s.push_str(&format_node(child, indent + 1));
323 first = false;
324 } else {
325 s.push('\n');
326 s.push_str(&indent_str(indent + 1));
327 s.push_str(&format_node(child, indent + 1));
328 }
329 }
330 s.push('\n');
331 s.push_str(&indent_str(indent));
332 s.push(')');
333 s
334 }
335 }
336 }
337}
338
339#[cfg_attr(feature = "wasm", wasm_bindgen)]
341#[must_use]
342pub fn format(input: &str) -> String {
343 let tokens = tokenize(input);
344 let nodes = parse_all(&tokens);
345 if nodes.len() == 1 {
346 format_node(&nodes[0], 0)
347 } else {
348 let mut s = String::new();
349 for node in nodes {
350 s.push_str(&format_node(&node, 0));
351 s.push('\n');
352 }
353 s
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use super::*;
360
361 #[test]
362 fn test_format() {
363 let input = r#"(module (func $add (param $a i32) (param $b i32) (result i32) (local $c i32) i32.uzumaki local.set $c local.get $a local.get $c i32.add) (export "add" (func $add) ) )"#;
364 let expected = r#"(module
365 (func $add (param $a i32) (param $b i32) (result i32)
366 (local $c i32)
367 i32.uzumaki
368 local.set $c
369 local.get $a
370 local.get $c
371 i32.add
372 )
373 (export "add" (func $add))
374)"#;
375 let output = format(input);
376 assert_eq!(output, expected);
377 }
378}