enginerenderer 0.0.1

A zero-dependency offline rendering engine in pure Rust — CPU path tracing, BVH acceleration, 16-band spectral rendering, PBR materials, animation & video export.
Documentation
use super::bytecode::{Instruction, Opcode};

pub fn compile_expr(src: &str) -> Result<Vec<Instruction>, String> {
    let tokens = tokenize(src)?;
    let mut pos = 0usize;
    let mut instrs = Vec::new();
    parse_expr(&tokens, &mut pos, &mut instrs)?;
    instrs.push(Instruction::new(Opcode::Halt, 0));
    Ok(instrs)
}

#[derive(Debug, Clone, PartialEq)]
enum Token {
    Number(i64),
    Float(f64),
    Plus,
    Minus,
    Star,
    Slash,
    LParen,
    RParen,
    EqEq,
    BangEq,
    Lt,
    Gt,
    LtEq,
    GtEq,
    Bang,
}

fn tokenize(src: &str) -> Result<Vec<Token>, String> {
    let mut tokens = Vec::new();
    let chars: Vec<char> = src.chars().collect();
    let mut i = 0;
    while i < chars.len() {
        match chars[i] {
            ' ' | '\t' | '\n' => {
                i += 1;
            }
            '+' => {
                tokens.push(Token::Plus);
                i += 1;
            }
            '-' => {
                tokens.push(Token::Minus);
                i += 1;
            }
            '*' => {
                tokens.push(Token::Star);
                i += 1;
            }
            '/' => {
                tokens.push(Token::Slash);
                i += 1;
            }
            '(' => {
                tokens.push(Token::LParen);
                i += 1;
            }
            ')' => {
                tokens.push(Token::RParen);
                i += 1;
            }
            '=' if chars.get(i + 1) == Some(&'=') => {
                tokens.push(Token::EqEq);
                i += 2;
            }
            '!' if chars.get(i + 1) == Some(&'=') => {
                tokens.push(Token::BangEq);
                i += 2;
            }
            '<' if chars.get(i + 1) == Some(&'=') => {
                tokens.push(Token::LtEq);
                i += 2;
            }
            '>' if chars.get(i + 1) == Some(&'=') => {
                tokens.push(Token::GtEq);
                i += 2;
            }
            '<' => {
                tokens.push(Token::Lt);
                i += 1;
            }
            '>' => {
                tokens.push(Token::Gt);
                i += 1;
            }
            '!' => {
                tokens.push(Token::Bang);
                i += 1;
            }
            '0'..='9' | '.' => {
                let start = i;
                let mut is_float = false;
                while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '.') {
                    if chars[i] == '.' {
                        is_float = true;
                    }
                    i += 1;
                }
                let s: String = chars[start..i].iter().collect();
                if is_float {
                    tokens.push(Token::Float(s.parse::<f64>().map_err(|e| e.to_string())?));
                } else {
                    tokens.push(Token::Number(s.parse::<i64>().map_err(|e| e.to_string())?));
                }
            }
            c => return Err(format!("unexpected character: {}", c)),
        }
    }
    Ok(tokens)
}

fn parse_expr(
    tokens: &[Token],
    pos: &mut usize,
    instrs: &mut Vec<Instruction>,
) -> Result<(), String> {
    parse_comparison(tokens, pos, instrs)
}

fn parse_comparison(
    tokens: &[Token],
    pos: &mut usize,
    instrs: &mut Vec<Instruction>,
) -> Result<(), String> {
    parse_additive(tokens, pos, instrs)?;
    while *pos < tokens.len() {
        match tokens[*pos] {
            Token::EqEq => {
                *pos += 1;
                parse_additive(tokens, pos, instrs)?;
                instrs.push(Instruction::new(Opcode::Eq, 0));
            }
            Token::BangEq => {
                *pos += 1;
                parse_additive(tokens, pos, instrs)?;
                instrs.push(Instruction::new(Opcode::Ne, 0));
            }
            Token::Lt => {
                *pos += 1;
                parse_additive(tokens, pos, instrs)?;
                instrs.push(Instruction::new(Opcode::Lt, 0));
            }
            Token::Gt => {
                *pos += 1;
                parse_additive(tokens, pos, instrs)?;
                instrs.push(Instruction::new(Opcode::Gt, 0));
            }
            Token::LtEq => {
                *pos += 1;
                parse_additive(tokens, pos, instrs)?;
                instrs.push(Instruction::new(Opcode::Le, 0));
            }
            Token::GtEq => {
                *pos += 1;
                parse_additive(tokens, pos, instrs)?;
                instrs.push(Instruction::new(Opcode::Ge, 0));
            }
            _ => break,
        }
    }
    Ok(())
}

fn parse_additive(
    tokens: &[Token],
    pos: &mut usize,
    instrs: &mut Vec<Instruction>,
) -> Result<(), String> {
    parse_multiplicative(tokens, pos, instrs)?;
    while *pos < tokens.len() {
        match tokens[*pos] {
            Token::Plus => {
                *pos += 1;
                parse_multiplicative(tokens, pos, instrs)?;
                instrs.push(Instruction::new(Opcode::Add, 0));
            }
            Token::Minus => {
                *pos += 1;
                parse_multiplicative(tokens, pos, instrs)?;
                instrs.push(Instruction::new(Opcode::Sub, 0));
            }
            _ => break,
        }
    }
    Ok(())
}

fn parse_multiplicative(
    tokens: &[Token],
    pos: &mut usize,
    instrs: &mut Vec<Instruction>,
) -> Result<(), String> {
    parse_primary(tokens, pos, instrs)?;
    while *pos < tokens.len() {
        match tokens[*pos] {
            Token::Star => {
                *pos += 1;
                parse_primary(tokens, pos, instrs)?;
                instrs.push(Instruction::new(Opcode::Mul, 0));
            }
            Token::Slash => {
                *pos += 1;
                parse_primary(tokens, pos, instrs)?;
                instrs.push(Instruction::new(Opcode::Div, 0));
            }
            _ => break,
        }
    }
    Ok(())
}

fn parse_primary(
    tokens: &[Token],
    pos: &mut usize,
    instrs: &mut Vec<Instruction>,
) -> Result<(), String> {
    if *pos >= tokens.len() {
        return Err("unexpected end of expression".to_string());
    }
    match &tokens[*pos] {
        Token::Number(n) => {
            instrs.push(Instruction::new(Opcode::Push, *n));
            *pos += 1;
        }
        Token::Float(f) => {
            instrs.push(Instruction::new(Opcode::PushFloat, f.to_bits() as i64));
            *pos += 1;
        }
        Token::Minus => {
            *pos += 1;
            parse_primary(tokens, pos, instrs)?;
            instrs.push(Instruction::new(Opcode::Neg, 0));
        }
        Token::Bang => {
            *pos += 1;
            parse_primary(tokens, pos, instrs)?;
            instrs.push(Instruction::new(Opcode::Not, 0));
        }
        Token::LParen => {
            *pos += 1;
            parse_expr(tokens, pos, instrs)?;
            if *pos >= tokens.len() || tokens[*pos] != Token::RParen {
                return Err("expected closing parenthesis".to_string());
            }
            *pos += 1;
        }
        t => return Err(format!("unexpected token: {:?}", t)),
    }
    Ok(())
}