logo-interp 0.1.0

Implements parsing and interpreting of Logo programming language
Documentation
use std::collections::HashMap;
use std::mem::swap;
use crate::core::{LogoValue, Word};
use crate::executor_state::*;
use crate::parser;

pub fn execute_str<S>(state: &mut EState<S>, proc_source: &str, source: &str) -> Result<(), String> {
    state.logo_procedures = parser::parse_procedures(proc_source)?;
    execute(state, parser::parse(source)?)
}

pub fn execute<S>(state: &mut EState<S>, source: Vec<LogoValue>) -> Result<(), String> {
    let transformed_source = math_transform(source)?;
    let mut it = transformed_source.iter();
    while it.len() > 0 {
        match execute_expr(state, &mut it)? {
            Some(val) => return Err(format!("Don't know what to do with {}", val)),
            None => {}
        }
    }
    Ok(())
}

fn execute_expr<'a, S>(state: &mut EState<S>, it: &mut impl Iterator<Item = &'a LogoValue>) -> Result<Option<LogoValue>, String>
{
    let cmd = it.next();
    if cmd.is_none() {
        return Ok(None)
    }
    let cmd = cmd.unwrap();
    if let LogoValue::Word(word) = cmd {
        let word = &word.0;
        if let Ok(_) = word.parse::<f64>() {
            return Ok(Some(LogoValue::Word(Word(word.clone()))))
        }

        let word = word.to_lowercase();
        if let Some(var_name) = word.strip_prefix(":") {
            if !state.vars.contains_key(var_name) {
                return Err("No such variable".to_string());
            }
            return Ok(Some(state.vars[var_name].clone()));
        }

        let fun = state.functions.get(word.as_str());
        if let Some(fun) = fun {
            let f = fun.f.clone();
            let mut args = Vec::with_capacity(fun.args as usize);
            for _ in 0..fun.args {
                let arg = execute_expr(state, it)?;
                match arg {
                    Some(arg) => args.push(arg),
                    None => return Err(format!("Missing argument for {}", word))
                }
            }
            return (f)(state, args);
        }

        if let Some(logo_proc) = state.logo_procedures.get(word.as_str()) {
            let logo_proc = logo_proc.clone();
            let backup = backup_vars(state, &logo_proc.arg_names);
            for arg_name in &logo_proc.arg_names {
                let expr_result = execute_expr(state, it);
                if let Err(_) = &expr_result {
                    restore_vars(state, backup);
                    return Err(expr_result.err().unwrap());
                }
                let expr_result = expr_result.unwrap();
                if expr_result.is_none() {
                    restore_vars(state, backup);
                    return Err(format!("Missing argument for {}", word));
                }
                state.vars.insert(arg_name.clone(), expr_result.unwrap());
            }
            let proc_result = execute(state, logo_proc.code);
            restore_vars(state, backup);
            return match proc_result {
                Err(err) => {
                    if err == "Output" {
                        let output = state.output.clone();
                        state.output = None;
                        return Ok(output);
                    }
                    Err(err)
                },
                Ok(()) => Ok(None)
            }
        }
        return Err(format!("Don't know what to do with {}", cmd))
    }

    return Ok(Some(cmd.clone()));
}

fn backup_vars<S>(state: &EState<S>, var_names: &Vec<String>) -> Vec<(String, Option<LogoValue>)> {
    let mut result = Vec::with_capacity(var_names.len());
    for var_name in var_names {
        let val = state.vars.get(var_name);
        match val {
            Some(val) => result.push((var_name.clone(), Some(val.clone()))),
            None => result.push((var_name.clone(), None))
        }
    }
    result
}

fn restore_vars<S>(state: &mut EState<S>, backup: Vec<(String, Option<LogoValue>)>) {
    for (var_name, val) in backup {
        match val {
            Some(val) => {
                state.vars.insert(var_name, val);
            },
            None => {
                state.vars.remove(var_name.as_str());
            }
        }
    }
}

fn math_transform(source: Vec<LogoValue>) -> Result<Vec<LogoValue>, String> {
    let mut tree = BracketTree::parse(source)?;
    process_math_signs(&mut tree, &HashMap::from([
        ("*".to_string(), "product".to_string()),
        ("/".to_string(), "quotient".to_string()),
    ]))?;
    process_math_signs(&mut tree, &HashMap::from([
        ("+".to_string(), "sum".to_string()),
        ("-".to_string(), "difference".to_string()),
    ]))?;
    process_math_signs(&mut tree, &HashMap::from([
        (">".to_string(), "greater?".to_string()),
        ("<".to_string(), "less?".to_string()),
        ("=".to_string(), "equal?".to_string()),
    ]))?;
    Ok(tree.to_list())
}

fn process_math_signs(tree: &mut BracketTree, signs: &HashMap<String, String>) -> Result<(), String> {
    tree.process(&|nodes: Vec<BracketTreeChild>| -> Result<Vec<BracketTreeChild>, String> {
        let mut result = Vec::new();
        let mut it = nodes.into_iter();
        while let Some(node) = it.next() {
            if let BracketTreeChild::Value(val) = &node {
                if let LogoValue::Word(word) = val {
                    if let Some(sing_op) = signs.get(word.0.as_str()) {
                        let prev = match result.pop() {
                            Some(val) => val,
                            None => return Err(format!("Missing first argument for {}", word.0))
                        };
                        let next = match it.next() {
                            Some(val) => val,
                            None => return Err(format!("Missing second argument for {}", word.0))
                        };
                        let subtree = BracketTree{
                            children: vec![
                                BracketTreeChild::Value(LogoValue::Word(Word(sing_op.clone()))),
                                prev,
                                next
                            ]
                        };
                        result.push(BracketTreeChild::Tree(Box::new(subtree)));
                        continue;
                    }
                }
            }
            result.push(node);
        }
        Ok(result)
    })
}

