rual-core 0.0.4

A slim, embeddable language
Documentation
use super::{Instruction, RlValue};
use crate::{
    expressions::{Expr, Value},
    lexing::Lexer,
    parsing::Parser,
    syntax::Punct,
};

#[cfg(test)]
mod tests;

pub(crate) fn compile(source: &str) -> Vec<Instruction> {
    let tokens = Lexer::from(source).lex_all().expect("Lexing failed");
    let expr = Parser::from(tokens).parse_all().expect("Parsing failed");

    compile_expr(&expr, source)
}

// string handling is shit
pub(crate) fn compile_expr(expr: &Expr, source: &str) -> Vec<Instruction> {
    let mk_str = |(start, end): &(usize, usize)| -> String {
        source.chars().skip(*start).take(*end - *start).collect()
    };

    let mut instrs = Vec::new();

    match expr {
        Expr::Multiple(exprs) => {
            for expr in exprs.iter() {
                instrs.append(&mut compile_expr(&expr, source));
            }
        }
        Expr::Declaration { name, val } => {
            instrs.append(&mut compile_expr(&val, source));
            instrs.push(Instruction::Declare(mk_str(name)));
        }
        // TODO: optimizations
        Expr::Rpn(exprs) => {
            return exprs
                .iter()
                .map(|expr| match expr {
                    Expr::Value(Value::Num(x)) => Instruction::Push(RlValue::Num(*x)),
                    Expr::IdentLookup(range) => Instruction::PushLocal(mk_str(range)),
                    Expr::Op(Punct::Add) => Instruction::Add,
                    Expr::Op(Punct::Sub) => Instruction::Sub,
                    Expr::Op(Punct::Mul) => Instruction::Mul,
                    _ => unimplemented!("{:?}", &expr),
                })
                .collect()
        }
        Expr::Value(Value::Num(x)) => instrs.push(Instruction::Push(RlValue::Num(*x))),
        Expr::Value(Value::Str((start, end))) => {
            instrs.push(Instruction::Push(RlValue::Str(mk_str(&(*start, *end)))));
        }
        Expr::Range { from, to } => {
            instrs.append(&mut compile_expr(&from, source));
            instrs.append(&mut compile_expr(&to, source));
            instrs.push(Instruction::MakeRange);
        }
        Expr::IdentLookup(range) => {
            instrs.push(Instruction::PushLocal(mk_str(range)));
        }
        Expr::FunctionDeclaration { name, params, body } => {
            let mut body = compile_expr(&body, source);

            // function body instructions are preceded by 2 instructions
            // - the function body address pointer
            // - the after-function-body address pointer
            // so that they don't execute initially
            let start = instrs.len() + 3;
            // also taking into consideration the `return` instruction
            let end = start + body.len() + params.len() + 1;

            instrs.push(Instruction::Push(RlValue::Function(
                start,
                params.len() as u8,
            )));
            instrs.push(Instruction::Push(RlValue::Addr(end)));
            instrs.push(Instruction::Jump);

            // the function args will already be on the stack
            instrs.append(
                &mut params
                    .iter()
                    .rev()
                    .map(|p| Instruction::Declare(mk_str(p)))
                    .collect(),
            );

            instrs.append(&mut body);
            instrs.push(Instruction::Ret);
            instrs.push(Instruction::Declare(mk_str(name)));
        }
        Expr::FunctionCall { name, args } => {
            let fn_name = mk_str(name);
            args.iter()
                .for_each(|arg| instrs.append(&mut compile_expr(arg, source)));

            let instr = match fn_name.as_ref() {
                "print" => Instruction::CallNative(fn_name),
                _ => Instruction::Call(fn_name),
            };

            instrs.push(instr);
        }
        _ => unimplemented!("{:?}", &expr),
    }

    instrs
}