1
0
dust/dust-lang/src/vm.rs

598 lines
21 KiB
Rust
Raw Normal View History

use crate::{
2024-09-19 03:02:28 +00:00
parse, AnnotatedError, Chunk, ChunkError, DustError, Identifier, Instruction, Local, Operation,
Span, Value, ValueError,
};
pub fn run(source: &str) -> Result<Option<Value>, DustError> {
let chunk = parse(source)?;
let mut vm = Vm::new(chunk);
vm.run()
.map_err(|error| DustError::Runtime { error, source })
}
#[derive(Debug, Eq, PartialEq)]
pub struct Vm {
chunk: Chunk,
ip: usize,
register_stack: Vec<Option<Value>>,
}
impl Vm {
const STACK_LIMIT: usize = u16::MAX as usize;
pub fn new(chunk: Chunk) -> Self {
Self {
chunk,
ip: 0,
register_stack: Vec::new(),
}
}
pub fn take_chunk(self) -> Chunk {
self.chunk
}
pub fn run(&mut self) -> Result<Option<Value>, VmError> {
2024-09-24 03:38:49 +00:00
// DRY helper to get constant or register values for binary operations
2024-09-23 23:16:15 +00:00
fn get_arguments(
2024-09-19 03:02:28 +00:00
vm: &mut Vm,
instruction: Instruction,
position: Span,
2024-09-23 23:16:15 +00:00
) -> Result<(&Value, &Value), VmError> {
2024-09-24 20:49:17 +00:00
let left = if instruction.b_is_constant() {
vm.chunk.get_constant(instruction.b(), position)?
2024-09-18 01:10:44 +00:00
} else {
2024-09-24 20:49:17 +00:00
vm.get(instruction.b(), position)?
2024-09-18 01:10:44 +00:00
};
2024-09-24 20:49:17 +00:00
let right = if instruction.c_is_constant() {
vm.chunk.get_constant(instruction.c(), position)?
2024-09-18 01:10:44 +00:00
} else {
2024-09-24 20:49:17 +00:00
vm.get(instruction.c(), position)?
2024-09-18 01:10:44 +00:00
};
Ok((left, right))
2024-09-19 03:02:28 +00:00
}
2024-09-18 01:10:44 +00:00
while let Ok((instruction, position)) = self.read(Span(0, 0)).copied() {
2024-09-25 05:27:10 +00:00
let ip = self.ip - 1;
let operation = instruction.operation();
let position_display = position.to_string();
log::info!("{ip:^3} | {position_display:^10} | {operation}");
2024-09-15 05:24:04 +00:00
match instruction.operation() {
Operation::Move => {
2024-09-24 20:49:17 +00:00
let from = instruction.b();
let to = instruction.a();
2024-09-23 23:16:15 +00:00
let value = self.take(from, position)?;
self.insert(value, to, position)?;
}
2024-09-18 03:06:58 +00:00
Operation::Close => {
2024-09-24 20:49:17 +00:00
let from = instruction.b();
let to = instruction.c();
2024-09-18 03:06:58 +00:00
for register_index in from..to {
self.register_stack[register_index as usize] = None;
}
}
Operation::LoadBoolean => {
2024-09-24 20:49:17 +00:00
let to_register = instruction.a();
let boolean = instruction.b_as_boolean();
let skip = instruction.c_as_boolean();
let value = Value::boolean(boolean);
self.insert(value, to_register, position)?;
2024-09-23 08:01:36 +00:00
if skip {
self.ip += 1;
}
}
Operation::LoadConstant => {
2024-09-24 20:49:17 +00:00
let to_register = instruction.a();
let from_constant = instruction.b();
let jump = instruction.c_as_boolean();
2024-09-14 19:06:13 +00:00
let value = self.chunk.take_constant(from_constant, position)?;
self.insert(value, to_register, position)?;
if jump {
self.ip += 1;
}
}
2024-09-17 23:35:33 +00:00
Operation::LoadList => {
2024-09-24 20:49:17 +00:00
let to_register = instruction.a();
let first_register = instruction.b();
let last_register = instruction.c();
let length = last_register - first_register + 1;
2024-09-17 23:35:33 +00:00
let mut list = Vec::with_capacity(length as usize);
for register_index in first_register..=last_register {
let value = match self.take(register_index, position) {
2024-09-18 03:06:58 +00:00
Ok(value) => value,
Err(VmError::EmptyRegister { .. }) => continue,
Err(error) => return Err(error),
};
2024-09-17 23:35:33 +00:00
list.push(value);
}
self.insert(Value::list(list), to_register, position)?;
}
2024-09-18 15:27:41 +00:00
Operation::DefineLocal => {
2024-09-24 20:49:17 +00:00
let from_register = instruction.a();
let to_local = instruction.b();
self.chunk.define_local(to_local, from_register, position)?;
}
Operation::GetLocal => {
2024-09-24 20:49:17 +00:00
let register_index = instruction.a();
let local_index = instruction.b();
let local = self.chunk.get_local(local_index, position)?;
let value = if let Some(index) = local.register_index {
self.take(index, position)?
} else {
return Err(VmError::UndefinedVariable {
identifier: local.identifier.clone(),
position,
});
};
self.insert(value, register_index, position)?;
}
2024-09-15 01:05:03 +00:00
Operation::SetLocal => {
2024-09-24 20:49:17 +00:00
let register_index = instruction.a();
let local_index = instruction.b();
let local = self.chunk.get_local(local_index, position)?;
let value = if let Some(index) = local.register_index {
self.take(index, position)?
} else {
return Err(VmError::UndefinedVariable {
identifier: local.identifier.clone(),
position,
});
};
2024-09-24 20:49:17 +00:00
let new_value = if instruction.b_is_constant() {
2024-09-18 15:27:41 +00:00
self.chunk.take_constant(register_index, position)?
} else {
self.take(register_index, position)?
2024-09-18 15:27:41 +00:00
};
2024-09-15 01:05:03 +00:00
2024-09-18 15:27:41 +00:00
value
.mutate(new_value)
.map_err(|error| VmError::Value { error, position })?;
self.insert(value, register_index, position)?;
2024-09-15 01:05:03 +00:00
}
Operation::Add => {
2024-09-23 23:16:15 +00:00
let (left, right) = get_arguments(self, instruction, position)?;
let sum = left
2024-09-23 12:57:49 +00:00
.add(right)
.map_err(|error| VmError::Value { error, position })?;
2024-09-24 20:49:17 +00:00
self.insert(sum, instruction.a(), position)?;
}
Operation::Subtract => {
2024-09-23 23:16:15 +00:00
let (left, right) = get_arguments(self, instruction, position)?;
let difference = left
2024-09-23 12:57:49 +00:00
.subtract(right)
.map_err(|error| VmError::Value { error, position })?;
2024-09-24 20:49:17 +00:00
self.insert(difference, instruction.a(), position)?;
}
Operation::Multiply => {
2024-09-23 23:16:15 +00:00
let (left, right) = get_arguments(self, instruction, position)?;
2024-09-24 20:49:17 +00:00
let product = left
2024-09-23 12:57:49 +00:00
.multiply(right)
.map_err(|error| VmError::Value { error, position })?;
2024-09-24 20:49:17 +00:00
self.insert(product, instruction.a(), position)?;
}
Operation::Divide => {
2024-09-23 23:16:15 +00:00
let (left, right) = get_arguments(self, instruction, position)?;
let quotient = left
2024-09-23 12:57:49 +00:00
.divide(right)
.map_err(|error| VmError::Value { error, position })?;
2024-09-24 20:49:17 +00:00
self.insert(quotient, instruction.a(), position)?;
}
Operation::Modulo => {
2024-09-23 23:16:15 +00:00
let (left, right) = get_arguments(self, instruction, position)?;
let remainder = left
2024-09-23 12:57:49 +00:00
.modulo(right)
.map_err(|error| VmError::Value { error, position })?;
2024-09-24 20:49:17 +00:00
self.insert(remainder, instruction.a(), position)?;
}
2024-09-19 15:41:18 +00:00
Operation::Test => {
2024-09-24 20:49:17 +00:00
let register = instruction.a();
let test_value = instruction.c_as_boolean();
2024-09-23 23:16:15 +00:00
let value = self.get(register, position)?;
2024-09-19 15:41:18 +00:00
let boolean = value.as_boolean().ok_or_else(|| VmError::ExpectedBoolean {
2024-09-23 23:16:15 +00:00
found: value.clone(),
2024-09-19 15:41:18 +00:00
position,
})?;
2024-09-24 20:49:17 +00:00
if boolean != test_value {
2024-09-19 15:41:18 +00:00
self.ip += 1;
}
}
2024-09-19 15:41:18 +00:00
Operation::TestSet => {
2024-09-24 20:49:17 +00:00
let to_register = instruction.a();
let argument = instruction.b();
let test_value = instruction.c_as_boolean();
2024-09-19 15:41:18 +00:00
let value = self.clone(argument, position)?;
let boolean = value.as_boolean().ok_or_else(|| VmError::ExpectedBoolean {
found: value.clone(),
position,
})?;
if boolean == test_value {
self.insert(value, to_register, position)?;
} else {
self.ip += 1;
}
}
Operation::Equal => {
2024-09-24 14:16:19 +00:00
let (jump, _) = *self.chunk.get_instruction(self.ip, position)?;
debug_assert_eq!(jump.operation(), Operation::Jump);
2024-09-23 23:16:15 +00:00
let (left, right) = get_arguments(self, instruction, position)?;
2024-09-24 14:16:19 +00:00
let boolean = left
2024-09-23 12:57:49 +00:00
.equal(right)
2024-09-24 14:16:19 +00:00
.map_err(|error| VmError::Value { error, position })?
.as_boolean()
.ok_or_else(|| VmError::ExpectedBoolean {
found: left.clone(),
position,
})?;
2024-09-24 20:49:17 +00:00
let compare_to = instruction.a_as_boolean();
2024-09-24 14:16:19 +00:00
if boolean == compare_to {
self.ip += 1;
} else {
2024-09-24 20:49:17 +00:00
let jump_distance = jump.a();
let is_positive = jump.a_as_boolean();
2024-09-24 14:16:19 +00:00
let new_ip = if is_positive {
self.ip + jump_distance as usize
} else {
self.ip - jump_distance as usize
};
self.ip = new_ip;
}
}
Operation::Less => {
let jump = self.chunk.get_instruction(self.ip, position)?.0;
2024-09-24 14:16:19 +00:00
2024-09-24 15:40:12 +00:00
debug_assert_eq!(jump.operation(), Operation::Jump);
2024-09-24 14:16:19 +00:00
2024-09-23 23:16:15 +00:00
let (left, right) = get_arguments(self, instruction, position)?;
2024-09-24 14:16:19 +00:00
let boolean = left
2024-09-24 15:40:12 +00:00
.less_than(right)
2024-09-24 14:16:19 +00:00
.map_err(|error| VmError::Value { error, position })?
.as_boolean()
.ok_or_else(|| VmError::ExpectedBoolean {
2024-09-24 15:40:12 +00:00
found: left.clone(),
2024-09-24 14:16:19 +00:00
position,
})?;
2024-09-24 20:49:17 +00:00
let compare_to = instruction.a_as_boolean();
2024-09-24 14:16:19 +00:00
if boolean == compare_to {
self.ip += 1;
} else {
2024-09-24 20:49:17 +00:00
let jump_distance = jump.a();
let is_positive = jump.a_as_boolean();
2024-09-24 14:16:19 +00:00
let new_ip = if is_positive {
self.ip + jump_distance as usize
} else {
self.ip - jump_distance as usize
};
self.ip = new_ip;
}
}
Operation::LessEqual => {
let jump = self.chunk.get_instruction(self.ip, position)?.0;
2024-09-24 15:40:12 +00:00
debug_assert_eq!(jump.operation(), Operation::Jump);
2024-09-23 23:16:15 +00:00
let (left, right) = get_arguments(self, instruction, position)?;
2024-09-24 14:16:19 +00:00
let boolean = left
2024-09-24 15:40:12 +00:00
.less_than_or_equal(right)
2024-09-24 14:16:19 +00:00
.map_err(|error| VmError::Value { error, position })?
.as_boolean()
.ok_or_else(|| VmError::ExpectedBoolean {
2024-09-24 15:40:12 +00:00
found: left.clone(),
2024-09-24 14:16:19 +00:00
position,
})?;
2024-09-24 20:49:17 +00:00
let compare_to = instruction.a_as_boolean();
2024-09-24 14:16:19 +00:00
if boolean == compare_to {
self.ip += 1;
} else {
2024-09-24 20:49:17 +00:00
let jump_distance = jump.a();
let is_positive = jump.a_as_boolean();
2024-09-24 14:16:19 +00:00
let new_ip = if is_positive {
self.ip + jump_distance as usize
} else {
self.ip - jump_distance as usize
};
self.ip = new_ip;
}
}
Operation::Negate => {
2024-09-24 20:49:17 +00:00
let value = if instruction.b_is_constant() {
self.chunk.get_constant(instruction.b(), position)?
2024-09-18 01:10:44 +00:00
} else {
2024-09-24 20:49:17 +00:00
self.get(instruction.b(), position)?
2024-09-18 01:10:44 +00:00
};
let negated = value
.negate()
.map_err(|error| VmError::Value { error, position })?;
2024-09-24 20:49:17 +00:00
self.insert(negated, instruction.a(), position)?;
}
Operation::Not => {
2024-09-24 20:49:17 +00:00
let value = if instruction.b_is_constant() {
self.chunk.get_constant(instruction.b(), position)?
2024-09-18 01:10:44 +00:00
} else {
2024-09-24 20:49:17 +00:00
self.get(instruction.b(), position)?
2024-09-18 01:10:44 +00:00
};
2024-09-18 11:58:31 +00:00
let not = value
.not()
.map_err(|error| VmError::Value { error, position })?;
2024-09-24 20:49:17 +00:00
self.insert(not, instruction.a(), position)?;
}
Operation::Jump => {
2024-09-24 20:49:17 +00:00
let offset = instruction.b();
let is_positive = instruction.c_as_boolean();
let new_ip = if is_positive {
self.ip + offset as usize
} else {
self.ip - offset as usize
};
self.ip = new_ip;
}
Operation::Return => {
2024-09-24 20:49:17 +00:00
let start_register = instruction.a();
let end_register = instruction.b();
let return_value_count = (end_register - start_register).max(1);
2024-09-19 17:54:28 +00:00
if return_value_count == 1 {
return Ok(Some(self.take(start_register, position)?));
}
}
}
}
Ok(None)
}
2024-09-15 01:05:03 +00:00
fn insert(&mut self, value: Value, index: u8, position: Span) -> Result<(), VmError> {
if self.register_stack.len() == Self::STACK_LIMIT {
Err(VmError::StackOverflow { position })
} else {
2024-09-15 01:05:03 +00:00
let index = index as usize;
while index >= self.register_stack.len() {
self.register_stack.push(None);
}
self.register_stack[index] = Some(value);
Ok(())
}
}
2024-09-19 15:41:18 +00:00
fn take(&mut self, index: u8, position: Span) -> Result<Value, VmError> {
let index = index as usize;
if let Some(register) = self.register_stack.get_mut(index) {
let value = register
.take()
.ok_or_else(|| VmError::EmptyRegister { index, position })?;
Ok(value)
} else {
Err(VmError::RegisterIndexOutOfBounds { position })
}
}
2024-09-23 12:57:49 +00:00
fn get(&self, index: u8, position: Span) -> Result<&Value, VmError> {
let index = index as usize;
if let Some(register) = self.register_stack.get(index) {
let value = register
.as_ref()
.ok_or_else(|| VmError::EmptyRegister { index, position })?;
Ok(value)
} else {
Err(VmError::RegisterIndexOutOfBounds { position })
}
}
2024-09-15 01:05:03 +00:00
fn clone(&mut self, index: u8, position: Span) -> Result<Value, VmError> {
let index = index as usize;
if let Some(register) = self.register_stack.get_mut(index) {
2024-09-14 19:06:13 +00:00
let cloneable = if let Some(value) = register.take() {
if value.is_raw() {
value.into_reference()
} else {
value
}
} else {
2024-09-14 19:06:13 +00:00
return Err(VmError::EmptyRegister { index, position });
};
*register = Some(cloneable.clone());
Ok(cloneable)
} else {
Err(VmError::RegisterIndexOutOfBounds { position })
}
}
fn _clone_mutable(&mut self, index: u8, position: Span) -> Result<Value, VmError> {
2024-09-18 15:27:41 +00:00
let index = index as usize;
if let Some(register) = self.register_stack.get_mut(index) {
let cloneable = if let Some(value) = register.take() {
value.into_mutable()
} else {
return Err(VmError::EmptyRegister { index, position });
};
*register = Some(cloneable.clone());
Ok(cloneable)
} else {
Err(VmError::RegisterIndexOutOfBounds { position })
}
}
fn _clone_as_variable(&mut self, local: Local, position: Span) -> Result<Value, VmError> {
2024-09-18 11:58:31 +00:00
let index = if let Some(index) = local.register_index {
index
} else {
return Err(VmError::UndefinedVariable {
2024-09-18 15:27:41 +00:00
identifier: local.identifier,
2024-09-18 11:58:31 +00:00
position,
});
};
2024-09-18 15:27:41 +00:00
let clone_result = if local.mutable {
self._clone_mutable(index, position)
2024-09-18 15:27:41 +00:00
} else {
self.clone(index, position)
};
2024-09-18 11:58:31 +00:00
match clone_result {
Err(VmError::EmptyRegister { .. }) => Err(VmError::UndefinedVariable {
identifier: local.identifier,
position,
}),
_ => clone_result,
}
}
fn pop(&mut self, position: Span) -> Result<Value, VmError> {
if let Some(register) = self.register_stack.pop() {
let value = register.ok_or(VmError::EmptyRegister {
index: self.register_stack.len().saturating_sub(1),
position,
})?;
Ok(value)
} else {
Err(VmError::StackUnderflow { position })
}
}
fn read(&mut self, position: Span) -> Result<&(Instruction, Span), VmError> {
let current = self.chunk.get_instruction(self.ip, position)?;
self.ip += 1;
Ok(current)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum VmError {
EmptyRegister {
index: usize,
position: Span,
},
2024-09-19 15:41:18 +00:00
ExpectedBoolean {
found: Value,
position: Span,
},
RegisterIndexOutOfBounds {
position: Span,
},
InvalidInstruction {
instruction: Instruction,
position: Span,
},
StackOverflow {
position: Span,
},
StackUnderflow {
position: Span,
},
UndefinedVariable {
identifier: Identifier,
position: Span,
},
// Wrappers for foreign errors
Chunk(ChunkError),
Value {
error: ValueError,
position: Span,
},
}
impl From<ChunkError> for VmError {
2024-09-18 15:27:41 +00:00
fn from(error: ChunkError) -> Self {
Self::Chunk(error)
}
}
impl AnnotatedError for VmError {
fn title() -> &'static str {
"Runtime Error"
}
fn description(&self) -> &'static str {
match self {
Self::EmptyRegister { .. } => "Empty register",
2024-09-19 15:41:18 +00:00
Self::ExpectedBoolean { .. } => "Expected boolean",
Self::RegisterIndexOutOfBounds { .. } => "Register index out of bounds",
Self::InvalidInstruction { .. } => "Invalid instruction",
Self::StackOverflow { .. } => "Stack overflow",
Self::StackUnderflow { .. } => "Stack underflow",
Self::UndefinedVariable { .. } => "Undefined variable",
Self::Value { .. } => "Value error",
2024-09-18 11:58:31 +00:00
Self::Chunk(error) => error.description(),
}
}
fn details(&self) -> Option<String> {
match self {
Self::EmptyRegister { index, .. } => Some(format!("Register {index} is empty")),
Self::UndefinedVariable { identifier, .. } => {
Some(format!("{identifier} is not in scope"))
}
Self::Chunk(error) => error.details(),
Self::Value { error, .. } => Some(error.to_string()),
_ => None,
}
}
fn position(&self) -> Span {
match self {
Self::EmptyRegister { position, .. } => *position,
2024-09-19 15:41:18 +00:00
Self::ExpectedBoolean { position, .. } => *position,
Self::RegisterIndexOutOfBounds { position } => *position,
Self::InvalidInstruction { position, .. } => *position,
Self::StackUnderflow { position } => *position,
Self::StackOverflow { position } => *position,
Self::UndefinedVariable { position, .. } => *position,
Self::Chunk(error) => error.position(),
Self::Value { position, .. } => *position,
}
}
}