glyph-runtime 0.0.1

Runtime execution engine for the Glyph programming language
Documentation
//! IR to bytecode compiler
//!
//! This module converts the intermediate representation (IR) into VM bytecode.

use crate::instruction::Instruction;
use crate::ir::*;
use glyph_types::Value;
use std::collections::HashMap;

/// IR to bytecode compiler
#[derive(Default)]
pub struct IrToBytecode {
    /// Map of function names to bytecode indices
    function_indices: HashMap<String, usize>,
    /// Map of block labels to instruction offsets within current function
    block_offsets: HashMap<String, usize>,
    /// Current function being compiled
    current_function: Vec<Instruction>,
    /// All compiled functions
    functions: Vec<Vec<Instruction>>,
}

impl IrToBytecode {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn compile(mut self, module: &IRModule) -> Vec<Vec<Instruction>> {
        // First pass: assign indices to functions
        let mut index = 0;

        // Main function gets index 0 if it exists
        if let Some(entry) = &module.entry_point {
            self.function_indices.insert(entry.clone(), index);
            index += 1;
        }

        // Other functions
        for name in module.functions.keys() {
            if Some(name) != module.entry_point.as_ref() {
                self.function_indices.insert(name.clone(), index);
                index += 1;
            }
        }

        // Second pass: compile each function
        if let Some(entry) = &module.entry_point {
            if let Some(func) = module.functions.get(entry) {
                self.compile_function(func);
            }
        }

        for (name, func) in &module.functions {
            if Some(name) != module.entry_point.as_ref() {
                self.compile_function(func);
            }
        }

        self.functions
    }

    fn compile_function(&mut self, func: &IRFunction) {
        self.current_function.clear();
        self.block_offsets.clear();

        // Add parameter binding instructions at the start
        // Parameters are pushed in order, so we bind them in reverse order
        for param in func.params.iter().rev() {
            self.current_function
                .push(Instruction::BindLocal(param.clone()));
        }

        // First pass: calculate block offsets
        let mut offset = func.params.len(); // Account for parameter bindings
        for block in &func.blocks {
            self.block_offsets.insert(block.label.clone(), offset);
            offset += block.instructions.len();
            offset += self.terminator_size(&block.terminator);
        }

        // Second pass: generate bytecode
        for block in &func.blocks {
            for inst in &block.instructions {
                self.compile_instruction(inst);
            }
            self.compile_terminator(&block.terminator);
        }

        // Add function to compiled list
        self.functions.push(self.current_function.clone());
    }

