brokkr

Bytecode virtual machine for Valhalla.
git clone git://git.knutsen.co/brokkr
Log | Files | Refs | README | LICENSE

commit d0da8b73ba305b16f669267cb208a962b6b0fc41
parent ba358a35bfa3587fecff3add83421426880723d9
Author: Demonstrandum <moi@knutsen.co>
Date:   Tue, 18 Feb 2020 15:18:01 +0000

Basic unmarshalling and call-frame construction.

Diffstat:
MBYTECODE.md | 58+++++++++++++++++++++++++++++-----------------------------
MCargo.toml | 9++++++++-
MREADME.md | 23+++++++++++++++++++++--
Mbytecode_spec.yaml | 25+++++++++++++++++++++++--
Mscripts/gen_bytecode_md_spec.rb | 6++++--
Asrc/bin.rs | 27+++++++++++++++++++++++++++
Msrc/lib.rs | 18+++++++++++++++---
Asrc/vm/frame.rs | 23+++++++++++++++++++++++
Asrc/vm/instructions.rs | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/vm/mod.rs | 5+++++
Asrc/vm/unmarshal.rs | 222+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 441 insertions(+), 39 deletions(-)

diff --git a/BYTECODE.md b/BYTECODE.md @@ -10,32 +10,32 @@ Sizes are as follows: ## Bytecodes | Byte | Name | Operands | Description | |:---:|:---:|---|---| -| `00000000` | `HALT` | 1 — Exit Code (Integer) | Stops execution. | -| `00000001` | `PUSH_CONST` | 1 — Unsigned index to constants table | Pushes a constant onto the stack. Constant is specified by a given index to the constant-table, containing all local constant. | -| `00000010` | `PUSH_LOCAL` | 1 — Local variable index (Unsigned) | Pushes a local variable (e.g. a function param) onto the stack. The local variable is identified by an index operand, to where it is in the current symbol table. | -| `00000011` | `PUSH_SUPER` | 1 — Name of super-scoped variable (String) | When a variable is out of the local scope, load it onto the stack by searching up the super-scope by its name, (much slower than `PUSH_LOCAL`). | -| `00000100` | `POP` | | Pops the top element off of the stack. | -| `00000101` | `STORE_LOCAL` | 1 — Index to store local variable in table (Unsigned) | Pops the top value and stores it in the local symbol table at a given index. | -| `00000110` | `STORE_SUPER` | 1 — Name of super-scoped variable (String) | When a variable is out of the local scope, pop it off of the stack to store it in the super-scope searching up the frames for its name, (much slower than `STORE_LOCAL`). | -| `00000110` | `DUP` | | Duplicates what was on the top of the stack, by popping it off and then pushing two copies. | -| `00000111` | `DUP_N` | 1 — The number of duplicates | Duplicates the top value on the stack N times. | -| `00001000` | `SWAP` | | Swaps the position of the top two stack elements. | -| `00101000` | `N_ADD` | | Addition between two pointer-sized unsigned integers. Pops top two elements from the stacks and adds them together, pushing the result. | -| `00101001` | `I_ADD` | | Addition between two pointer-sized signed integers. Pops top two elements from the stacks and adds them together, pushing the result. | -| `00101010` | `R_ADD` | | Addition between two 64-bit floating-point numbers. Pops top two elements from the stacks and adds them together, pushing the result. | -| `00101011` | `U_ADD` | | Addition between two values of unknown types, found out at runtime. Pops top two elements from the stacks and adds them together, pushing the result. | -| `00101100` | `CONCAT` | | Works like add, but concatenates strings. | -| `00101101` | `N_SUB` | | Subtraction between two pointer-sized unsigned integers. Pops top two elements from the stacks and subtracts them together, pushing the result. | -| `00101110` | `I_SUB` | | Subtraction between two pointer-sized signed integers. Pops top two elements from the stacks and subtracts them together, pushing the result. | -| `00101111` | `R_SUB` | | Subtraction between two 64-bit floating-point numbers. Pops top two elements from the stacks and subtracts them together, pushing the result. | -| `00110000` | `U_SUB` | | Subtraction between two values of unknown types, found out at runtime. Pops top two elements from the stacks and subtracts one from the other, pushing the result. | -| `00110001` | `N_MUL` | | Multiplication between two pointer-sized unsigned integers. Pops top two elements from the stacks and multiplies them together, pushing the result. | -| `00110010` | `I_MUL` | | Multiplication between two pointer-sized signed integers. Pops top two elements from the stacks and multiplies them together, pushing the result. | -| `00110011` | `R_MUL` | | Multiplication between two 64-bit floating-point numbers. Pops top two elements from the stacks and multiplies them together, pushing the result. | -| `00110100` | `U_MUL` | | Multiplication between two values of unknown types, found out at runtime. Pops top two elements from the stacks and multiplies them together, pushing the result. | -| `00110101` | `N_DIV` | | Division between two pointer-sized unsigned integers. Pops top two elements from the stacks and finds their quotient, pushing the result. | -| `00110110` | `I_DIV` | | Division between two pointer-sized signed integers. Pops top two elements from the stacks and finds their quotient, pushing the result. | -| `00110111` | `R_DIV` | | Division between two 64-bit floating-point numbers. Pops top two elements from the stacks and finds their quotient, pushing the result. | -| `00111000` | `U_DIV` | | Division between two values of unknown types, found out at runtime. Pops top two elements from the stacks and finds their quotient, pushing the result. | -| `11111110` | `SET_LINE` | 1 — Current line number, given directly as `u16`. Operand value is line number. | Sets the current line number that the subsequent bytecode instructions correspond to in the code source file. | -| `11111111` | `NOP` | | <u>N</u>o <u>Op</u>eration. Does nothing. You shouldn't see this in the final compiled bytecode, it may only exists temporarily while the bytecode is being produced. | +| `0xc8` | `HALT` | 1 — Exit Code (Integer) | Stops execution. | +| `0x01` | `PUSH_CONST` | 1 — Unsigned index to constants table | Pushes a constant onto the stack. Constant is specified by a given index to the constant-table, containing all local constant. | +| `0x02` | `PUSH_LOCAL` | 1 — Local variable index (Unsigned) | Pushes a local variable (e.g. a function param) onto the stack. The local variable is identified by an index operand, to where it is in the current symbol table. | +| `0x03` | `PUSH_SUPER` | 1 — Name of super-scoped variable (String) | When a variable is out of the local scope, load it onto the stack by searching up the super-scope by its name, (much slower than `PUSH_LOCAL`). | +| `0x04` | `POP` | | Pops the top element off of the stack. | +| `0x05` | `STORE_LOCAL` | 1 — Index to store local variable in table (Unsigned) | Pops the top value and stores it in the local symbol table at a given index. | +| `0x06` | `STORE_SUPER` | 1 — Name of super-scoped variable (String) | When a variable is out of the local scope, pop it off of the stack to store it in the super-scope searching up the frames for its name, (much slower than `STORE_LOCAL`). | +| `0x06` | `DUP` | | Duplicates what was on the top of the stack, by popping it off and then pushing two copies. | +| `0x07` | `DUP_N` | 1 — The number of duplicates | Duplicates the top value on the stack N times. | +| `0x08` | `SWAP` | | Swaps the position of the top two stack elements. | +| `0x28` | `N_ADD` | | Addition between two pointer-sized unsigned integers. Pops top two elements from the stacks and adds them together, pushing the result. | +| `0x29` | `I_ADD` | | Addition between two pointer-sized signed integers. Pops top two elements from the stacks and adds them together, pushing the result. | +| `0x2a` | `R_ADD` | | Addition between two 64-bit floating-point numbers. Pops top two elements from the stacks and adds them together, pushing the result. | +| `0x2b` | `U_ADD` | | Addition between two values of unknown types, found out at runtime. Pops top two elements from the stacks and adds them together, pushing the result. | +| `0x2c` | `CONCAT` | | Works like add, but concatenates strings. | +| `0x2d` | `N_SUB` | | Subtraction between two pointer-sized unsigned integers. Pops top two elements from the stacks and subtracts them together, pushing the result. | +| `0x2e` | `I_SUB` | | Subtraction between two pointer-sized signed integers. Pops top two elements from the stacks and subtracts them together, pushing the result. | +| `0x2f` | `R_SUB` | | Subtraction between two 64-bit floating-point numbers. Pops top two elements from the stacks and subtracts them together, pushing the result. | +| `0x30` | `U_SUB` | | Subtraction between two values of unknown types, found out at runtime. Pops top two elements from the stacks and subtracts one from the other, pushing the result. | +| `0x31` | `N_MUL` | | Multiplication between two pointer-sized unsigned integers. Pops top two elements from the stacks and multiplies them together, pushing the result. | +| `0x32` | `I_MUL` | | Multiplication between two pointer-sized signed integers. Pops top two elements from the stacks and multiplies them together, pushing the result. | +| `0x33` | `R_MUL` | | Multiplication between two 64-bit floating-point numbers. Pops top two elements from the stacks and multiplies them together, pushing the result. | +| `0x34` | `U_MUL` | | Multiplication between two values of unknown types, found out at runtime. Pops top two elements from the stacks and multiplies them together, pushing the result. | +| `0x35` | `N_DIV` | | Division between two pointer-sized unsigned integers. Pops top two elements from the stacks and finds their quotient, pushing the result. | +| `0x36` | `I_DIV` | | Division between two pointer-sized signed integers. Pops top two elements from the stacks and finds their quotient, pushing the result. | +| `0x37` | `R_DIV` | | Division between two 64-bit floating-point numbers. Pops top two elements from the stacks and finds their quotient, pushing the result. | +| `0x38` | `U_DIV` | | Division between two values of unknown types, found out at runtime. Pops top two elements from the stacks and finds their quotient, pushing the result. | +| `0xfe` | `SET_LINE` | 1 — Current line number, given directly as `u16`. Operand value is line number. | Sets the current line number that the subsequent bytecode instructions correspond to in the code source file. | +| `0xff` | `NOP` | | <u>N</u>o <u>Op</u>eration. Does nothing. You shouldn't see this in the final compiled bytecode, it may only exists temporarily while the bytecode is being produced. | diff --git a/Cargo.toml b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "brokkr" description = "Virtual Machine / Bytecode Interpreter for set-theoretic languages." -homepage = "https://knutsen.co" +homepage = "https://github.com/Demonstrandum/brokkr" repository = "https://github.com/Demonstrandum/brokkr" documentation = "https://github.com/Demonstrandum/brokkr" keywords = ["set-theory", "assembler", "bytecode", "interpreter", "virtual-machine"] @@ -16,6 +16,13 @@ edition = "2018" name = "brokkr" path = "src/lib.rs" + +[[bin]] +name = "brokkr" +path = "src/bin.rs" + [dependencies] lazy_static = "1.3.0" +num-traits = "0.2" +num-derive = "0.3" diff --git a/README.md b/README.md @@ -2,5 +2,24 @@ VM for executing compiled bytecode. The custom VM of the Valhalla Set-based Programming Language. +## Compile + +In the root of this repository, in your shell, you may write: + +```sh +cargo +nightly run [compiled-bytecode-file] +``` + +If you also have the [`valhallac`](https://github.com/valhalla-lang/valhallac) compiler cloned, you may us it to compile the `test_source.vh` file, and then run this here interpreter on the file to be unmarshalled and executed, e.g. + +```sh +cargo +nightly run ../valhallac/test_source +``` + +### Why Nightly? + +Currently, the source uses the `#![feature(const_generics)]` feature, for some function which casts an array-slice to a fixed-size type array. It’s not really worth it, so either I wait until it becomes a proper feature, or I’ll soon remove it. + ## Bytecode Reference -See the [BYTECODE.md file](BYTECODE.md).- \ No newline at end of file + +See the [BYTECODE.md file](BYTECODE.md) (Might not always be up-to-date).+ \ No newline at end of file diff --git a/bytecode_spec.yaml b/bytecode_spec.yaml @@ -6,6 +6,15 @@ # ... - :byte: 0 + :name: EOI + :operands: 0 + :desc: >- + End Of Instructions! This is not a real opcode and + only appears at the end of the instruction byte-stream + to signal that there are no more instructions to be read. + Essentially it's just a null-terminator. + +- :byte: 200 :name: HALT :operands: 1 :operand_desc: ['Exit Code (Integer)'] @@ -86,6 +95,19 @@ :desc: >- Swaps the position of the top two stack elements. +- :byte: 9 + :name: CALL_1 + :operands: 0 + :desc: >- + Calls function at top of stack with argument at second + from top of stack. + +- :byte: 10 + :name: CHECK_TYPE + :operands: 0 + :desc: >- + Checks if second from top of stack value is a member + of the set/type loaded from the top of the stack. - :byte: 40 :name: N_ADD @@ -238,4 +260,4 @@ <u>N</u>o <u>Op</u>eration. Does nothing. You shouldn't see this in the final compiled bytecode, it may only exists temporarily while the - bytecode is being produced.- \ No newline at end of file + bytecode is being produced. diff --git a/scripts/gen_bytecode_md_spec.rb b/scripts/gen_bytecode_md_spec.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + require 'yaml' def wrap(arr) @@ -11,7 +13,7 @@ rows = Array.new LIST.each do |row| fields = Array.new - fields << "`#{row[:byte].to_s(2).rjust(8, '0')}`" + fields << "`0x#{row[:byte].to_s(16).rjust(2, '0')}`" fields << "`#{row[:name].upcase}`" operand_desc = Array.new @@ -21,7 +23,7 @@ LIST.each do |row| fields << operand_desc.join('<br />') fields << row[:desc] - + rows << (wrap fields) end diff --git a/src/bin.rs b/src/bin.rs @@ -0,0 +1,27 @@ +use brokkr::vm::unmarshal; + +use std::env; +use std::io::prelude::*; +use std::{fs::File, path::Path}; + +pub fn main() -> Result<(), std::io::Error> { + let mut args : Vec<String> = env::args().collect(); + args.remove(0); + + let files = args.iter().filter(|arg| Path::new(arg).exists()); + + for file in files { + #[cfg(debug_assertions)] + println!("Reading file {}...", file); + + let mut f = File::open(file) + .expect("Could not open binary for reading."); + let mut buffer = Vec::new(); + f.read_to_end(&mut buffer) + .expect("Could not dump file contents to bytesteam."); + + let frame = unmarshal::parse_blob(&buffer); + } + + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs @@ -1,2 +1,15 @@ -mod assembler; -mod vm;- \ No newline at end of file +#![allow(incomplete_features)] +#![feature(const_generics)] + #![warn( + clippy::all, + clippy::pedantic, + )] +#![allow(clippy::needless_return)] + +use std::ffi::c_void; +type VoidPtr = *const c_void; + +pub mod assembler; +pub mod vm; + +pub fn main() { std::process::exit(0); } diff --git a/src/vm/frame.rs b/src/vm/frame.rs @@ -0,0 +1,23 @@ +use std::collections::HashSet; +use crate::VoidPtr; + +/// Frame on the call-stack +#[derive(Debug)] +pub struct Frame { + /// Path for non-compiled source file. + pub source_file : String, + /// Name for the frame, i.e. the module name. + pub name : String, + /// Vector of all constants used in the _function_. + pub constants : Vec<VoidPtr>, + /// Set of names for local variables. + pub locals : HashSet<String>, + /// Instructions for execution of the _function_. + pub instructions : Vec<usize>, + + /// Maximum depth for the evaluation-stack. + pub stack_depth : u16, + /// Evaluation-stack (since it is a stack based VM). + pub evaluations : Vec<VoidPtr> +} + diff --git a/src/vm/instructions.rs b/src/vm/instructions.rs @@ -0,0 +1,64 @@ +use num_derive::FromPrimitive; + +#[repr(usize)] +#[allow(non_camel_case_types)] +#[derive(FromPrimitive, Clone, Copy)] +pub enum Operators { + EOI = 0, // TAKES 0 OPERAND(s) (Not a proper operator) + + PUSH_CONST = 1, // TAKES 1 OPERAND(s) + PUSH_LOCAL = 2, // TAKES 1 OPERAND(s) + PUSH_SUPER = 3, // TAKES 1 OPERAND(s) + POP = 4, // TAKES 0 OPERAND(s) + STORE_LOCAL = 5, // TAKES 1 OPERAND(s) + DUP = 6, // TAKES 0 OPERAND(s) + DUP_N = 7, // TAKES 1 OPERAND(s) + SWAP = 8, // TAKES 0 OPERAND(s) + CALL_1 = 9, // TAKES 0 OPERAND(s) + CHECK_TYPE = 10, // TAKES 0 OPERAND(s) + CAST = 11, // TAKES 2 OPERAND(s) (2 operands, 1 out of 2 bytes for each) + MAKE_FUNC = 12, // TAKES 0 OPERAND(s) + YIELD = 13, // TAKES 0 OPERAND(s) + RAW_PRINT = 14, // TAKES 0 OPERAND(s) + + N_ADD = 40, // TAKES 0 OPERAND(s) + I_ADD = 41, // TAKES 0 OPERAND(s) + R_ADD = 42, // TAKES 0 OPERAND(s) + U_ADD = 43, // TAKES 0 OPERAND(s) + CONCAT = 44, // TAKES 0 OPERAND(s) + N_SUB = 45, // TAKES 0 OPERAND(s) + I_SUB = 46, // TAKES 0 OPERAND(s) + R_SUB = 47, // TAKES 0 OPERAND(s) + U_SUB = 48, // TAKES 0 OPERAND(s) + N_MUL = 49, // TAKES 0 OPERAND(s) + I_MUL = 50, // TAKES 0 OPERAND(s) + R_MUL = 51, // TAKES 0 OPERAND(s) + U_MUL = 52, // TAKES 0 OPERAND(s) + N_DIV = 53, // TAKES 0 OPERAND(s) + I_DIV = 54, // TAKES 0 OPERAND(s) + R_DIV = 55, // TAKES 0 OPERAND(s) + U_DIV = 56, // TAKES 0 OPERAND(s) + + HALT = 200, // TAKES 1 OPERAND(s) + + // Misc- / Meta-codes + SET_LINE = 254, // TAKES 1 OPERAND(s) + NOP = 255, // TAKES 0 OPERAND(s) +} + +impl Operators { + #[must_use] + pub fn takes_operand(self) -> bool { + match self { + Self::HALT + | Self::PUSH_CONST + | Self::PUSH_LOCAL + | Self::PUSH_SUPER + | Self::STORE_LOCAL + | Self::DUP_N + | Self::CAST + | Self::SET_LINE => true, + _ => false + } + } +} diff --git a/src/vm/mod.rs b/src/vm/mod.rs @@ -0,0 +1,5 @@ +pub mod instructions; +pub mod frame; +pub mod unmarshal; + + diff --git a/src/vm/unmarshal.rs b/src/vm/unmarshal.rs @@ -0,0 +1,222 @@ +/*! + * # Parse/unmarshal bytecode compiled blobs. + * + * Files get directly parsed into stack frames, using an abundance + * of pre-existing code in this project, forming them into well-understood + * data structures, that are used throughout the program. + * + * The immediate next step from here is to pass the frames to the VM + * and have it follow the instructions byte-for-byte. + * +!*/ +use std::collections::HashSet; +use crate::VoidPtr; +use super::instructions; +use super::frame; + +use num_traits::FromPrimitive; + +const POINTER_BYTES : usize = std::mem::size_of::<usize>(); + +type Bytes = Vec<u8>; +type ByteSlice = [u8]; + +#[inline] +fn fix_slice_size<T, const N : usize>(slice : &[T]) -> &[T; N] { + let ptr = slice.as_ptr() as *const [T; N]; + unsafe { &*ptr } +} + +/// Functions that consume byte by byte, to reconstruct a code block. +mod eat { + use super::*; + + /// Consume a null terminated string. + pub fn null_string(mut i : usize, bytes : &ByteSlice) -> (usize, String) { + let mut string : Bytes = vec![]; + while bytes[i] != 0x00 { + string.push(bytes[i]); + i += 1; + } // Trust these are valid bytes. + let string = std::str::from_utf8(&string) + .expect("Invalid utf8 bytes in null-terminated string. Bad bytecode.") + .to_owned(); + + return (i + 1, string); + } + + fn consume_sized(mut i : usize, bytes : &ByteSlice) -> (usize, Bytes) { + let size = bytes[i] as usize; + i += 1; + + let mut padded = vec![0_u8; POINTER_BYTES]; + let slice = bytes[i..i + size].to_owned(); + for j in 0..size { + padded[POINTER_BYTES - j - 1] = slice[size - j - 1]; + } + + (i + size, padded) + } + + fn constant(mut i : usize, bytes : &ByteSlice) -> (usize, VoidPtr) { + let const_type = bytes[i]; + i += 1; + return match const_type { + // Parse number-types + 0x01..=0x03 => { + let (i, bytes_slice) = consume_sized(i, bytes); + let bytes_slice = fix_slice_size::<u8, POINTER_BYTES>(&bytes_slice[..POINTER_BYTES]); + let value = usize::from_be_bytes(*bytes_slice) as VoidPtr; + (i, value) + }, + // Parse Strings + 0x04 => { + let (i, bytes_slice) = consume_sized(i, bytes); + let bytes_slice = fix_slice_size::<u8, POINTER_BYTES>(&bytes_slice[..POINTER_BYTES]); + let str_len = usize::from_be_bytes(*bytes_slice); + + // Don't deallocate the string by wrapping it in a `Box`, then + // casting it to a raw pointer and then to *void. + let string = Box::new(std::str::from_utf8(&bytes[i..i + str_len]) + .expect("Invalid utf8 bytes in string. Bad bytecode.")); + let string = Box::into_raw(string); // Shadowed... + // String is then accessed by doing: + // `unsafe { &*(frame.constants[2] as *const &str) }` + (i + str_len, string as *const _ as VoidPtr) + } + _ => panic!(format!( + "Type-specifier-prefix ({:x}) is not recognised.", + const_type)) + } + } + + pub fn constants(mut i : usize, bytes : &ByteSlice) -> (usize, Vec<VoidPtr>) { + // Constant blocks are expected to start with `0x11`. + #[cfg(debug_assertions)] + assert_eq!(bytes[i], 0x11); + i += 1; + + let mut consts : Vec<VoidPtr> = vec![]; + while bytes[i] != 0x00 { + let (j, void) = constant(i, bytes); + i = j; + consts.push(void); + } + return (i + 1, consts); + + } + + /// Parse local variable names (null terminated strings). + pub fn locals(mut i : usize, bytes : &ByteSlice) -> (usize, HashSet<String>) { + let mut set : HashSet<String> = HashSet::new(); + #[cfg(debug_assertions)] + assert_eq!(bytes[i], 0x12); + i += 1; + + while bytes[i] != 0x00 { // Read strings until end of block. + let (j, local) = eat::null_string(i, bytes); + set.insert(local); + i = j; + } + + (i + 1, set) + } + + pub fn instructions(mut i : usize, bytes : &ByteSlice) -> (usize, Vec<usize>) { + let mut instrs : Vec<usize> = vec![]; + #[cfg(debug_assertions)] + assert_eq!(bytes[i], 0x13); + i += 1; + + while bytes[i] != 0x00 { + instrs.push(bytes[i] as usize); + let maybe_instr : Option<instructions::Operators> = + FromPrimitive::from_usize(bytes[i] as usize); + if let Some(instr) = maybe_instr { + // If the opcode takes an operand (u16), consume this too. + if instr.takes_operand() { + i += 2; + let operand = (u16::from(bytes[i - 1]) << 8) + + u16::from(bytes[i]); + instrs.push(operand as usize); + } + } + i += 1; + } + + (i, instrs) + } + + /// Parse whole code-block. + pub fn block(i : usize, bytes : &ByteSlice) -> (usize, frame::Frame) { + // Parse source filename. + let (i, filename) = eat::null_string(i, bytes); + // Parse module name. + let (i, module) = eat::null_string(i, bytes); + // Parse max evaluation-stack depth. + let stack_depth = (u16::from(bytes[i]) << 8) + u16::from(bytes[i + 1]); + let i = i + 2; + // Parse constants. + let (i, constants) = eat::constants(i, bytes); + // Parse locals. + let (i, locals) = eat::locals(i, bytes); + // Parse instructions. + let (i, instructions) = eat::instructions(i, bytes); + + // Construct call-frame. + let stack_frame = frame::Frame { + source_file: filename, + name: module, + constants, + locals, + instructions, + + stack_depth, + evaluations: Vec::with_capacity(stack_depth as usize) + }; + return (i, stack_frame); + } +} + +#[must_use] +pub fn parse_blob(bytes : &ByteSlice) -> frame::Frame { + let mut i : usize = 0; + // Parse compiler version number. + let version = bytes[0..2].as_ref(); + i += 3; + + // Parse primary/root code block. + let (_, stack_frame) = eat::block(i, bytes); + + #[cfg(debug_assertions)] + println!("{:#?}", stack_frame); + + // If `stack_frame.constants[2]` is a pointer to a string, then, to use + // it in Rust, all you have to do is: + // ``` + // let string : &str = unsafe { + // *(stack_frame.constants[2] as *const &str) + // }; + // println!("str: {}", string); + // ``` + + return stack_frame; +} + +/* === ROOT BLOB FORMAT ===: + * | VERSION [u8; 3] + * | === MARSHALLED CODE BLOCK FORMAT ===: + * | | source-filename [u8; x] (abs path, null terminated, utf8) + * | | module-name [u8; x] (null terminated, utf8) + * | | stack-depth [u8; 2] + * | | + * | | CONSTANTS [u8; x] (block begin: 0x11 byte) + * | | (can contain other marshalled code blocks) + * | | (block end: 0x00) + * | | LOCAL NAMES [u8; x] (block begin: 0x12) + * | | (contains null terminated strings) + * | | (block end: 0x00) + * | | INSTRUCTION CODES [u8; x] (block begin: 0x13) + * | | (contains stream of operators and operands) + * | | (block end: 0x00 (EOI)) + */