xylo-lang 0.1.2

A functional programming language for generative art.
Documentation
#[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()));
    }
}