tuck5 0.2.0

A pragmatic lexer/parser generator
Documentation
use std::collections::HashMap;

use crate::meta::graph_with_tags;

use super::*;
use test_case::test_case;

pub fn parse(text: &str) -> Vec<Token<Vec<String>>> {
    meta::eval_prog_from_text(
        "
        % {
            {.print.}. print;
            {.let.}. let;
            {.true.}. true, expr;
            {.false.}. false, expr;
            {.if.}. if;
            {.input.}. input, expr;
            a..z | A..Z | _. letter;
            letter+. word, expr;

            0..9+. int, positive, number, expr;
            int & '.' & int+. decimal, positive, number, expr;
            '-' & positive. negative, number, expr;

            ws~;
        }

        {
            expr & '+' & expr: add, expr;
            print & expr & ';': printStmt, stmt;
            let & word & '=' & expr & ';': letStmt, stmt;
            word & '=' & expr & ';': mutStmt, stmt;
            if & expr & '{' & stmt+ & '}': ifStmt, stmt;
        }
    ",
        text,
    )
}

pub struct Program {
    prog: Vec<Statement>,
    errors: Vec<String>,
}

pub enum Statement {
    Let {
        var: String,
        val: Expression,
    },
    Mut {
        var: String,
        val: Expression,
    },
    If {
        cond: Expression,
        body: Vec<Statement>,
    },
    Print(Expression),
}

pub enum Expression {
    Number(f64),
    VarRef(String),
    Add(Box<Expression>, Box<Expression>),
    True,
    False,
    Input,
}

pub enum StackOp {
    Push(u64),
    Move(usize),
    Set(usize),
    Print,
    Add,
    If(Vec<StackOp>),
    Input,
}

pub fn eval_program(tokens: &Vec<Token<Vec<String>>>) -> Option<Program> {
    graph_with_tags(tokens);

    let mut to_ret = Program {
        prog: Vec::new(),
        errors: Vec::new(),
    };

    for token in tokens {
        if let Some(stmt) = eval_stmt(token) {
            to_ret.prog.push(stmt);
        } else {
            to_ret
                .errors
                .push("Tried to add statement, but could not.".to_string());
        }
    }

    Some(to_ret)
}

pub fn eval_stmt(token: &Token<Vec<String>>) -> Option<Statement> {
    if let TokenType::Branch(children) = &token.t_type {
        if token.data.contains(&"printStmt".to_string()) {
            Some(Statement::Print(eval_expr(&children[1])?))
        } else if token.data.contains(&"letStmt".to_string()) {
            Some(Statement::Let {
                var: children[1].content().to_string(),
                val: eval_expr(&children[3])?,
            })
        } else if token.data.contains(&"mutStmt".to_string()) {
            Some(Statement::Mut {
                var: children[0].content().to_string(),
                val: eval_expr(&children[2])?,
            })
        } else if token.data.contains(&"ifStmt".to_string()) {
            Some(Statement::If {
                cond: eval_expr(&children[1])?,
                body: children[3..children.len() - 1]
                    .iter()
                    .map(|t| eval_stmt(t))
                    .filter_map(|o| o)
                    .collect(),
            })
        } else {
            None
        }
    } else {
        None
    }
}

pub fn eval_expr(token: &Token<Vec<String>>) -> Option<Expression> {
    if token.data.contains(&"number".to_string()) {
        Some(Expression::Number(str::parse::<f64>(token.content()).ok()?))
    } else if token.data.contains(&"word".to_string()) {
        Some(Expression::VarRef(token.content().to_string()))
    } else if token.data.contains(&"add".to_string()) {
        if let TokenType::Branch(children) = &token.t_type {
            Some(Expression::Add(
                Box::new(eval_expr(&children[0])?),
                Box::new(eval_expr(&children[2])?),
            ))
        } else {
            None
        }
    } else if token.data.contains(&"true".to_string()) {
        Some(Expression::True)
    } else if token.data.contains(&"false".to_string()) {
        Some(Expression::False)
    } else if token.data.contains(&"input".to_string()) {
        Some(Expression::Input)
    } else {
        None
    }
}

pub fn prog_to_stack(prog: &Program) -> Vec<StackOp> {
    let mut vars: HashMap<&str, usize> = HashMap::new();
    let mut ops = Vec::new();
    let mut stack_length = 0usize;
    for err in &prog.errors {
        println!("ERROR: {err}");
    }
    for stmt in &prog.prog {
        ops.append(&mut stmt_on_stack(stmt, &mut vars, &mut stack_length));
    }
    ops
}

