Skip to content
Snippets Groups Projects
Verified Commit 128a9930 authored by Louis's avatar Louis :fire:
Browse files

Add basic opcodes to VM, create VM based repl

parent 1274af0d
No related branches found
No related tags found
No related merge requests found
Pipeline #501 passed with stages
in 1 minute and 15 seconds
......@@ -33,6 +33,9 @@ dependencies = [
[[package]]
name = "forge-script"
version = "0.1.0"
dependencies = [
"forge-script-lang",
]
[[package]]
name = "forge-script-lang"
......
pub mod executor;
pub mod numbers;
pub mod value;
mod vm;
pub mod vm;
......@@ -3,6 +3,7 @@ use crate::parser::ast::LiteralNode;
use crate::runtime::numbers::Number;
use std::fmt::{Display, Formatter};
use std::ops::{Add, Div, Mul, Not, Rem, Sub};
use std::process::{ExitCode, Termination};
#[derive(Clone, Debug, Default)]
#[cfg_attr(
......@@ -123,6 +124,12 @@ impl ForgeValue {
}
}
impl Termination for ForgeValue {
fn report(self) -> ExitCode {
ExitCode::from(0)
}
}
impl Display for ForgeValue {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
......@@ -270,9 +277,9 @@ impl Add for ForgeValue {
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct UnsupportedOperation {
operation: BinaryOp,
left_type: &'static str,
right_type: &'static str,
pub operation: BinaryOp,
pub left_type: &'static str,
pub right_type: &'static str,
}
impl UnsupportedOperation {
......
use crate::runtime::value::ForgeValue;
use crate::runtime::vm::const_data::{ConstData, ConstDataRef};
use crate::runtime::vm::opcode::{OpCode, OpCodeError};
use crate::utilities::Clown;
use std::fmt::{Display, Formatter};
use std::ops::{Deref, DerefMut};
......@@ -53,18 +54,32 @@ impl Chunk {
}
}
pub fn op_return(&mut self) {
self.push_op(OpCode::Return);
}
pub fn op_constant(&mut self, data: ForgeValue) {
let idx = self.push_const_data(data);
self.push_op(OpCode::Constant(idx));
}
}
impl<'scope> Clown<Chunk> for ChunkRef<'scope> {
fn clown(&self) -> Chunk {
Chunk {
consts: self.consts.clown(),
code: Vec::from(self.code),
}
}
}
pub trait ChunkOps {
fn as_slice(&self) -> &[OpCode];
fn as_const_slice(&self) -> &[ForgeValue];
fn as_chunk(&self) -> Chunk {
ChunkRef {
code: self.as_slice(),
consts: self.as_const_slice().into(),
}
.clown()
}
fn write_chunk_data(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut offset = 0;
......@@ -80,6 +95,14 @@ pub trait ChunkOps {
write!(f, "[{:08x}] {} ", offset, opcode)?;
opcode.format_operand(f)?;
match opcode {
OpCode::Constant(idx) => {
write!(f, " {}", self.as_const_slice()[*idx])?;
}
_ => {}
}
writeln!(f, "")?;
offset = opcode.next_offset(offset);
......@@ -103,6 +126,9 @@ impl ChunkOps for Chunk {
fn as_slice(&self) -> &[OpCode] {
self.code.as_slice()
}
fn as_const_slice(&self) -> &[ForgeValue] {
self.consts.as_slice()
}
chunk_ops_generic!();
}
......@@ -110,6 +136,10 @@ impl<'scope> ChunkOps for ChunkRef<'scope> {
fn as_slice(&self) -> &[OpCode] {
self.code
}
fn as_const_slice(&self) -> &[ForgeValue] {
*self.consts
}
chunk_ops_generic!();
}
......@@ -141,10 +171,10 @@ mod _chunks {
fn const_chunk() {
let mut chunk = Chunk::default();
chunk.op_constant(ForgeValue::Null);
chunk.op_return();
chunk.push_op(OpCode::Return);
let expected = format!(
"== test ==\n[00000000] op_constant {:016x}\n[00000001] op_return \n",
"== test ==\n[00000000] op_constant {:016x} null\n[00000001] op_return \n",
0
);
......
use crate::runtime::value::ForgeValue;
use crate::utilities::Clown;
use std::ops::{Deref, DerefMut};
#[derive(Default)]
pub struct ConstData(Vec<ForgeValue>);
pub struct ConstDataRef<'scope>(&'scope [ForgeValue]);
impl<'scope> Clown<ConstData> for ConstDataRef<'scope> {
fn clown(&self) -> ConstData {
ConstData(self.0.to_vec())
}
}
impl From<Vec<ForgeValue>> for ConstData {
fn from(value: Vec<ForgeValue>) -> Self {
ConstData(value)
......
use crate::parser::ast::BinaryOp;
use crate::runtime::value::{ForgeValue, UnsupportedOperation};
use crate::runtime::vm::chunks::Chunk;
use crate::runtime::vm::{ChunkOps, OpCode};
use std::collections::VecDeque;
use std::fmt::{Display, Formatter};
const INITIAL_STACK: usize = 512;
pub struct Forge {
ip: usize,
sp: usize,
chunk: Chunk,
value_stack: VecDeque<ForgeValue>,
}
macro_rules! impl_binary_opcode {
($vm: expr, $op: expr) => {{
let (lhs, rhs) = $vm.pop_stack_binary_or_err()?;
$vm.push_stack($op(lhs, rhs)?);
}};
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum RuntimeErrorKind {
MissingEof,
InvalidConstData,
InvalidStackData,
UnsupportedOperation(BinaryOp),
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum VmError {
RuntimeError(RuntimeErrorKind),
CompilerError,
}
impl From<UnsupportedOperation> for VmError {
fn from(value: UnsupportedOperation) -> Self {
VmError::RuntimeError(RuntimeErrorKind::UnsupportedOperation(value.operation))
}
}
impl Display for VmError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::RuntimeError(kind) => write!(f, "{:?}", kind),
Self::CompilerError => write!(f, "Compiler Error"),
}
}
}
pub type VmResult = Result<ForgeValue, VmError>;
impl Forge {
fn next(&mut self) -> Option<OpCode> {
let code = self.chunk.as_slice().get(self.ip);
self.ip = self.ip.saturating_add(1);
code.copied()
}
/// Get a constant data value from the target chunk's constant section
fn get_const(&self, idx: usize) -> Option<&ForgeValue> {
self.chunk.as_const_slice().get(idx)
}
/// Adds a new value to the end of the value stack and returns the new
/// stack length
fn push_stack(&mut self, value: ForgeValue) -> usize {
self.value_stack.push_back(value);
self.value_stack.len()
}
/// Takes the value off of the end of the value stack and returns it, if
/// one exists
fn pop_stack(&mut self) -> Option<ForgeValue> {
self.value_stack.pop_back()
}
/// Examine the value at the end of the value stack without modifying the
/// stack
fn peek_stack(&self) -> Option<&ForgeValue> {
self.value_stack.back()
}
fn pop_stack_or_err(&mut self) -> VmResult {
self.pop_stack()
.ok_or(VmError::RuntimeError(RuntimeErrorKind::InvalidStackData))
}
fn pop_stack_binary_or_err(&mut self) -> Result<(ForgeValue, ForgeValue), VmError> {
let first = match self.pop_stack() {
Some(value) => value,
None => return Err(VmError::RuntimeError(RuntimeErrorKind::InvalidStackData)),
};
let second = match self.pop_stack() {
Some(value) => value,
None => {
self.push_stack(first);
return Err(VmError::RuntimeError(RuntimeErrorKind::InvalidStackData));
}
};
Ok((second, first))
}
/// Execute a chunk of code in a new VM instance
pub fn exec(chunk: impl ChunkOps) -> VmResult {
let mut vm = Forge::from_chunk(chunk.as_chunk());
vm.run()
}
/// Create a new VM instance with the given chunk data. No execution is
/// performed
pub fn from_chunk(chunk: Chunk) -> Self {
Forge {
chunk,
ip: 0,
sp: 0,
value_stack: VecDeque::with_capacity(INITIAL_STACK),
}
}
pub fn run(&mut self) -> VmResult {
while let Some(opcode) = self.next() {
match opcode {
OpCode::Return => return Ok(self.peek_stack().cloned().unwrap_or_default()),
OpCode::Constant(idx) => {
if let Some(value) = self.get_const(idx) {
println!("Load Const {:?}", &value);
self.push_stack(value.clone());
} else {
return Err(VmError::RuntimeError(RuntimeErrorKind::InvalidConstData));
}
}
OpCode::Invert => {
if let Some(value) = self.pop_stack() {
self.push_stack(value.invert());
}
}
OpCode::Add => {
let (lhs, rhs) = self.pop_stack_binary_or_err()?;
self.push_stack(lhs + rhs);
}
OpCode::Multiply => impl_binary_opcode!(self, std::ops::Mul::mul),
OpCode::Divide => impl_binary_opcode!(self, std::ops::Div::div),
OpCode::Subtract => impl_binary_opcode!(self, std::ops::Sub::sub),
}
}
Err(VmError::RuntimeError(RuntimeErrorKind::MissingEof))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::runtime::value::ForgeValue;
use crate::runtime::vm::{Chunk, Forge, OpCode, VmResult};
use test_case::test_case;
#[test]
fn op_negate() {
let mut chunk = Chunk::default();
chunk.op_constant(ForgeValue::from(100));
chunk.push_op(OpCode::Invert);
chunk.push_op(OpCode::Return);
assert_eq!(Forge::exec(chunk), Ok(ForgeValue::from(-100)));
}
#[test_case(ForgeValue::from(100), ForgeValue::from(100) => Ok(ForgeValue::from(200)) ; "Adding Numbers")]
#[test_case(ForgeValue::from("foo "), ForgeValue::from("bar") => Ok(ForgeValue::from("foo bar")) ; "Adding strings")]
#[test_case(ForgeValue::from("foo "), ForgeValue::from(100) => Ok(ForgeValue::from("foo 100")) ; "Adding string to number")]
#[test_case(ForgeValue::from(true), ForgeValue::from(false) => Ok(ForgeValue::from(false)) ; "Adding bools")]
fn op_add(lhs: ForgeValue, rhs: ForgeValue) -> VmResult {
let mut chunk = Chunk::default();
chunk.op_constant(lhs);
chunk.op_constant(rhs);
chunk.push_op(OpCode::Add);
chunk.push_op(OpCode::Return);
Forge::exec(chunk)
}
#[test_case(ForgeValue::from(100), ForgeValue::from(100) => Ok(ForgeValue::from(0)) ; "Subtract Numbers")]
#[test_case(ForgeValue::from("foo "), ForgeValue::from("bar") => Err(VmError::RuntimeError(RuntimeErrorKind::UnsupportedOperation(BinaryOp::Subtract))) ; "Subtract strings")]
#[test_case(ForgeValue::from("foo "), ForgeValue::from(100) => Err(VmError::RuntimeError(RuntimeErrorKind::UnsupportedOperation(BinaryOp::Subtract))) ; "Subtract string to number")]
#[test_case(ForgeValue::from(true), ForgeValue::from(false) => Err(VmError::RuntimeError(RuntimeErrorKind::UnsupportedOperation(BinaryOp::Subtract))) ; "Subtract bools")]
fn op_sub(lhs: ForgeValue, rhs: ForgeValue) -> VmResult {
let mut chunk = Chunk::default();
chunk.op_constant(lhs);
chunk.op_constant(rhs);
chunk.push_op(OpCode::Subtract);
chunk.push_op(OpCode::Return);
Forge::exec(chunk)
}
#[test_case(ForgeValue::from(100), ForgeValue::from(100) => Ok(ForgeValue::from(1_00_00)) ; "Multiply Numbers")]
#[test_case(ForgeValue::from("foo "), ForgeValue::from("bar") => Err(VmError::RuntimeError(RuntimeErrorKind::UnsupportedOperation(BinaryOp::Multiply))) ; "Multiply strings")]
#[test_case(ForgeValue::from("foo "), ForgeValue::from(100) => Err(VmError::RuntimeError(RuntimeErrorKind::UnsupportedOperation(BinaryOp::Multiply))) ; "Multiply string to number")]
#[test_case(ForgeValue::from(true), ForgeValue::from(false) => Err(VmError::RuntimeError(RuntimeErrorKind::UnsupportedOperation(BinaryOp::Multiply))) ; "Multiply bools")]
fn op_mul(lhs: ForgeValue, rhs: ForgeValue) -> VmResult {
let mut chunk = Chunk::default();
chunk.op_constant(lhs);
chunk.op_constant(rhs);
chunk.push_op(OpCode::Multiply);
chunk.push_op(OpCode::Return);
Forge::exec(chunk)
}
#[test_case(ForgeValue::from(100), ForgeValue::from(100) => Ok(ForgeValue::from(1)) ; "Divide Numbers")]
#[test_case(ForgeValue::from("foo "), ForgeValue::from("bar") => Err(VmError::RuntimeError(RuntimeErrorKind::UnsupportedOperation(BinaryOp::Divide))) ; "Divide strings")]
#[test_case(ForgeValue::from("foo "), ForgeValue::from(100) => Err(VmError::RuntimeError(RuntimeErrorKind::UnsupportedOperation(BinaryOp::Divide))) ; "Divide string to number")]
#[test_case(ForgeValue::from(true), ForgeValue::from(false) => Err(VmError::RuntimeError(RuntimeErrorKind::UnsupportedOperation(BinaryOp::Divide))) ; "Divide bools")]
fn op_div(lhs: ForgeValue, rhs: ForgeValue) -> VmResult {
let mut chunk = Chunk::default();
chunk.op_constant(lhs);
chunk.op_constant(rhs);
chunk.push_op(OpCode::Divide);
chunk.push_op(OpCode::Return);
Forge::exec(chunk)
}
}
mod chunks;
mod const_data;
mod machine;
mod opcode;
pub use chunks::{Chunk, ChunkOps, ChunkRef};
pub use const_data::{ConstData, ConstDataRef};
pub use machine::{Forge, VmError, VmResult};
pub use opcode::{OpCode, OpCodeError};
......@@ -6,6 +6,11 @@ pub enum OpCode {
Return,
/// Load a constant value from the chunk's data pool. Data starts at the included offset
Constant(usize),
Invert,
Add,
Multiply,
Divide,
Subtract,
}
impl OpCode {
......@@ -19,13 +24,18 @@ impl OpCode {
match self {
OpCode::Return => 1,
OpCode::Constant(_) => 2,
OpCode::Invert => 3,
OpCode::Add => 4,
OpCode::Multiply => 5,
OpCode::Divide => 6,
OpCode::Subtract => 7,
}
}
pub fn operand_bytes(&self) -> Vec<u8> {
match self {
OpCode::Return => vec![],
OpCode::Constant(value) => value.to_le_bytes().to_vec(),
_ => vec![],
}
}
......@@ -61,6 +71,11 @@ impl Display for OpCode {
match self {
OpCode::Return => "op_return",
OpCode::Constant(_) => "op_constant",
OpCode::Invert => "op_negate",
OpCode::Add => "op_add",
OpCode::Multiply => "op_multiply",
OpCode::Divide => "op_divide",
OpCode::Subtract => "op_subtract",
}
)
}
......
......@@ -35,3 +35,10 @@ macro_rules! deref_as {
}
};
}
/// "Clone-owned", clone a struct into an owned version of that struct, which will typically be
/// a different type to the implementor. If the struct is already an owned struct then the impl
/// _should_ perform a simple clone, though the impl may choose to perform otherwise
pub trait Clown<Target> {
fn clown(&self) -> Target;
}
......@@ -10,3 +10,4 @@ authors = [
repository = "https://lab.lcr.gr/microhacks/forge-script.git"
[dependencies]
forge-script-lang = { path = "../forge-script-lang" }
\ No newline at end of file
fn main() {
println!("Hello, world!");
use crate::repl::Repl;
use forge_script_lang::runtime::value::ForgeValue;
use forge_script_lang::runtime::vm::{Chunk, ChunkOps, Forge, OpCode, VmResult};
mod repl;
fn main() -> VmResult {
let mut repl = Repl::new();
repl.run();
Ok(ForgeValue::Null)
// let mut chunk = Chunk::default();
// chunk.op_constant(ForgeValue::String(String::from("foo")));
// chunk.push_op(OpCode::Invert);
// chunk.push_op(OpCode::Return);
//
// let value = Forge::exec(chunk.as_ref());
// println!("{:?}", &value);
// value
}
use forge_script_lang::runtime::vm::{Chunk, Forge};
use std::io::Write;
use std::num::ParseIntError;
pub struct Repl {
buffer: Vec<String>,
vm: Forge,
}
fn parse_hex(string: &str) -> Result<Vec<u8>, ParseIntError> {
let parts = string.split(" ").collect::<Vec<&str>>();
let mut out = Vec::with_capacity(parts.len());
for part in parts {
out.push(u8::from_str_radix(part, 16)?);
}
Ok(out)
}
impl Repl {
pub fn new() -> Self {
Repl {
buffer: Vec::new(),
vm: Forge::from_chunk(Chunk::default()),
}
}
pub fn run(&mut self) {
println!("\n] =========== [");
println!("] Forgescript [");
println!("] =========== [");
println!("\nRepl v{}\n", env!("CARGO_PKG_VERSION"));
'repl_loop: loop {
print!("> ");
match std::io::stdout().flush() {
Ok(_) => {}
Err(e) => {
eprintln!("{}", e);
break 'repl_loop;
}
}
let mut buffer = String::new();
let stdin = std::io::stdin();
match stdin.read_line(&mut buffer) {
Ok(_) => {}
Err(e) => {
eprintln!("\n{}", e);
break 'repl_loop;
}
}
let buffer = buffer.trim();
match buffer {
".quit" => {
break 'repl_loop;
}
".history" => {
println!("\n== History ==");
for line in self.buffer.iter() {
println!("{}", line);
}
println!("=============\n");
}
other => {
println!("{}", other);
self.buffer.push(String::from(other));
}
}
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment