glowdust 0.0.1

A DBMS with a data model based on functions and pattern matching
Documentation
use crate::compiler::value::values::Value;
use crate::runtime::bytecode::OpCode::*;
use crate::runtime::bytecode::{instruction_length, OpCode};
use crate::runtime::compiled_script::Bytecode;
use std::collections::hash_map::Entry::Vacant;
use std::collections::HashMap;
use std::io::Result;
use std::io::Write;
use tracing::trace;

#[derive(Default)]
struct Disassembly {
    offset: usize,
    labels: HashMap<usize, String>,
    buffer: Vec<u8>,
}

fn simple_instruction(instruction: &str, context: &mut Disassembly) -> Result<()> {
    writeln!(context.buffer, "{:>15}", instruction)?;
    context.offset += 1;
    Ok(())
}

fn loop_instruction(
    instruction: &str,
    context: &mut Disassembly,
    bytecode: &Bytecode,
) -> Result<()> {
    let argument = bytecode.get(context.offset + 1) as usize;

    let label = context
        .labels
        .get(&(context.offset - argument + 2))
        .unwrap();
    writeln!(
        context.buffer,
        "{:>15}{:>8}{:>20}",
        instruction, argument, label
    )?;
    context.offset += 2;
    Ok(())
}

fn jump_instruction(
    instruction: &str,
    context: &mut Disassembly,
    bytecode: &Bytecode,
) -> Result<()> {
    let argument = bytecode.get(context.offset + 1) as usize;

    let label = context
        .labels
        .get(&(context.offset + argument + 2))
        .unwrap();
    writeln!(
        context.buffer,
        "{:>15}{:>8}{:>20}",
        instruction, argument, label
    )?;
    context.offset += 2;
    Ok(())
}

fn byte_instruction(
    instruction: &str,
    context: &mut Disassembly,
    bytecode: &Bytecode,
) -> Result<()> {
    let argument = bytecode.get(context.offset + 1);
    writeln!(context.buffer, "{:>15}{:>8}", instruction, argument)?;
    context.offset += 2;
    Ok(())
}

fn single_byte_constant_instruction(
    instruction: &str,
    context: &mut Disassembly,
    bytecode: &Bytecode,
    constants: &[Value],
) -> Result<()> {
    let argument = bytecode.get(context.offset + 1) as usize;
    let constant = &constants[argument];
    writeln!(
        context.buffer,
        "{:>15}{:>8}{:>20}",
        instruction, argument, constant
    )?;
    context.offset += 2;
    Ok(())
}

fn dump_instruction(
    instruction: u8,
    bytecode: &Bytecode,
    context: &mut Disassembly,
    constants: &[Value],
) -> Result<()> {
    match OpCode::from_byte(instruction) {
        OpNone => simple_instruction("OpNone", context),
        OpPop => simple_instruction("OpPop", context),
        OpConst => single_byte_constant_instruction("OpConst", context, bytecode, constants),
        OpTrue => simple_instruction("OpTrue", context),
        OpFalse => simple_instruction("OpFalse", context),
        OpMktuple => byte_instruction("OpMkTuple", context, bytecode),
        OpAdd => simple_instruction("OpAdd", context),
        OpSubtract => simple_instruction("OpSubtract", context),
        OpMultiply => simple_instruction("OpMultiply", context),
        OpDivide => simple_instruction("OpDivide", context),
        OpNegate => simple_instruction("OpNegate", context),
        OpAnd => simple_instruction("OpAnd", context),
        OpOr => simple_instruction("OpOr", context),
        OpNot => simple_instruction("OpNot", context),
        OpEquals => simple_instruction("OpEquals", context),
        OpGt => simple_instruction("OpGt", context),
        OpLt => simple_instruction("OpLt", context),
        OpReturn => simple_instruction("OpReturn", context),
        OpSetGlobal => byte_instruction("OpSetGlobal", context, bytecode),
        OpGetGlobal => byte_instruction("OpGetGlobal", context, bytecode),
        OpSetLocal => byte_instruction("OpSetLocal", context, bytecode),
        OpGetLocal => byte_instruction("OpGetLocal", context, bytecode),
        OpSetFunt => single_byte_constant_instruction("OpSetFunt", context, bytecode, constants),
        OpCall => byte_instruction("OpCall", context, bytecode),
        OpJump => jump_instruction("OpJump", context, bytecode),
        OpJumpIfFalse => jump_instruction("OpJumpIfFalse", context, bytecode),
        OpLoop => loop_instruction("OpLoop", context, bytecode),
        OpPull => byte_instruction("OpPull", context, bytecode),
        OpProduce => byte_instruction("OpProduce", context, bytecode),
        OpOpenCursor => byte_instruction("OpOpenCursor", context, bytecode),
        OpCloseCursor => byte_instruction("OpCloseCursor", context, bytecode),
        OpTupleGet => byte_instruction("OpTupleGet", context, bytecode),

        OpNewInstance => byte_instruction("OpNewInstance", context, bytecode),
        OpSetField => byte_instruction("OpSetField", context, bytecode),
        OpGetField => byte_instruction("OpGetField", context, bytecode),
    }
}