pub fn stmt_on_stack<'a>(
    stmt: &'a Statement,
    vars: &mut HashMap<&'a str, usize>,
    stack_length: &mut usize,
) -> Vec<StackOp> {
    let mut ops = Vec::new();
    match stmt {
        Statement::Let { var, val } => {
            eval_to_stack(val, &mut ops, &vars, stack_length);
            vars.insert(&var, *stack_length - 1);
        }
        Statement::Mut { var, val } => {
            eval_to_stack(val, &mut ops, &vars, stack_length);
            ops.push(StackOp::Set(*vars.get(var.as_str()).unwrap()))
        }
        Statement::Print(var) => {
            eval_to_stack(var, &mut ops, &vars, stack_length);
            ops.push(StackOp::Print);
            *stack_length += 1;
        }
        Statement::If { cond, body } => {
            eval_to_stack(cond, &mut ops, &vars, stack_length);
            ops.push(StackOp::If(
                body.iter()
                    .map(|s| stmt_on_stack(s, vars, stack_length))
                    .flatten()
                    .collect(),
            ))
        }
    }
    ops
}

pub fn eval_to_stack(
    expr: &Expression,
    ops: &mut Vec<StackOp>,
    vars: &HashMap<&str, usize>,
    stack_length: &mut usize,
) {
    match expr {
        Expression::Number(num) => {
            ops.push(StackOp::Push(num.to_bits()));
            *stack_length += 1;
        }
        Expression::VarRef(var) => {
            ops.push(StackOp::Move(vars[var.as_str()]));
            *stack_length += 1;
        }
        Expression::Add(lhs, rhs) => {
            eval_to_stack(lhs, ops, vars, stack_length);
            eval_to_stack(rhs, ops, vars, stack_length);
            ops.push(StackOp::Add);
            *stack_length -= 1;
        }
        Expression::True => {
            ops.push(StackOp::Push(1));
            *stack_length += 1;
        }
        Expression::False => {
            ops.push(StackOp::Push(0));
            *stack_length += 1;
        }
        Expression::Input => {
            ops.push(StackOp::Input);
            *stack_length += 1;
        }
    }
}

pub fn run_stack_ops(stack: &mut Vec<u64>, ops: &Vec<StackOp>, verbose: bool) {
    for op in ops {
        match op {
            StackOp::Move(ptr) => {
                if verbose {
                    println!("Pushing element at {ptr}");
                }
                stack.push(stack[*ptr]);
            }
            StackOp::Set(ptr) => {
                let val = stack.pop().unwrap();
                if verbose {
                    println!("Setting {ptr} to {val}");
                }
                stack[*ptr] = val;
            }
            StackOp::Print => println!("{}", f64::from_bits(stack.pop().unwrap())),
            StackOp::Push(val) => {
                if verbose {
                    println!("Pushing {val}");
                }
                stack.push(*val);
            }
            StackOp::Add => {
                let rhs = f64::from_bits(stack.pop().unwrap());
                let lhs = f64::from_bits(stack.pop().unwrap());
                if verbose {
                    println!("Adding {lhs} + {rhs}");
                }
                stack.push((lhs + rhs).to_bits());
            }
            StackOp::If(inner) => {
                if stack.pop().unwrap() == 1 {
                    run_stack_ops(stack, inner, verbose)
                }
            }
            StackOp::Input => {
                let mut buf = String::new();
                let _ = std::io::stdin().read_line(&mut buf);
                stack.push(str::parse::<f64>(&buf.trim()).unwrap().to_bits());
            }
        }
    }
}

#[test_case("
print 3;
"; "simple print statement")]
#[test_case("
print 03.06;
"; "decimal print statement")]
#[test_case("
let a = 3;
print a;
"; "simple let statement")]
#[test_case("
let a = 3;
let b = a;
print b;
"; "double let statement")]
#[test_case("
let a = 3;
let b = a;
print b;
print a;
"; "double print statement")]
#[test_case("
print 3 + 2;
"; "simple add expression")]
#[test_case("
print 1 + 2 + 3 + 4;
"; "quadruple add expression")]
#[test_case("
let a = 3;
let b = a;
print a + b;
"; "add var to itself")]
#[test_case("
let a = 3;
a = 4;
print a;
"; "simple mut statement")]
#[test_case("
let a = 1;
a = a + a;
print a;
"; "self-incrementing")]
#[test_case("
if true {
    print 5;
}
if false {
    print 6;
}
"; "simple if expression")]
#[test_case("
if false {
    let a = 3;
}
let b = 5;
let c = 7;
print a;
"; "let in if")]
#[test_case("
print input + 1;
"; "echo input")]
pub fn run_prog_from_text(text: &str) {
    let stack = &mut Vec::new();
    run_stack_ops(
        stack,
        &prog_to_stack(&eval_program(&parse(&text)).unwrap()),
        false,
    )
}