diff --git a/dust-cli/src/main.rs b/dust-cli/src/main.rs index 41070c2..665db33 100644 --- a/dust-cli/src/main.rs +++ b/dust-cli/src/main.rs @@ -196,10 +196,10 @@ fn main() { } } - let chunk = compiler.finish(None, None); + let chunk = compiler.finish(); let compile_end = start_time.elapsed(); - let vm = Vm::new(&chunk, None, None); + let vm = Vm::new(chunk); let return_value = vm.run(); let run_end = start_time.elapsed(); diff --git a/dust-lang/src/compiler/error.rs b/dust-lang/src/compiler/error.rs index 39dc8c1..431f64e 100644 --- a/dust-lang/src/compiler/error.rs +++ b/dust-lang/src/compiler/error.rs @@ -231,7 +231,7 @@ impl AnnotatedError for CompileError { ) ] } - _ => todo!(), + _ => SmallVec::new(), } } @@ -248,7 +248,7 @@ impl AnnotatedError for CompileError { Span(left_position.0, right_position.1) )] } - _ => todo!(), + _ => SmallVec::new(), } } } diff --git a/dust-lang/src/compiler/mod.rs b/dust-lang/src/compiler/mod.rs index cf8dcb4..69b9251 100644 --- a/dust-lang/src/compiler/mod.rs +++ b/dust-lang/src/compiler/mod.rs @@ -20,8 +20,7 @@ use smallvec::{smallvec, SmallVec}; use crate::{ instruction::{ - CallNative, Close, GetLocal, Jump, LoadList, LoadSelf, Negate, Not, Point, Return, - SetLocal, Test, + CallNative, Close, GetLocal, Jump, LoadList, Negate, Not, Point, Return, SetLocal, Test, }, Argument, Chunk, ConcreteValue, DustError, DustString, FunctionType, Instruction, Lexer, Local, NativeFunction, Operation, Scope, Span, Token, TokenKind, Type, Value, @@ -46,7 +45,7 @@ pub fn compile(source: &str) -> Result { .compile() .map_err(|error| DustError::compile(error, source))?; - let chunk = compiler.finish(None, None); + let chunk = compiler.finish(); Ok(chunk) } @@ -63,9 +62,9 @@ pub struct Compiler<'src> { /// [`Compiler::finish`] is called. self_name: Option, - /// Return type of the function being compiled. This is assigned to the chunk when - /// [`Compiler::finish`] is called. - return_type: Option, + /// Type of the function being compiled. This is assigned to the chunk when [`Compiler::finish`] + /// is called. + r#type: FunctionType, /// Instructions, along with their types and positions, that have been compiled. The /// instructions and positions are assigned to the chunk when [`Compiler::finish`] is called. @@ -76,8 +75,8 @@ pub struct Compiler<'src> { /// is called. constants: SmallVec<[Value; 16]>, - /// Locals that have been compiled. These are assigned to the chunk when [`Compiler::finish`] is - /// called. + /// Block-local variables and their types. The locals are assigned to the chunk when + /// [`Compiler::finish`] is called. The types are discarded after compilation. locals: SmallVec<[(Local, Type); 8]>, /// Prototypes that have been compiled. These are assigned to the chunk when @@ -108,6 +107,12 @@ pub struct Compiler<'src> { /// Index of the record (i.e. runtime data) that the VM will use when calling the function. This /// is a depth-first index. record_index: u8, + + /// Record index for the next nested chunk that is compiled. When a function is compiled, its + /// `record_index` is assigned from this value. Then `next_record_index` is incremented to + /// maintain the depth-first index. After the function is compiled, the its `next_record_index` + /// is assigned to this chunk. + next_record_index: u8, } impl<'src> Compiler<'src> { @@ -122,6 +127,11 @@ impl<'src> Compiler<'src> { Ok(Compiler { self_name: None, + r#type: FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::None, + }, instructions: SmallVec::new(), constants: SmallVec::new(), locals: SmallVec::new(), @@ -132,26 +142,17 @@ impl<'src> Compiler<'src> { current_position, previous_token: Token::Eof, previous_position: Span(0, 0), - return_type: None, minimum_register: 0, block_index: 0, current_scope: Scope::default(), record_index: 0, + next_record_index: 1, }) } - pub fn finish( - self, - type_parameters: Option>, - value_parameters: Option>, - ) -> Chunk { + pub fn finish(self) -> Chunk { log::info!("End chunk"); - let r#type = FunctionType { - type_parameters, - value_parameters, - return_type: self.return_type.unwrap_or(Type::None), - }; let (instructions, positions): (SmallVec<[Instruction; 32]>, SmallVec<[Span; 32]>) = self .instructions .into_iter() @@ -165,7 +166,7 @@ impl<'src> Compiler<'src> { Chunk::new( self.self_name, - r#type, + self.r#type, instructions, positions, self.constants, @@ -400,26 +401,22 @@ impl<'src> Compiler<'src> { }) } - /// Updates [Self::return_type] with the given [Type]. + /// Updates [`Self::type`] with the given [Type] as `return_type`. /// - /// If [Self::return_type] is already set, it will check if the given [Type] is compatible with - /// it and set it to the least restrictive of the two. + /// If [`Self::type`] is already set, it will check if the given [Type] is compatible. fn update_return_type(&mut self, new_return_type: Type) -> Result<(), CompileError> { - if let Some(return_type) = &self.return_type { - return_type.check(&new_return_type).map_err(|conflict| { - CompileError::ReturnTypeConflict { + if self.r#type.return_type != Type::None { + self.r#type + .return_type + .check(&new_return_type) + .map_err(|conflict| CompileError::ReturnTypeConflict { conflict, position: self.current_position, - } - })?; - - if *return_type != Type::Any { - self.return_type = Some(new_return_type); - }; - } else { - self.return_type = Some(new_return_type); + })?; } + self.r#type.return_type = new_return_type; + Ok(()) } @@ -1026,9 +1023,9 @@ impl<'src> Compiler<'src> { return self.parse_call_native(native_function); } else if self.self_name.as_deref() == Some(identifier) { let destination = self.next_register(); - let load_self = Instruction::from(LoadSelf { destination }); + let load_function = Instruction::load_function(destination, self.record_index); - self.emit_instruction(load_self, Type::SelfFunction, start_position); + self.emit_instruction(load_function, Type::SelfFunction, start_position); return Ok(()); } else { @@ -1572,8 +1569,8 @@ impl<'src> Compiler<'src> { let function_start = self.current_position.0; let mut function_compiler = Compiler::new(self.lexer)?; - self.record_index += 1; - function_compiler.record_index = self.record_index; + function_compiler.record_index = self.next_record_index; + function_compiler.next_record_index = self.next_record_index + 1; let identifier_info = if let Token::Identifier(text) = function_compiler.current_token { let position = function_compiler.current_position; @@ -1646,8 +1643,13 @@ impl<'src> Compiler<'src> { } else { Type::None }; + let function_type = FunctionType { + type_parameters: None, + value_parameters, + return_type, + }; - function_compiler.return_type = Some((return_type).clone()); + function_compiler.r#type = function_type.clone(); function_compiler.expect(Token::LeftBrace)?; function_compiler.compile()?; @@ -1657,20 +1659,15 @@ impl<'src> Compiler<'src> { self.previous_position = function_compiler.previous_position; self.current_token = function_compiler.current_token; self.current_position = function_compiler.current_position; - self.record_index = function_compiler.record_index; + self.next_record_index = function_compiler.next_record_index; self.lexer.skip_to(self.current_position.1); let function_end = function_compiler.previous_position.1; - let prototype = function_compiler.finish(None, value_parameters.clone()); + let chunk = function_compiler.finish(); let destination = self.next_register(); - let function_type = FunctionType { - type_parameters: None, - value_parameters, - return_type, - }; - self.prototypes.push(prototype); + self.prototypes.push(chunk); if let Some((identifier, _)) = identifier_info { self.declare_local( @@ -1680,20 +1677,24 @@ impl<'src> Compiler<'src> { false, self.current_scope, ); - } else { - let load_function = Instruction::load_function(destination, self.record_index); - - self.emit_instruction( - load_function, - Type::function(function_type), - Span(function_start, function_end), - ); } + let load_function = Instruction::load_function(destination, self.record_index); + + self.emit_instruction( + load_function, + Type::function(function_type), + Span(function_start, function_end), + ); + Ok(()) } fn parse_call(&mut self) -> Result<(), CompileError> { + let start = self.current_position.0; + + self.advance()?; + let (last_instruction, last_instruction_type, _) = self.instructions .last() @@ -1710,10 +1711,10 @@ impl<'src> Compiler<'src> { }); } - let prototype_index = last_instruction.b_field(); + let function_register = last_instruction.a_field(); let function_return_type = match last_instruction_type { Type::Function(function_type) => function_type.return_type.clone(), - Type::SelfFunction => self.return_type.clone().unwrap_or(Type::None), + Type::SelfFunction => self.r#type.return_type.clone(), _ => { return Err(CompileError::ExpectedFunction { found: self.previous_token.to_owned(), @@ -1722,9 +1723,6 @@ impl<'src> Compiler<'src> { }); } }; - let start = self.current_position.0; - - self.advance()?; let mut argument_count = 0; @@ -1752,7 +1750,7 @@ impl<'src> Compiler<'src> { let end = self.current_position.1; let destination = self.next_register(); - let call = Instruction::call(destination, prototype_index, argument_count); + let call = Instruction::call(destination, function_register, argument_count); self.emit_instruction(call, function_return_type, Span(start, end)); diff --git a/dust-lang/src/dust_error.rs b/dust-lang/src/dust_error.rs index f6c4fdb..06585c7 100644 --- a/dust-lang/src/dust_error.rs +++ b/dust-lang/src/dust_error.rs @@ -5,7 +5,7 @@ use std::fmt::{self, Display, Formatter}; use annotate_snippets::{Level, Renderer, Snippet}; use smallvec::SmallVec; -use crate::{CompileError, Span}; +use crate::{CompileError, NativeFunctionError, Span}; /// A top-level error that can occur during the interpretation of Dust code. #[derive(Debug, PartialEq)] @@ -14,6 +14,10 @@ pub enum DustError<'src> { error: CompileError, source: &'src str, }, + NativeFunction { + error: NativeFunctionError, + source: &'src str, + }, } impl<'src> DustError<'src> { @@ -57,12 +61,19 @@ impl<'src> DustError<'src> { error.detail_snippets(), error.help_snippets(), ), + Self::NativeFunction { error, .. } => ( + NativeFunctionError::title(), + error.description(), + error.detail_snippets(), + error.help_snippets(), + ), } } fn source(&self) -> &str { match self { Self::Compile { source, .. } => source, + Self::NativeFunction { source, .. } => source, } } } diff --git a/dust-lang/src/instruction/call.rs b/dust-lang/src/instruction/call.rs index 979aedd..ad7b93f 100644 --- a/dust-lang/src/instruction/call.rs +++ b/dust-lang/src/instruction/call.rs @@ -1,20 +1,36 @@ use crate::{Instruction, Operation}; +use super::InstructionData; + pub struct Call { pub destination: u8, - pub prototype_index: u8, + pub function_register: u8, pub argument_count: u8, } impl From<&Instruction> for Call { fn from(instruction: &Instruction) -> Self { let destination = instruction.a_field(); - let prototype_index = instruction.b_field(); + let function_register = instruction.b_field(); let argument_count = instruction.c_field(); Call { destination, - prototype_index, + function_register, + argument_count, + } + } +} + +impl From for Call { + fn from(instruction: InstructionData) -> Self { + let destination = instruction.a_field; + let function_register = instruction.b_field; + let argument_count = instruction.c_field; + + Call { + destination, + function_register, argument_count, } } @@ -23,7 +39,7 @@ impl From<&Instruction> for Call { impl From for Instruction { fn from(call: Call) -> Self { let a = call.destination; - let b = call.prototype_index; + let b = call.function_register; let c = call.argument_count; Instruction::new(Operation::CALL, a, b, c, false, false, false) diff --git a/dust-lang/src/instruction/call_native.rs b/dust-lang/src/instruction/call_native.rs index 43bf70a..dd8f13d 100644 --- a/dust-lang/src/instruction/call_native.rs +++ b/dust-lang/src/instruction/call_native.rs @@ -1,5 +1,7 @@ use crate::{Instruction, NativeFunction, Operation}; +use super::InstructionData; + pub struct CallNative { pub destination: u8, pub function: NativeFunction, @@ -19,6 +21,16 @@ impl From<&Instruction> for CallNative { } } +impl From for CallNative { + fn from(data: InstructionData) -> Self { + CallNative { + destination: data.a_field, + function: NativeFunction::from(data.b_field), + argument_count: data.c_field, + } + } +} + impl From for Instruction { fn from(call_native: CallNative) -> Self { let operation = Operation::CALL_NATIVE; diff --git a/dust-lang/src/instruction/close.rs b/dust-lang/src/instruction/close.rs index ff485a9..2abadc1 100644 --- a/dust-lang/src/instruction/close.rs +++ b/dust-lang/src/instruction/close.rs @@ -19,8 +19,8 @@ impl From<&Instruction> for Close { impl From for Close { fn from(instruction: InstructionData) -> Self { Close { - from: instruction.b, - to: instruction.c, + from: instruction.b_field, + to: instruction.c_field, } } } diff --git a/dust-lang/src/instruction/load_boolean.rs b/dust-lang/src/instruction/load_boolean.rs index 64c19b9..7200439 100644 --- a/dust-lang/src/instruction/load_boolean.rs +++ b/dust-lang/src/instruction/load_boolean.rs @@ -10,9 +10,9 @@ pub struct LoadBoolean { impl From for LoadBoolean { fn from(instruction: InstructionData) -> Self { - let destination = instruction.a; - let value = instruction.b != 0; - let jump_next = instruction.c != 0; + let destination = instruction.a_field; + let value = instruction.b_field != 0; + let jump_next = instruction.c_field != 0; LoadBoolean { destination, diff --git a/dust-lang/src/instruction/load_constant.rs b/dust-lang/src/instruction/load_constant.rs index 7c8598b..461758e 100644 --- a/dust-lang/src/instruction/load_constant.rs +++ b/dust-lang/src/instruction/load_constant.rs @@ -26,9 +26,9 @@ impl From<&Instruction> for LoadConstant { impl From for LoadConstant { fn from(instruction: InstructionData) -> Self { - let destination = instruction.a; - let constant_index = instruction.b; - let jump_next = instruction.c != 0; + let destination = instruction.a_field; + let constant_index = instruction.b_field; + let jump_next = instruction.c_field != 0; LoadConstant { destination, diff --git a/dust-lang/src/instruction/load_function.rs b/dust-lang/src/instruction/load_function.rs index 99d6804..caef3de 100644 --- a/dust-lang/src/instruction/load_function.rs +++ b/dust-lang/src/instruction/load_function.rs @@ -1,32 +1,39 @@ use std::fmt::{self, Display, Formatter}; -use super::{Instruction, Operation}; +use super::{Instruction, InstructionData, Operation}; pub struct LoadFunction { pub destination: u8, - pub prototype_index: u8, + pub record_index: u8, } impl From<&Instruction> for LoadFunction { fn from(instruction: &Instruction) -> Self { let destination = instruction.a_field(); - let prototype_index = instruction.b_field(); + let record_index = instruction.b_field(); LoadFunction { destination, - prototype_index, + record_index, + } + } +} + +impl From for LoadFunction { + fn from(instruction: InstructionData) -> Self { + LoadFunction { + destination: instruction.a_field, + record_index: instruction.b_field, } } } impl From for Instruction { fn from(load_function: LoadFunction) -> Self { - let operation = Operation::LOAD_FUNCTION; - Instruction::new( - operation, + Operation::LOAD_FUNCTION, load_function.destination, - load_function.prototype_index, + load_function.record_index, 0, false, false, @@ -37,6 +44,6 @@ impl From for Instruction { impl Display for LoadFunction { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "R{} = P{}", self.destination, self.prototype_index) + write!(f, "R{} = P{}", self.destination, self.record_index) } } diff --git a/dust-lang/src/instruction/load_list.rs b/dust-lang/src/instruction/load_list.rs index 7b8faeb..7e426db 100644 --- a/dust-lang/src/instruction/load_list.rs +++ b/dust-lang/src/instruction/load_list.rs @@ -1,5 +1,7 @@ use crate::{Instruction, Operation}; +use super::InstructionData; + pub struct LoadList { pub destination: u8, pub start_register: u8, @@ -17,6 +19,18 @@ impl From<&Instruction> for LoadList { } } +impl From for LoadList { + fn from(instruction: InstructionData) -> Self { + let destination = instruction.a_field; + let start_register = instruction.b_field; + + LoadList { + destination, + start_register, + } + } +} + impl From for Instruction { fn from(load_list: LoadList) -> Self { let operation = Operation::LOAD_LIST; diff --git a/dust-lang/src/instruction/load_self.rs b/dust-lang/src/instruction/load_self.rs index 7bbccfd..804d3a2 100644 --- a/dust-lang/src/instruction/load_self.rs +++ b/dust-lang/src/instruction/load_self.rs @@ -1,5 +1,7 @@ use crate::{Instruction, Operation}; +use super::InstructionData; + pub struct LoadSelf { pub destination: u8, } @@ -12,6 +14,14 @@ impl From<&Instruction> for LoadSelf { } } +impl From for LoadSelf { + fn from(instruction: InstructionData) -> Self { + let destination = instruction.a_field; + + LoadSelf { destination } + } +} + impl From for Instruction { fn from(load_self: LoadSelf) -> Self { let operation = Operation::LOAD_SELF; diff --git a/dust-lang/src/instruction/mod.rs b/dust-lang/src/instruction/mod.rs index 3ff7257..44363d7 100644 --- a/dust-lang/src/instruction/mod.rs +++ b/dust-lang/src/instruction/mod.rs @@ -154,16 +154,6 @@ use crate::NativeFunction; #[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] pub struct Instruction(u32); -#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct InstructionData { - pub a: u8, - pub b: u8, - pub c: u8, - pub b_is_constant: bool, - pub c_is_constant: bool, - pub d: bool, -} - impl Instruction { pub fn new( operation: Operation, @@ -231,12 +221,12 @@ impl Instruction { ( self.operation(), InstructionData { - a: self.a_field(), - b: self.b_field(), - c: self.c_field(), + a_field: self.a_field(), + b_field: self.b_field(), + c_field: self.c_field(), b_is_constant: self.b_is_constant(), c_is_constant: self.c_is_constant(), - d: self.d_field(), + d_field: self.d_field(), }, ) } @@ -265,10 +255,10 @@ impl Instruction { }) } - pub fn load_function(destination: u8, prototype_index: u8) -> Instruction { + pub fn load_function(destination: u8, record_index: u8) -> Instruction { Instruction::from(LoadFunction { destination, - prototype_index, + record_index, }) } @@ -385,10 +375,10 @@ impl Instruction { }) } - pub fn call(destination: u8, prototype_index: u8, argument_count: u8) -> Instruction { + pub fn call(destination: u8, function_register: u8, argument_count: u8) -> Instruction { Instruction::from(Call { destination, - prototype_index, + function_register, argument_count, }) } @@ -487,6 +477,7 @@ impl Instruction { match self.operation() { Operation::LOAD_BOOLEAN | Operation::LOAD_CONSTANT + | Operation::LOAD_FUNCTION | Operation::LOAD_LIST | Operation::LOAD_SELF | Operation::GET_LOCAL @@ -697,16 +688,18 @@ impl Instruction { Operation::CALL => { let Call { destination, - prototype_index, + function_register: record_index, argument_count, } = Call::from(self); let arguments_start = destination.saturating_sub(argument_count); match argument_count { - 0 => format!("R{destination} = P{prototype_index}()"), - 1 => format!("R{destination} = P{prototype_index}(R{arguments_start})"), + 0 => format!("R{destination} = P{record_index}()"), + 1 => format!("R{destination} = P{record_index}(R{arguments_start})"), _ => { - format!("R{destination} = P{prototype_index}(R{arguments_start}..R{destination})") + format!( + "R{destination} = P{record_index}(R{arguments_start}..R{destination})" + ) } } } @@ -760,10 +753,32 @@ impl Instruction { impl Debug for Instruction { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{self}") + } +} + +impl Display for Instruction { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{} {}", self.operation(), self.disassembly_info()) } } +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] +pub struct InstructionData { + pub a_field: u8, + pub b_field: u8, + pub c_field: u8, + pub d_field: bool, + pub b_is_constant: bool, + pub c_is_constant: bool, +} + +impl Display for InstructionData { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{self:?}") + } +} + #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] pub enum Argument { Constant(u8), diff --git a/dust-lang/src/instruction/move.rs b/dust-lang/src/instruction/move.rs index 887f94d..cf9e61e 100644 --- a/dust-lang/src/instruction/move.rs +++ b/dust-lang/src/instruction/move.rs @@ -21,8 +21,8 @@ impl From<&Instruction> for Point { impl From for Point { fn from(instruction: InstructionData) -> Self { Point { - from: instruction.b, - to: instruction.c, + from: instruction.b_field, + to: instruction.c_field, } } } diff --git a/dust-lang/src/native_function/assertion.rs b/dust-lang/src/native_function/assertion.rs index 3e73a8a..1acdf29 100644 --- a/dust-lang/src/native_function/assertion.rs +++ b/dust-lang/src/native_function/assertion.rs @@ -1,27 +1,26 @@ -use std::panic; +use std::{ops::Range, panic}; -use smallvec::SmallVec; - -use crate::{vm::Record, DustString, NativeFunctionError, Value}; +use crate::{ + vm::{Record, ThreadSignal}, + NativeFunctionError, +}; pub fn panic( record: &mut Record, - arguments: SmallVec<[&Value; 4]>, -) -> Result, NativeFunctionError> { - let mut message: Option = None; + _: Option, + argument_range: Range, +) -> Result { + let position = record.current_position(); + let mut message = format!("Dust panic at {position}!"); - for value_ref in arguments { - let string = value_ref.display(record); + for register_index in argument_range { + let value = record.open_register(register_index); - match message { - Some(ref mut message) => message.push_str(&string), - None => message = Some(string), + if let Some(string) = value.as_string() { + message.push_str(&string); + message.push('\n'); } } - if let Some(message) = message { - panic!("{message}"); - } else { - panic!("Explicit panic"); - } + panic!("{}", message) } diff --git a/dust-lang/src/native_function/io.rs b/dust-lang/src/native_function/io.rs index 153ceb8..3b74b28 100644 --- a/dust-lang/src/native_function/io.rs +++ b/dust-lang/src/native_function/io.rs @@ -1,13 +1,15 @@ use std::io::{stdin, stdout, Write}; +use std::ops::Range; -use smallvec::SmallVec; - +use crate::vm::{Register, ThreadSignal}; use crate::{vm::Record, ConcreteValue, NativeFunctionError, Value}; pub fn read_line( record: &mut Record, - _: SmallVec<[&Value; 4]>, -) -> Result, NativeFunctionError> { + destination: Option, + _argument_range: Range, +) -> Result { + let destination = destination.unwrap(); let mut buffer = String::new(); match stdin().read_line(&mut buffer) { @@ -16,54 +18,77 @@ pub fn read_line( buffer.truncate(length.saturating_sub(1)); - Ok(Some(Value::Concrete(ConcreteValue::string(buffer)))) + let register = Register::Value(Value::Concrete(ConcreteValue::string(buffer))); + + record.set_register(destination, register); + } + Err(error) => { + return Err(NativeFunctionError::Io { + error: error.kind(), + position: record.current_position(), + }) } - Err(error) => Err(NativeFunctionError::Io { - error: error.kind(), - }), } + + Ok(ThreadSignal::Continue) } pub fn write( record: &mut Record, - arguments: SmallVec<[&Value; 4]>, -) -> Result, NativeFunctionError> { + _destination: Option, + argument_range: Range, +) -> Result { let mut stdout = stdout(); - for argument in arguments { - let string = argument.display(record); + for register_index in argument_range { + let value = record.open_register(register_index); + let string = value.display(record); stdout - .write_all(string.as_bytes()) + .write(string.as_bytes()) .map_err(|io_error| NativeFunctionError::Io { error: io_error.kind(), + position: record.current_position(), })?; } - Ok(None) + stdout.flush().map_err(|io_error| NativeFunctionError::Io { + error: io_error.kind(), + position: record.current_position(), + })?; + + Ok(ThreadSignal::Continue) } pub fn write_line( record: &mut Record, - arguments: SmallVec<[&Value; 4]>, -) -> Result, NativeFunctionError> { - let mut stdout = stdout(); + _destination: Option, + argument_range: Range, +) -> Result { + let mut stdout = stdout().lock(); - for argument in arguments { - let string = argument.display(record); + for register_index in argument_range { + let value = record.open_register(register_index); + let string = value.display(record); stdout - .write_all(string.as_bytes()) + .write(string.as_bytes()) .map_err(|io_error| NativeFunctionError::Io { error: io_error.kind(), + position: record.current_position(), + })?; + stdout + .write(b"\n") + .map_err(|io_error| NativeFunctionError::Io { + error: io_error.kind(), + position: record.current_position(), })?; } - stdout - .write(b"\n") - .map_err(|io_error| NativeFunctionError::Io { - error: io_error.kind(), - })?; + stdout.flush().map_err(|io_error| NativeFunctionError::Io { + error: io_error.kind(), + position: record.current_position(), + })?; - Ok(None) + Ok(ThreadSignal::Continue) } diff --git a/dust-lang/src/native_function/mod.rs b/dust-lang/src/native_function/mod.rs index cf4f842..1fd5ed4 100644 --- a/dust-lang/src/native_function/mod.rs +++ b/dust-lang/src/native_function/mod.rs @@ -9,13 +9,17 @@ mod string; use std::{ fmt::{self, Display, Formatter}, io::ErrorKind as IoErrorKind, + ops::Range, string::ParseError, }; use serde::{Deserialize, Serialize}; use smallvec::{smallvec, SmallVec}; -use crate::{AnnotatedError, FunctionType, Span, Type, Value, Vm}; +use crate::{ + vm::{Record, ThreadSignal}, + AnnotatedError, FunctionType, Span, Type, +}; macro_rules! define_native_function { ($(($name:ident, $bytes:literal, $str:expr, $type:expr, $function:expr)),*) => { @@ -32,12 +36,13 @@ macro_rules! define_native_function { impl NativeFunction { pub fn call( &self, - vm: &Vm<'_>, - arguments: SmallVec<[&Value; 4]>, - ) -> Result, NativeFunctionError> { + record: &mut Record, + destination: Option, + argument_range: Range, + ) -> Result { match self { $( - NativeFunction::$name => $function(vm, arguments), + NativeFunction::$name => $function(record, destination, argument_range), )* } } @@ -258,7 +263,7 @@ pub enum NativeFunctionError { position: Span, }, Panic { - message: Option, + message: String, position: Span, }, Parse { @@ -288,10 +293,28 @@ impl AnnotatedError for NativeFunctionError { } fn detail_snippets(&self) -> SmallVec<[(String, Span); 2]> { - todo!() + match self { + NativeFunctionError::ExpectedArgumentCount { + expected, + found, + position, + } => smallvec![( + format!("Expected {expected} arguments, found {found}"), + *position + )], + NativeFunctionError::Panic { message, position } => { + smallvec![(format!("Dust panic!\n{message}"), *position)] + } + NativeFunctionError::Parse { error, position } => { + smallvec![(format!("{error}"), *position)] + } + NativeFunctionError::Io { error, position } => { + smallvec![(format!("{error}"), *position)] + } + } } fn help_snippets(&self) -> SmallVec<[(String, Span); 2]> { - todo!() + SmallVec::new() } } diff --git a/dust-lang/src/native_function/string.rs b/dust-lang/src/native_function/string.rs index 2faec45..a4db8dd 100644 --- a/dust-lang/src/native_function/string.rs +++ b/dust-lang/src/native_function/string.rs @@ -1,22 +1,21 @@ -use smallvec::SmallVec; +use std::ops::Range; -use crate::{ConcreteValue, NativeFunctionError, Value, Vm}; +use crate::{ + vm::{Record, Register, ThreadSignal}, + ConcreteValue, NativeFunctionError, Value, +}; pub fn to_string( - vm: &Vm, - arguments: SmallVec<[&Value; 4]>, -) -> Result, NativeFunctionError> { - if arguments.len() != 1 { - return Err(NativeFunctionError::ExpectedArgumentCount { - expected: 1, - found: 0, - position: vm.current_position(), - }); - } + record: &mut Record, + destination: Option, + argument_range: Range, +) -> Result { + let argument_value = record.open_register(argument_range.start); + let argument_string = argument_value.display(record); + let destination = destination.unwrap(); + let register = Register::Value(Value::Concrete(ConcreteValue::string(argument_string))); - let argument_string = arguments[0].display(vm); + record.set_register(destination, register); - Ok(Some(Value::Concrete(ConcreteValue::string( - argument_string, - )))) + Ok(ThreadSignal::Continue) } diff --git a/dust-lang/src/value/function.rs b/dust-lang/src/value/function.rs index 7e0ee11..3a2e96b 100644 --- a/dust-lang/src/value/function.rs +++ b/dust-lang/src/value/function.rs @@ -8,6 +8,7 @@ use super::DustString; pub struct Function { pub name: Option, pub r#type: FunctionType, + pub record_index: usize, pub prototype_index: usize, } diff --git a/dust-lang/src/value/mod.rs b/dust-lang/src/value/mod.rs index 70e21b7..8a41c4e 100644 --- a/dust-lang/src/value/mod.rs +++ b/dust-lang/src/value/mod.rs @@ -45,6 +45,14 @@ impl Value { } } + pub fn as_string(&self) -> Option<&DustString> { + if let Value::Concrete(ConcreteValue::String(value)) = self { + Some(value) + } else { + None + } + } + pub fn r#type(&self) -> Type { match self { Value::Concrete(concrete_value) => concrete_value.r#type(), diff --git a/dust-lang/src/vm/call_stack.rs b/dust-lang/src/vm/call_stack.rs index 367ed8e..0e1b927 100644 --- a/dust-lang/src/vm/call_stack.rs +++ b/dust-lang/src/vm/call_stack.rs @@ -1,6 +1,6 @@ use std::fmt::{self, Debug, Display, Formatter}; -use super::FunctionCall; +use super::{FunctionCall, VmError}; #[derive(Clone, PartialEq)] pub struct CallStack { @@ -31,6 +31,22 @@ impl CallStack { pub fn pop(&mut self) -> Option { self.calls.pop() } + + pub fn last(&self) -> Option<&FunctionCall> { + self.calls.last() + } + + pub fn pop_or_panic(&mut self) -> FunctionCall { + assert!(!self.is_empty(), "{}", VmError::CallStackUnderflow); + + self.calls.pop().unwrap() + } + + pub fn last_or_panic(&self) -> &FunctionCall { + assert!(!self.is_empty(), "{}", VmError::CallStackUnderflow); + + self.calls.last().unwrap() + } } impl Debug for CallStack { @@ -43,8 +59,8 @@ impl Display for CallStack { fn fmt(&self, f: &mut Formatter) -> fmt::Result { writeln!(f, "-- DUST CALL STACK --")?; - for FunctionCall { function, .. } in &self.calls { - writeln!(f, "{function}")?; + for function_call in &self.calls { + writeln!(f, "{function_call:?}")?; } Ok(()) diff --git a/dust-lang/src/vm/error.rs b/dust-lang/src/vm/error.rs index 77d69ae..7b6e378 100644 --- a/dust-lang/src/vm/error.rs +++ b/dust-lang/src/vm/error.rs @@ -1,12 +1,13 @@ use std::fmt::{self, Display, Formatter}; -use crate::DustString; +use crate::{DustString, InstructionData, Value}; use super::call_stack::CallStack; #[derive(Clone, Debug, PartialEq)] pub enum VmError { - CallStackUnderflow { thread_name: DustString }, + CallStackUnderflow, + ExpectedFunction { value: Value }, InstructionIndexOutOfBounds { call_stack: CallStack, ip: usize }, MalformedInstruction { instruction: InstructionData }, } @@ -14,12 +15,18 @@ pub enum VmError { impl Display for VmError { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { - Self::CallStackUnderflow { thread_name } => { - write!(f, "Call stack underflow in thread {thread_name}") + Self::CallStackUnderflow => { + write!(f, "Call stack underflow") + } + Self::ExpectedFunction { value } => { + write!(f, "Expected function, found {value}") } Self::InstructionIndexOutOfBounds { call_stack, ip } => { write!(f, "Instruction index {} out of bounds\n{call_stack}", ip) } + Self::MalformedInstruction { instruction } => { + write!(f, "Malformed instruction {instruction}") + } } } } diff --git a/dust-lang/src/vm/mod.rs b/dust-lang/src/vm/mod.rs index 969fb8c..d3b82fb 100644 --- a/dust-lang/src/vm/mod.rs +++ b/dust-lang/src/vm/mod.rs @@ -11,9 +11,10 @@ use std::{ thread::spawn, }; +pub use call_stack::CallStack; pub use error::VmError; pub use record::Record; -use thread::Thread; +pub use thread::{Thread, ThreadSignal}; use crate::{compile, Chunk, DustError, Value}; @@ -94,7 +95,7 @@ impl Display for Pointer { #[derive(Clone, Debug, PartialEq)] pub struct FunctionCall { - prototype_index: usize, record_index: usize, return_register: u8, + argument_count: u8, } diff --git a/dust-lang/src/vm/record.rs b/dust-lang/src/vm/record.rs index a85bd97..69a0626 100644 --- a/dust-lang/src/vm/record.rs +++ b/dust-lang/src/vm/record.rs @@ -1,4 +1,4 @@ -use std::env::consts::OS; +use std::mem::replace; use smallvec::SmallVec; @@ -134,6 +134,30 @@ impl Record { } } + pub fn replace_register_or_clone_constant( + &mut self, + register_index: u8, + new_register: Register, + ) -> Value { + let register_index = register_index as usize; + + assert!( + register_index < self.stack.len(), + "VM Error: Register index out of bounds" + ); + + let old_register = replace(&mut self.stack[register_index], new_register); + + match old_register { + Register::Value(value) => value, + Register::Pointer(pointer) => match pointer { + Pointer::Stack(register_index) => self.open_register(register_index).clone(), + Pointer::Constant(constant_index) => self.get_constant(constant_index).clone(), + }, + Register::Empty => panic!("VM Error: Register {register_index} is empty"), + } + } + /// DRY helper to get a value from an Argument pub fn get_argument(&self, index: u8, is_constant: bool) -> &Value { if is_constant { diff --git a/dust-lang/src/vm/runner.rs b/dust-lang/src/vm/runner.rs index 175e680..43bcb3d 100644 --- a/dust-lang/src/vm/runner.rs +++ b/dust-lang/src/vm/runner.rs @@ -1,11 +1,15 @@ use smallvec::SmallVec; use crate::{ - instruction::{Close, LoadBoolean, LoadConstant, Point}, - AbstractList, ConcreteValue, Instruction, InstructionData, NativeFunction, Type, Value, + instruction::{ + Call, CallNative, Close, LoadBoolean, LoadConstant, LoadFunction, LoadList, LoadSelf, Point, + }, + vm::VmError, + AbstractList, ConcreteValue, Function, Instruction, InstructionData, NativeFunction, Type, + Value, }; -use super::{thread::ThreadSignal, Pointer, Record, Register}; +use super::{thread::ThreadSignal, FunctionCall, Pointer, Record, Register}; #[derive(Clone, Copy, Debug, PartialEq)] pub struct RunAction { @@ -33,11 +37,12 @@ impl From for RunAction { pub type RunnerLogic = fn(InstructionData, &mut Record) -> ThreadSignal; -pub const RUNNER_LOGIC_TABLE: [RunnerLogic; 24] = [ +pub const RUNNER_LOGIC_TABLE: [RunnerLogic; 25] = [ r#move, close, load_boolean, load_constant, + load_function, load_list, load_self, get_local, @@ -127,11 +132,14 @@ pub fn load_constant(instruction_data: InstructionData, record: &mut Record) -> } pub fn load_list(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { - let InstructionData { a, b, .. } = instruction_data; - let mut item_pointers = Vec::with_capacity((a - b) as usize); + let LoadList { + destination, + start_register, + } = instruction_data.into(); + let mut item_pointers = Vec::with_capacity((destination - start_register) as usize); let mut item_type = Type::Any; - for register_index in b..a { + for register_index in start_register..destination { match record.get_register(register_index) { Register::Empty => continue, Register::Value(value) => { @@ -157,37 +165,62 @@ pub fn load_list(instruction_data: InstructionData, record: &mut Record) -> Thre }); let register = Register::Value(list_value); - record.set_register(a, register); + record.set_register(destination, register); + + ThreadSignal::Continue +} + +pub fn load_function(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { + let LoadFunction { + destination, + record_index, + } = instruction_data.into(); + + ThreadSignal::Continue } pub fn load_self(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { - let InstructionData { a, .. } = instruction_data; + let LoadSelf { destination } = instruction_data.into(); let register = Register::Value(Value::SelfFunction); - record.set_register(a, register); + record.set_register(destination, register); + + ThreadSignal::Continue } pub fn get_local(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { - let InstructionData { a, b, .. } = instruction_data; + let InstructionData { + a_field: a, + b_field: b, + .. + } = instruction_data; let local_register_index = record.get_local_register(b); let register = Register::Pointer(Pointer::Stack(local_register_index)); record.set_register(a, register); + + ThreadSignal::Continue } pub fn set_local(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { - let InstructionData { b, c, .. } = instruction_data; + let InstructionData { + b_field: b, + c_field: c, + .. + } = instruction_data; let local_register_index = record.get_local_register(c); let register = Register::Pointer(Pointer::Stack(b)); record.set_register(local_register_index, register); + + ThreadSignal::Continue } pub fn add(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { let InstructionData { - a, - b, - c, + a_field: a, + b_field: b, + c_field: c, b_is_constant, c_is_constant, .. @@ -206,13 +239,15 @@ pub fn add(instruction_data: InstructionData, record: &mut Record) -> ThreadSign let register = Register::Value(sum); record.set_register(a, register); + + ThreadSignal::Continue } pub fn subtract(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { let InstructionData { - a, - b, - c, + a_field: a, + b_field: b, + c_field: c, b_is_constant, c_is_constant, .. @@ -231,13 +266,15 @@ pub fn subtract(instruction_data: InstructionData, record: &mut Record) -> Threa let register = Register::Value(difference); record.set_register(a, register); + + ThreadSignal::Continue } pub fn multiply(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { let InstructionData { - a, - b, - c, + a_field: a, + b_field: b, + c_field: c, b_is_constant, c_is_constant, .. @@ -256,13 +293,15 @@ pub fn multiply(instruction_data: InstructionData, record: &mut Record) -> Threa let register = Register::Value(product); record.set_register(a, register); + + ThreadSignal::Continue } pub fn divide(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { let InstructionData { - a, - b, - c, + a_field: a, + b_field: b, + c_field: c, b_is_constant, c_is_constant, .. @@ -281,13 +320,15 @@ pub fn divide(instruction_data: InstructionData, record: &mut Record) -> ThreadS let register = Register::Value(quotient); record.set_register(a, register); + + ThreadSignal::Continue } pub fn modulo(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { let InstructionData { - a, - b, - c, + a_field: a, + b_field: b, + c_field: c, b_is_constant, c_is_constant, .. @@ -306,13 +347,15 @@ pub fn modulo(instruction_data: InstructionData, record: &mut Record) -> ThreadS let register = Register::Value(remainder); record.set_register(a, register); + + ThreadSignal::Continue } pub fn test(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { let InstructionData { - b, + b_field: b, b_is_constant, - c, + c_field: c, .. } = instruction_data; let value = record.get_argument(b, b_is_constant); @@ -324,16 +367,17 @@ pub fn test(instruction_data: InstructionData, record: &mut Record) -> ThreadSig let test_value = c != 0; if boolean == test_value { - record.ip += 2; - } else { + record.ip += 1; } + + ThreadSignal::Continue } pub fn test_set(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { let InstructionData { - a, - b, - c, + a_field: a, + b_field: b, + c_field: c, b_is_constant, .. } = instruction_data; @@ -346,7 +390,7 @@ pub fn test_set(instruction_data: InstructionData, record: &mut Record) -> Threa let test_value = c != 0; if boolean == test_value { - record.ip += 2; + record.ip += 1; } else { let pointer = if b_is_constant { Pointer::Constant(b) @@ -357,15 +401,17 @@ pub fn test_set(instruction_data: InstructionData, record: &mut Record) -> Threa record.set_register(a, register); } + + ThreadSignal::Continue } pub fn equal(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { let InstructionData { - b, - c, + b_field: b, + c_field: c, b_is_constant, c_is_constant, - d, + d_field: d, .. } = instruction_data; let left = record.get_argument(b, b_is_constant); @@ -373,18 +419,19 @@ pub fn equal(instruction_data: InstructionData, record: &mut Record) -> ThreadSi let is_equal = left == right; if is_equal == d { - record.ip += 2; - } else { + record.ip += 1; } + + ThreadSignal::Continue } pub fn less(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { let InstructionData { - b, - c, + b_field: b, + c_field: c, b_is_constant, c_is_constant, - d, + d_field: d, .. } = instruction_data; let left = record.get_argument(b, b_is_constant); @@ -392,18 +439,19 @@ pub fn less(instruction_data: InstructionData, record: &mut Record) -> ThreadSig let is_less = left < right; if is_less == d { - record.ip += 2; - } else { + record.ip += 1; } + + ThreadSignal::Continue } pub fn less_equal(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { let InstructionData { - b, - c, + b_field: b, + c_field: c, b_is_constant, c_is_constant, - d, + d_field: d, .. } = instruction_data; let left = record.get_argument(b, b_is_constant); @@ -411,15 +459,16 @@ pub fn less_equal(instruction_data: InstructionData, record: &mut Record) -> Thr let is_less_or_equal = left <= right; if is_less_or_equal == d { - record.ip += 2; - } else { + record.ip += 1; } + + ThreadSignal::Continue } pub fn negate(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { let InstructionData { - a, - b, + a_field: a, + b_field: b, b_is_constant, .. } = instruction_data; @@ -428,12 +477,14 @@ pub fn negate(instruction_data: InstructionData, record: &mut Record) -> ThreadS let register = Register::Value(negated); record.set_register(a, register); + + ThreadSignal::Continue } pub fn not(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { let InstructionData { - a, - b, + a_field: a, + b_field: b, b_is_constant, .. } = instruction_data; @@ -445,10 +496,16 @@ pub fn not(instruction_data: InstructionData, record: &mut Record) -> ThreadSign let register = Register::Value(Value::Concrete(not)); record.set_register(a, register); + + ThreadSignal::Continue } pub fn jump(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { - let InstructionData { b, c, .. } = instruction_data; + let InstructionData { + b_field: b, + c_field: c, + .. + } = instruction_data; let offset = b as usize; let is_positive = c != 0; @@ -457,88 +514,55 @@ pub fn jump(instruction_data: InstructionData, record: &mut Record) -> ThreadSig } else { record.ip -= offset } + + ThreadSignal::Continue } pub fn call(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { - let InstructionData { a, b, c, .. } = instruction_data; - let prototype = record.get_prototype(b); + let Call { + destination, + function_register, + argument_count, + } = instruction_data.into(); + let function_value = record.open_register(function_register); + let function = match function_value { + Value::Function(function) => function, + _ => panic!( + "{}", + VmError::ExpectedFunction { + value: function_value.clone() + } + ), + }; - let first_argument_index = a - c; - let mut argument_index = 0; - - for argument_register_index in first_argument_index..a { - let target_register_is_empty = matches!( - record.stack[argument_register_index as usize], - Register::Empty - ); - - if target_register_is_empty { - continue; - } - - function_record.set_register( - argument_index as u8, - Register::Pointer(Pointer::ParentStack(argument_register_index)), - ); - - argument_index += 1; - } - - let return_value = function_record.run(); - - if let Some(concrete_value) = return_value { - let register = Register::Value(concrete_value); - - record.set_register(a, register); - } + ThreadSignal::Call(FunctionCall { + record_index: function.record_index, + return_register: destination, + argument_count, + }) } pub fn call_native(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { - let InstructionData { a, b, c, .. } = instruction_data; - let first_argument_index = (a - c) as usize; - let argument_range = first_argument_index..a as usize; - let mut arguments: SmallVec<[&Value; 4]> = SmallVec::new(); + let CallNative { + destination, + function, + argument_count, + } = instruction_data.into(); + let first_argument_index = destination - argument_count; + let argument_range = first_argument_index..destination; - for register_index in argument_range { - let register = &record.stack[register_index]; - let value = match register { - Register::Value(value) => value, - Register::Pointer(pointer) => { - let value_option = record.follow_pointer_allow_empty(*pointer); + let function = NativeFunction::from(function); + let thread_signal = function + .call(record, Some(destination), argument_range) + .unwrap_or_else(|error| panic!("{error:?}")); - match value_option { - Some(value) => value, - None => continue, - } - } - Register::Empty => continue, - }; - - arguments.push(value); - } - - let function = NativeFunction::from(b); - let return_value = function.call(record.arguments).unwrap(); - - if let Some(value) = return_value { - let register = Register::Value(value); - - record.set_register(a, register); - } + thread_signal } -pub fn r#return(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal { - let should_return_value = instruction_data.b != 0; +pub fn r#return(instruction_data: InstructionData, _: &mut Record) -> ThreadSignal { + let should_return_value = instruction_data.b_field != 0; - if !should_return_value {} - - if let Some(register_index) = &record.last_assigned_register { - let return_value = record.open_register(*register_index).clone(); - - record.return_value = Some(return_value); - } else { - panic!("Stack underflow"); - } + ThreadSignal::Return(should_return_value) } #[cfg(test)] diff --git a/dust-lang/src/vm/thread.rs b/dust-lang/src/vm/thread.rs index 7ee3f2b..9b685c5 100644 --- a/dust-lang/src/vm/thread.rs +++ b/dust-lang/src/vm/thread.rs @@ -1,16 +1,15 @@ -use crate::{Chunk, Value}; +use std::mem::swap; -use super::{call_stack::CallStack, record::Record, runner::RunAction, FunctionCall, VmError}; +use crate::{vm::Register, Chunk, Value}; + +use super::{record::Record, runner::RunAction, CallStack, FunctionCall, VmError}; fn create_records(chunk: Chunk, records: &mut Vec) { let (_, _, instructions, positions, constants, locals, prototypes, stack_size) = chunk.take_data(); - let actions = instructions - .into_iter() - .map(|instruction| RunAction::from(instruction)) - .collect(); + let actions = instructions.into_iter().map(RunAction::from).collect(); let record = Record::new( - Vec::with_capacity(stack_size), + vec![Register::Empty; stack_size], constants, locals, actions, @@ -27,27 +26,23 @@ fn create_records(chunk: Chunk, records: &mut Vec) { pub struct Thread { call_stack: CallStack, records: Vec, - return_register: Option, } impl Thread { pub fn new(chunk: Chunk) -> Self { - let call_stack = CallStack::new(); - let mut records = Vec::with_capacity(chunk.prototypes().len()); + let call_stack = CallStack::with_capacity(chunk.prototypes().len() + 1); + let mut records = Vec::with_capacity(chunk.prototypes().len() + 1); create_records(chunk, &mut records); Thread { call_stack, records, - return_register: None, } } pub fn run(&mut self) -> Option { - assert!(!self.call_stack.is_empty()); - - let mut record = &mut self.records[0]; + let (record, remaining_records) = self.records.split_first_mut().unwrap(); loop { assert!( @@ -60,25 +55,46 @@ impl Thread { ); let action = record.actions[record.ip]; - let signal = (action.logic)(action.data, &mut record); + let signal = (action.logic)(action.data, record); match signal { ThreadSignal::Continue => { record.ip += 1; } - ThreadSignal::Call(FunctionCall { - record_index, - return_register, - .. - }) => { - record = &mut self.records[record_index]; - self.return_register = Some(return_register); + ThreadSignal::Call(function_call) => { + swap(record, &mut remaining_records[function_call.record_index]); + self.call_stack.push(function_call); } - ThreadSignal::Return(value_option) => { - let outer_call = self.call_stack.pop(); + ThreadSignal::Return(should_return_value) => { + let returning_call = match self.call_stack.pop() { + Some(function_call) => function_call, + None => { + if should_return_value { + return record.last_assigned_register().map(|register| { + record.replace_register_or_clone_constant( + register, + Register::Empty, + ) + }); + } else { + return None; + } + } + }; + let outer_call = self.call_stack.last_or_panic(); - if outer_call.is_none() { - return value_option; + if should_return_value { + let return_register = record + .last_assigned_register() + .unwrap_or_else(|| panic!("Expected return value")); + let value = record + .replace_register_or_clone_constant(return_register, Register::Empty); + + swap(record, &mut remaining_records[outer_call.record_index]); + + record.set_register(returning_call.return_register, Register::Value(value)); + } else { + swap(record, &mut remaining_records[outer_call.record_index]); } } } @@ -89,5 +105,5 @@ impl Thread { pub enum ThreadSignal { Continue, Call(FunctionCall), - Return(Option), + Return(bool), }