    fn compile_instruction(&mut self, inst: &IRInstruction) {
        match inst {
            IRInstruction::LoadConst(value) => {
                self.current_function.push(Instruction::Push(value.clone()));
            }
            IRInstruction::LoadVar(name) => {
                self.current_function
                    .push(Instruction::LoadLocal(name.clone()));
            }
            IRInstruction::StoreVar(name) => {
                self.current_function
                    .push(Instruction::BindLocal(name.clone()));
            }
            IRInstruction::BinaryOp(op) => {
                let vm_inst = match op {
                    BinaryOp::Add => Instruction::Add,
                    BinaryOp::Sub => Instruction::Sub,
                    BinaryOp::Mul => Instruction::Mul,
                    BinaryOp::Div => Instruction::Div,
                    BinaryOp::Mod => Instruction::Mod,
                    BinaryOp::Pow => Instruction::Pow,
                    BinaryOp::Eq => Instruction::Eq,
                    BinaryOp::Ne => Instruction::Ne,
                    BinaryOp::Lt => Instruction::Lt,
                    BinaryOp::Le => Instruction::Le,
                    BinaryOp::Gt => Instruction::Gt,
                    BinaryOp::Ge => Instruction::Ge,
                    BinaryOp::And => Instruction::And,
                    BinaryOp::Or => Instruction::Or,
                };
                self.current_function.push(vm_inst);
            }
            IRInstruction::UnaryOp(op) => {
                let vm_inst = match op {
                    UnaryOp::Neg => Instruction::Neg,
                    UnaryOp::Not => Instruction::Not,
                };
                self.current_function.push(vm_inst);
            }
            IRInstruction::Call {
                func,
                args_count: _,
            } => {
                if let Some(&index) = self.function_indices.get(func) {
                    self.current_function.push(Instruction::Call(index));
                } else {
                    // External function - use CallNative
                    self.current_function
                        .push(Instruction::CallNative(func.clone()));
                }
            }
            IRInstruction::CallIntrinsic {
                name,
                args_count: _,
            } => {
                // Map intrinsic names to capabilities
                let capability = match name.as_str() {
                    "voice.speak" => Some("audio.speak".to_string()),
                    "display.chart" => Some("display.chart".to_string()),
                    "display.image" => Some("display.image".to_string()),
                    "net.fetch" => Some("network.fetch".to_string()),
                    "wait.confirm" => Some("display.text".to_string()),
                    _ => None,
                };

                self.current_function.push(Instruction::CallIntrinsic {
                    name: name.clone(),
                    capability,
                });
            }
            IRInstruction::MakeList(count) => {
                self.current_function.push(Instruction::MakeList(*count));
            }
            IRInstruction::MakeDict(count) => {
                self.current_function.push(Instruction::MakeDict(*count));
            }
            IRInstruction::GetAttr(attr) => {
                self.current_function
                    .push(Instruction::GetAttr(attr.clone()));
            }
            IRInstruction::GetItem => {
                self.current_function.push(Instruction::GetIndex);
            }
            IRInstruction::CallMethod { name, argc: _ } => {
                // For now, methods are implemented as regular function calls
                // In a real implementation, we'd need proper method dispatch
                self.current_function
                    .push(Instruction::CallNative(name.clone()));
            }
            IRInstruction::Await => {
                self.current_function.push(Instruction::AwaitPromise);
            }
            IRInstruction::Dup => {
                self.current_function.push(Instruction::Dup);
            }
            IRInstruction::Pop => {
                self.current_function.push(Instruction::Pop);
            }
        }
    }

    fn compile_terminator(&mut self, term: &IRTerminator) {
        match term {
            IRTerminator::Return => {
                self.current_function.push(Instruction::Return);
            }
            IRTerminator::Jump(label) => {
                if let Some(&offset) = self.block_offsets.get(label) {
                    self.current_function.push(Instruction::Jump(offset));
                }
            }
            IRTerminator::JumpIf {
                then_block,
                else_block,
            } => {
                // Duplicate condition for both tests
                self.current_function.push(Instruction::Dup);

                if let Some(&then_offset) = self.block_offsets.get(then_block) {
                    self.current_function.push(Instruction::JumpIf(then_offset));
                }

                if let Some(&else_offset) = self.block_offsets.get(else_block) {
                    self.current_function.push(Instruction::Jump(else_offset));
                }
            }
            IRTerminator::Match { cases, default } => {
                // Pattern matching implementation
                // We need to preserve the subject value for variable patterns

                for (i, (pattern, label)) in cases.iter().enumerate() {
                    // For each case, we need to:
                    // 1. Duplicate the subject value (except for first case)
                    // 2. Test the pattern
                    // 3. If match, jump to case block with subject still on stack

                    if i > 0 {
                        // Duplicate the subject for next comparison
                        self.current_function.push(Instruction::Dup);
                    }

                    // Generate pattern matching code based on pattern type
                    match pattern {
                        IRPattern::Literal(value) => {
                            // For literal patterns: duplicate subject, push literal, compare
                            self.current_function.push(Instruction::Dup);
                            self.current_function.push(Instruction::Push(value.clone()));
                            self.current_function.push(Instruction::Eq);
                        }
                        IRPattern::Variable(_) => {
                            // Variable patterns always match, keep subject on stack
                            self.current_function
                                .push(Instruction::Push(Value::Bool(true)));
                        }
                        IRPattern::Wildcard => {
                            // Wildcard always matches, but doesn't need the value
                            self.current_function
                                .push(Instruction::Push(Value::Bool(true)));
                        }
                        IRPattern::Constructor { .. } => {
                            // TODO: Implement constructor pattern matching
                            self.current_function
                                .push(Instruction::Push(Value::Bool(false)));
                        }
                    }

                    if let Some(&offset) = self.block_offsets.get(label) {
                        self.current_function.push(Instruction::JumpIf(offset));
                    }

                    // Pop the subject value if this case didn't match
                    // (except for the last case, where we might need it for default)
                    if i < cases.len() - 1 || default.is_some() {
                        // The subject is still on the stack, ready for next pattern
                    } else {
                        // Last case and no default - pop the subject
                        self.current_function.push(Instruction::Pop);
                    }
                }

                // Default case or cleanup
                if let Some(default_label) = default {
                    if let Some(&offset) = self.block_offsets.get(default_label) {
                        self.current_function.push(Instruction::Jump(offset));
                    }
                } else if cases.is_empty() {
                    // No cases at all, just pop the subject
                    self.current_function.push(Instruction::Pop);
                }
            }
        }
    }

