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:
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))
+ */