fn disassemble_instruction(
    bytecode: &Bytecode,
    context: &mut Disassembly,
    constants: &[Value],
) -> Result<()> {
    write!(context.buffer, "{:04}", context.offset)?;
    if context.offset > 0 && bytecode.lines[context.offset] == bytecode.lines[context.offset - 1] {
        write!(context.buffer, "{:>8} ", "|")?;
    } else {
        write!(context.buffer, "{:>8} ", bytecode.lines[context.offset])?;
    }
    dump_instruction(bytecode.get(context.offset), bytecode, context, constants)
}

fn disassemble_instruction_no_lines(
    bytecode: &Bytecode,
    context: &mut Disassembly,
    constants: &[Value],
) -> Result<()> {
    write!(context.buffer, "  {:04}   ", context.offset)?;
    dump_instruction(bytecode.get(context.offset), bytecode, context, constants)
}

pub fn disassemble(name: &str, bytecode: &Bytecode, constants: &[Value]) -> Result<()> {
    let mut context = Disassembly::default();

    writeln!(context.buffer, "== {} ==", name)?;
    writeln!(
        context.buffer,
        "{:<6}, {:<4}, {:>12}, {:>8}, {:>8}",
        "offset", "line", "opcode", "operand1", "constant1"
    )?;
    while context.offset < bytecode.len() {
        if let Some(label) = context.labels.get(&context.offset) {
            writeln!(context.buffer, "{}:", label)?;
        }
        disassemble_instruction(bytecode, &mut context, constants)?;
    }
    trace!("{}", String::from_utf8(context.buffer).unwrap());
    Ok(())
}

pub fn disassemble_no_lines(name: &str, bytecode: &Bytecode, constants: &[Value]) -> Result<()> {
    let mut context = Disassembly::default();
    labels(&bytecode.bytes, &mut context);
    writeln!(context.buffer, "== {} ==", name)?;
    writeln!(
        context.buffer,
        "{:>6}, {:>16}, {:>10}, {:>8}",
        "offset", "opcode", "operand", "constant"
    )?;
    while context.offset < bytecode.len() {
        if let Some(label) = context.labels.get(&context.offset) {
            writeln!(context.buffer, "{}:", label)?;
        }
        disassemble_instruction_no_lines(bytecode, &mut context, constants)?;
    }
    trace!("{}", String::from_utf8(context.buffer).unwrap());
    Ok(())
}

fn labels(bytecode: &[u8], context: &mut Disassembly) {
    let mut offset = 0;

    let mut loop_count = 0;
    let mut cond_jump_count = 0;
    let mut jump_count = 0;

    while offset < bytecode.len() {
        let current = OpCode::from_byte(bytecode[offset]);
        match current {
            OpLoop => {
                let target = offset - bytecode[offset + 1] as usize + 2_usize; // this could be OOB but it's ok to panic
                                                                               //                                                           ^^^ + 2 because it goes backwards
                                                                               // and when the command is executed, the IP has advanced + 1 for the instruction and another
                                                                               // +1 for the offset
                if let Vacant(e) = context.labels.entry(target) {
                    e.insert(format!("loop_{}", loop_count));
                    loop_count += 1;
                }
            }
            OpJumpIfFalse => {
                let target = offset + bytecode[offset + 1] as usize + 2; // this could be OOB but it's ok to panic
                if let Vacant(e) = context.labels.entry(target) {
                    e.insert(format!("cond_jump_{}", cond_jump_count));
                    cond_jump_count += 1;
                }
            }
            OpJump => {
                let target = offset + bytecode[offset + 1] as usize + 2; // this could be OOB but it's ok to panic
                if let Vacant(e) = context.labels.entry(target) {
                    e.insert(format!("jump_{}", jump_count));
                    jump_count += 1;
                }
            }
            _ => {}
        }
        offset += instruction_length(current);
    }
}