#[cfg(feature = "no-std")]
use alloc::{
format,
string::{String, ToString},
vec,
vec::Vec,
};
use crate::error::Result;
use crate::parser::{parse, Definition, Literal, Pattern, Token};
fn block_to_string(block: &[Token]) -> String {
let mut index = 0;
let mut stack = vec![Vec::new()];
let mut depth = 0;
let mut lets = vec![false];
let mut in_if = false;
while index < block.len() {
match &block[index] {
Token::Literal(literal) => {
stack[depth].push(literal.to_string());
index += 1;
}
Token::List(blocks) => {
stack[depth].push(format!(
"[{}]",
blocks
.iter()
.map(|block| block_to_string(block))
.collect::<Vec<_>>()
.join(", ")
));
index += 1;
}
Token::UnaryOperator(op) => {
let a = stack[depth].pop().unwrap();
stack[depth].push(format!("{}({})", op.as_str(), a));
index += 1;
}
Token::BinaryOperator(op) => {
let b = stack[depth].pop().unwrap();
let a = stack[depth].pop().unwrap();
stack[depth].push(format!("({} {} {})", a, op.as_str(), b));
index += 1;
}
Token::Call(name, argc) => {
let mut args = Vec::new();
for _ in 0..*argc {
args.push(stack[depth].pop().unwrap());
}
args.reverse();
if args.is_empty() {
stack[depth].push(name.to_string());
} else {
stack[depth].push(format!("({}{})", name, format!(" {}", args.join(" "))));
}
index += 1;
}
Token::Let(name, params, skip) => {
if !lets[depth] {
depth += 1;
stack.push(Vec::new());
lets.push(true);
}
stack[depth].push(format!(
"{}{} = {}",
name,
if params.is_empty() {
String::new()
} else {
format!(" {}", params.join(" "))
},
block_to_string(&block[index + 1..index + skip])
));
lets[depth] = true;
index += skip + 1;
}
Token::Pop => {
if lets[depth] {
let defs = stack[depth][..stack[depth].len() - 1].join("\n\t\t");
let block = stack[depth].last().unwrap().clone();
stack[depth - 1].push(format!("let {}\n\t\t{}", defs, block));
stack.pop().unwrap();
lets.pop().unwrap();
depth -= 1;
}
index += 1;
}
Token::If(skip) => {
stack[depth].push(block_to_string(&block[index + 1..index + skip]));
in_if = true;
index += skip;
}
Token::Jump(skip) => {
if in_if {
let else_block = block_to_string(&block[index + 1..index + skip + 1]);
let if_block = stack[depth].pop().unwrap();
let condition = stack[depth].pop().unwrap();
stack[depth].push(format!(
"if {}\n\t\t{}\n\telse\n\t\t{}",
condition, if_block, else_block
));
index += skip + 1;
in_if = false;
} else {
index += 1;
}
}
Token::Match(patterns) => {
let a = stack[depth].pop().unwrap();
index += 1;
let mut pats = Vec::new();
for (pattern, skip) in patterns {
match pattern {
Pattern::Matches(matches) => {
let matches = matches
.iter()
.map(Literal::to_string)
.collect::<Vec<String>>()
.join(",");
pats.push(format!(
"{}\n\t\t\t{}",
matches,
block_to_string(&block[index..index + skip])
));
}
Pattern::Wildcard => {
pats.push(format!(
"_\n\t\t\t{}",
block_to_string(&block[index..index + skip])
));
}
}
index += skip;
}
stack[depth].push(format!("match {}\n\t\t{}", a, pats.join("\n\t\t")));
}
Token::ForStart(var) => {
let iter = stack[depth].pop().unwrap();
depth += 1;
stack.push(vec![format!("for {} in {}", var, iter)]);
lets.push(false);
index += 1;
}
Token::ForEnd => {
let block = stack[depth].pop().unwrap();
let def = stack[depth].pop().unwrap();
stack[depth - 1].push([def, block].join("\n\t\t"));
stack.pop().unwrap();
lets.pop().unwrap();
depth -= 1;
index += 1;
}
Token::LoopStart => {
let count = stack[depth].pop().unwrap();
depth += 1;
stack.push(vec![format!("loop {}", count)]);
lets.push(false);
index += 1;
}
Token::LoopEnd => {
let block = stack[depth].pop().unwrap();
let def = stack[depth].pop().unwrap();
stack[depth - 1].push([def, block].join("\n\t\t"));
stack.pop().unwrap();
lets.pop().unwrap();
depth -= 1;
index += 1;
}
Token::Return(_) => index += 1,
}
}
assert!(stack.len() == 1);
stack[0].pop().unwrap()
}
fn definition_to_string(definition: &Definition) -> String {
if definition.weight == 1.0 {
format!(
"{}{} =\n\t{}",
definition.name,
if definition.params.is_empty() {
String::new()
} else {
format!(" {}", definition.params.join(" "))
},
block_to_string(&definition.block)
)
} else {
format!(
"{}@{}{} =\n\t{}",
definition.name,
definition.weight,
if definition.params.is_empty() {
String::new()
} else {
format!(" {}", definition.params.join(" "))
},
block_to_string(&definition.block)
)
}
}
pub fn format(input: &str) -> Result<String> {
let tree = parse(input)?;
let output = tree
.iter()
.map(|definition| definition_to_string(definition))
.collect::<Vec<String>>()
.join("\n\n");
Ok(format!("{}\n", output))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::interpreter::execute;
use crate::out::Config;
fn can_execute(output: &str) -> bool {
let config = Config {
seed: Some([0; 32]),
..Config::default()
};
parse(output).and_then(|tree| execute(tree, config)).is_ok()
}
#[test]
fn test_binary_operation() {
let output = format(
"
root= square
square =ss (3+5) SQUARE
",
);
assert!(output.is_ok());
assert_eq!(
output.as_ref().unwrap(),
"\
root =
square
square =
(ss (3 + 5) SQUARE)
"
);
assert!(can_execute(output.as_ref().unwrap()));
}
#[test]
fn test_let_statement() {
let output = format(
"
root = square
square =
let n1 = 3; n2 = 5 ->
tx (n1+n2) SQUARE
",
);
assert!(output.is_ok());
assert_eq!(
output.as_ref().unwrap(),
"\
root =
square
square =
let n1 = 3
n2 = 5
(tx (n1 + n2) SQUARE)
"
);
assert!(can_execute(output.as_ref().unwrap()));
}
#[test]
fn test_if_statement() {
let output = format(
"
root =shape true
shape is_square =if is_square
SQUARE
else -> CIRCLE
",
);
assert!(output.is_ok());
assert_eq!(
output.as_ref().unwrap(),
"\
root =
(shape true)
shape is_square =
if is_square
SQUARE
else
CIRCLE
"
);
assert!(can_execute(output.as_ref().unwrap()));
}
#[test]
fn test_match_statement() {
let output = format(
"
root = shape 1
shape n =
match n
1 -> SQUARE; 2 -> CIRCLE
3 -> TRIANGLE; _ -> EMPTY
",
);
assert!(output.is_ok());
assert_eq!(
output.as_ref().unwrap(),
"\
root =
(shape 1)
shape n =
match n
1
SQUARE
2
CIRCLE
3
TRIANGLE
_
EMPTY
"
);
assert!(can_execute(output.as_ref().unwrap()));
}
#[test]
fn test_for_statement() {
let output = format(
"
root = collect squares
squares =
for i in 0..3 -> tx i SQUARE
",
);
assert!(output.is_ok());
assert_eq!(
output.as_ref().unwrap(),
"\
root =
(collect squares)
squares =
for i in (0 .. 3)
(tx i SQUARE)
"
);
assert!(can_execute(output.as_ref().unwrap()));
}
#[test]
fn test_loop_statement() {
let output = format(
"
root = collect squares
squares =
loop 3 -> tx (rand * 10) SQUARE
",
);
assert!(output.is_ok());
assert_eq!(
output.as_ref().unwrap(),
"\
root =
(collect squares)
squares =
loop 3
(tx (rand * 10) SQUARE)
"
);
assert!(can_execute(output.as_ref().unwrap()));
}
}