    fn terminator_size(&self, term: &IRTerminator) -> usize {
        match term {
            IRTerminator::Return => 1,
            IRTerminator::Jump(_) => 1,
            IRTerminator::JumpIf { .. } => 3, // Dup + JumpIf + Jump
            IRTerminator::Match { cases, default } => {
                let mut size = 0;
                for (i, (pattern, _)) in cases.iter().enumerate() {
                    if i > 0 {
                        size += 1; // Dup subject for next comparison
                    }
                    // Size depends on pattern type
                    match pattern {
                        IRPattern::Literal(_) => size += 4, // Dup + Push + Eq + JumpIf
                        IRPattern::Variable(_) | IRPattern::Wildcard => size += 2, // Push(true) + JumpIf
                        IRPattern::Constructor { .. } => size += 2, // Push(false) + JumpIf
                    }

                    // Pop after non-matching case (except last)
                    if i == cases.len() - 1 && default.is_none() {
                        size += 1; // Pop on last case if no default
                    }
                }
                if default.is_some() {
                    size += 1; // Jump to default
                } else if cases.is_empty() {
                    size += 1; // Pop if no cases
                }
                size
            }
        }
    }
}

/// Compile IR module to bytecode
pub fn ir_to_bytecode(module: &IRModule) -> Vec<Vec<Instruction>> {
    let compiler = IrToBytecode::new();
    compiler.compile(module)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_simple_function() {
        let mut module = IRModule {
            program: IRProgram {
                name: "test".to_string(),
                version: "1.0".to_string(),
                requires: vec![],
            },
            functions: HashMap::new(),
            entry_point: Some("main".to_string()),
        };

        let main_func = IRFunction {
            name: "main".to_string(),
            params: vec![],
            locals_count: 0,
            blocks: vec![IRBlock {
                label: "entry".to_string(),
                instructions: vec![IRInstruction::LoadConst(Value::Int(42))],
                terminator: IRTerminator::Return,
            }],
            is_async: false,
        };

        module.functions.insert("main".to_string(), main_func);

        let bytecode = ir_to_bytecode(&module);
        assert_eq!(bytecode.len(), 1);
        assert_eq!(bytecode[0].len(), 2);
        assert_eq!(bytecode[0][0], Instruction::Push(Value::Int(42)));
        assert_eq!(bytecode[0][1], Instruction::Return);
    }

    #[test]
    fn test_arithmetic() {
        let mut module = IRModule {
            program: IRProgram {
                name: "test".to_string(),
                version: "1.0".to_string(),
                requires: vec![],
            },
            functions: HashMap::new(),
            entry_point: Some("main".to_string()),
        };

        let main_func = IRFunction {
            name: "main".to_string(),
            params: vec![],
            locals_count: 0,
            blocks: vec![IRBlock {
                label: "entry".to_string(),
                instructions: vec![
                    IRInstruction::LoadConst(Value::Int(10)),
                    IRInstruction::LoadConst(Value::Int(5)),
                    IRInstruction::BinaryOp(BinaryOp::Add),
                ],
                terminator: IRTerminator::Return,
            }],
            is_async: false,
        };

        module.functions.insert("main".to_string(), main_func);

        let bytecode = ir_to_bytecode(&module);
        assert_eq!(bytecode[0][0], Instruction::Push(Value::Int(10)));
        assert_eq!(bytecode[0][1], Instruction::Push(Value::Int(5)));
        assert_eq!(bytecode[0][2], Instruction::Add);
        assert_eq!(bytecode[0][3], Instruction::Return);
    }
}