struct BracketTree {
    children: Vec<BracketTreeChild>
}

enum BracketTreeChild {
    Value(LogoValue),
    Tree(Box<BracketTree>)
}

impl BracketTree {
    fn new() -> Self {
        BracketTree {children: Vec::new()}
    }

    fn parse(list: Vec<LogoValue>) -> Result<Self, String> {
        let mut stack = Vec::new();
        stack.push(BracketTree::new());
        for el in list {
            if let LogoValue::Word(word) = &el {
                if word.0 == "(" {
                    stack.push(BracketTree::new());
                    continue;
                }
                else if word.0 == ")" {
                    if stack.len() == 1 {
                        return Err("Missing corresponding opening bracket for ')'".to_string());
                    }
                    let last_stack = stack.pop().unwrap();
                    stack.last_mut().unwrap().children.push(BracketTreeChild::Tree(Box::new(last_stack)));
                    continue;
                }
            }
            stack.last_mut().unwrap().children.push(BracketTreeChild::Value(el));
        }
        if stack.len() > 1 {
            return Err("Missing corresponding closing bracket for '('".to_string());
        }
        Ok(stack.pop().unwrap())
    }

    fn into_list(self, list: &mut Vec<LogoValue>) {
        for child in self.children {
            match child {
                BracketTreeChild::Value(val) => {
                    list.push(val)
                },
                BracketTreeChild::Tree(tree) => {
                    tree.into_list(list);
                }
            }
        }
    }

    fn to_list(self) -> Vec<LogoValue> {
        let mut result = Vec::new();
        self.into_list(&mut result);
        result
    }

    fn process(&mut self, f: &impl Fn(Vec<BracketTreeChild>) -> Result<Vec<BracketTreeChild>, String>) -> Result<(), String> {
        let mut tmp = Vec::new();
        swap(&mut tmp, &mut self.children);
        self.children = f(tmp)?;
        for child in &mut self.children {
            if let BracketTreeChild::Tree(tree) = child {
                tree.process(f)?;
            }
        }
        Ok(())
    }
}

#[test]
fn test_execution() {
    use crate::stdlib::*;

    struct S {
        total: i32
    }
    let mut state = EState::new(S{total: 0});
    add_stdlib(&mut state);
    state.functions.insert("add".to_string(), Function::from_proc1(|s: &mut EState<S>, x: i32| -> Result<(), String> {
        s.state.total += x;
        Ok(())
    }));

    execute_str(&mut state, "", "add 5 repeat 4 [add 1] add 10").unwrap();
    assert_eq!(state.state.total, 19);

    state.state.total = 0;
    execute_str(&mut state, "", "make 'hi' 5 add :hi add thing 'hi'").unwrap();
    assert_eq!(state.state.total, 10);

    state.state.total = 0;
    execute_str(&mut state, "to add4 add 4 end \
        to add_double :x add :x add :x end \
        to double :x output sum :x :x end",
 "add4 add_double 6 add double 3").unwrap();
    assert_eq!(state.state.total, 22);
}

#[test]
fn test_execution_math() {
    use crate::stdlib::*;

    struct S {
        result: i32
    }
    let mut state = EState::new(S{result: 0});
    add_stdlib(&mut state);
    state.functions.insert("return".to_string(), Function::from_proc1(|s: &mut EState<S>, x: i32| -> Result<(), String> {
        s.state.result = x;
        Ok(())
    }));

    execute_str(&mut state, "", "return 2 + 3").unwrap();
    assert_eq!(state.state.result, 5);

    execute_str(&mut state, "", "return 2 + +4").unwrap();
    assert_eq!(state.state.result, 6);

    execute_str(&mut state, "", "return 2 + -3").unwrap();
    assert_eq!(state.state.result, -1);

    execute_str(&mut state, "", "return product 2 3 + sum 4 5").unwrap();
    assert_eq!(state.state.result, 24);

    execute_str(&mut state, "", "return (product 2 3) + (sum 4 5)").unwrap();
    assert_eq!(state.state.result, 15);

    execute_str(&mut state, "", "return 3 + 4 * 5 + 2").unwrap();
    assert_eq!(state.state.result, 25);

    execute_str(&mut state, "", "return (3 + 4) * (5 + 2)").unwrap();
    assert_eq!(state.state.result, 49);

    execute_str(&mut state, "", "return (1 + (3 + 4)) * ((5 + 2) + 2)").unwrap();
    assert_eq!(state.state.result, 72);
}

#[test]
fn test_execution_comparison() {
    use crate::stdlib::*;

    struct S {
        result: bool
    }
    let mut state = EState::new(S{result: false});
    add_stdlib(&mut state);
    state.functions.insert("return".to_string(), Function::from_proc1(|s: &mut EState<S>, x: bool| -> Result<(), String> {
        s.state.result = x;
        Ok(())
    }));

    execute_str(&mut state, "", "return 2 = 2").unwrap();
    assert_eq!(state.state.result, true);

    execute_str(&mut state, "", "return 2 = 4 / 2").unwrap();
    assert_eq!(state.state.result, true);

    execute_str(&mut state, "", "return 1 = pi / pi").unwrap();
    assert_eq!(state.state.result, true);

    execute_str(&mut state, "", "return 1/3 = 2/6").unwrap();
    assert_eq!(state.state.result, true);

    execute_str(&mut state, "", "return 1/4 < 1/5").unwrap();
    assert_eq!(state.state.result, false);

    execute_str(&mut state, "", "return (ln 1) > 0").unwrap();
    assert_eq!(state.state.result, false);
}