1
0

Make runtime improvements

This commit is contained in:
Jeff 2025-01-08 04:21:01 -05:00
parent 4473ea5b23
commit e9bd9b37b0
45 changed files with 523 additions and 916 deletions

View File

@ -46,19 +46,19 @@ write_line(fib(25))
## Goals
This project's goal is to deliver a language that not only *works* but that offers genunine value
due to a unique combination of design choices and a high-quality implementation. As mentioned in the
first sentence, Dust's general aspirations are to be **fast**, **safe** and **easy**.
This project's goal is to deliver a language with features that stand out due to a combination of
design choices and a high-quality implementation. As mentioned in the first sentence, Dust's general
aspirations are to be **fast**, **safe** and **easy**.
- **Easy**
- **Simple Syntax** Dust should be easier to learn than most programming languages. Its syntax
should be familiar to users of other C-like languages to the point that even a new user can read
Dust code and understand what it does. Rather than being dumbed down by a lack of features, Dust
Dust code and understand what it does. Rather than being held back by a lack of features, Dust
should be powerful and elegant in its simplicity, seeking a maximum of capability with a minimum
of complexity. When advanced features are added, they should never obstruct existing features,
including readability. Even the advanced type system should be clear and unintimidating.
- **Excellent Errors** Dust should provide helpful error messages that guide the user to the
source of the problem and suggest a solution. Errors should be a helpful learning ressource for
source of the problem and suggest a solution. Errors should be a helpful learning resource for
users rather than a source of frustration.
- **Relevant Documentation** Users should have the resources they need to learn Dust and write
code in it. They should know where to look for answers and how to reach out for help.
@ -68,7 +68,7 @@ first sentence, Dust's general aspirations are to be **fast**, **safe** and **ea
typed language, users should feel confident in the type-consistency of their code and not want
to go back to a dynamically typed language.
- **Memory Safety** Dust should be free of memory bugs. Being implemented in Rust makes this easy
but, to accomodate long-running programs, Dust still requires a memory management strategy.
but, to accommodate long-running programs, Dust still requires a memory management strategy.
Dust's design is to use a separate thread for garbage collection, allowing the main thread to
continue executing code while the garbage collector looks for unused memory.
- **Fast**
@ -76,7 +76,7 @@ first sentence, Dust's general aspirations are to be **fast**, **safe** and **ea
executing quickly. The compilation time should feel negligible to the user.
- **Fast Execution** Dust should be generally faster than Python, Ruby and NodeJS. It should be
competitive with highly optimized, modern, register-based VM languages like Lua. Dust should
be benchmarked during development to inform decisions about performance.
be bench tested during development to inform decisions about performance.
- **Low Resource Usage** Despite its performance, Dust's use of memory and CPU power should be
conservative and predictable enough to accomodate a wide range of devices.
@ -94,10 +94,10 @@ the family but Rust is its primary point of reference for syntax. Rust was chose
because its imperative code is *obvious by design* and *widely familiar*. Those qualities are
aligned with Dust's emphasis on usability.
However, some differences exist. Dust *evaluates* all of the code in the file while Rust only
initiates from a "main" function. Dust's execution model is more like one found in a scripting
language. If we put `42 + 42 == 84` into a file and run it, it will return `true` because the outer
context is, in a sense, the "main" function.
However, some differences exist. Dust *evaluates* all the code in the file while Rust only initiates
from a "main" function. Dust's execution model is more like one found in a scripting language. If we
put `42 + 42 == 84` into a file and run it, it will return `true` because the outer context is, in a
sense, the "main" function.
So while the syntax is by no means compatible, it is superficially similar, even to the point that
syntax highlighting for Rust code works well with Dust code. This is not a design goal but a happy

View File

@ -1,10 +0,0 @@
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `../../target/release/dust addictive_addition.ds` | 99.1 ± 3.6 | 95.4 | 110.6 | 9.46 ± 0.53 |
| `node addictive_addition.js` | 45.4 ± 0.5 | 44.4 | 46.4 | 4.33 ± 0.19 |
| `deno addictive_addition.js` | 24.0 ± 2.7 | 18.4 | 39.0 | 2.29 ± 0.28 |
| `bun addictive_addition.js` | 10.5 ± 0.5 | 8.9 | 11.6 | 1.00 |
| `python addictive_addition.py` | 231.9 ± 27.2 | 213.6 | 310.9 | 22.14 ± 2.76 |
| `lua addictive_addition.lua` | 21.9 ± 0.3 | 21.7 | 23.7 | 2.09 ± 0.10 |
| `ruby addictive_addition.rb` | 83.5 ± 3.9 | 76.6 | 92.4 | 7.97 ± 0.51 |
| `java addictive_addition.java` | 199.6 ± 5.6 | 194.8 | 214.4 | 19.06 ± 0.98 |

View File

@ -1,6 +0,0 @@
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `../../target/release/dust recursion.ds` | 2.0 ± 0.2 | 1.9 | 2.9 | 1.00 |
| `node recursion.js` | 42.4 ± 0.9 | 41.3 | 45.5 | 21.11 ± 1.68 |
| `deno recursion.js` | 21.2 ± 1.7 | 17.3 | 23.9 | 10.57 ± 1.15 |
| `bun recursion.js` | 8.3 ± 0.3 | 7.3 | 9.7 | 4.13 ± 0.36 |

View File

@ -223,7 +223,8 @@ fn main() {
if let Mode::Disassemble { style, name, input } = mode {
let (source, file_name) = get_source_and_file_name(input);
let lexer = Lexer::new(&source);
let mut compiler = match Compiler::new(lexer) {
let program_name = name.or(file_name);
let mut compiler = match Compiler::new(lexer, program_name) {
Ok(compiler) => compiler,
Err(error) => {
handle_compile_error(error, &source);
@ -241,7 +242,7 @@ fn main() {
}
}
let chunk = compiler.finish(name.or(file_name));
let chunk = compiler.finish();
let mut stdout = stdout().lock();
chunk
@ -310,7 +311,8 @@ fn main() {
{
let (source, file_name) = get_source_and_file_name(input);
let lexer = Lexer::new(&source);
let mut compiler = match Compiler::new(lexer) {
let program_name = name.or(file_name);
let mut compiler = match Compiler::new(lexer, program_name) {
Ok(compiler) => compiler,
Err(error) => {
handle_compile_error(error, &source);
@ -328,7 +330,7 @@ fn main() {
}
}
let chunk = compiler.finish(name.or(file_name));
let chunk = compiler.finish();
let compile_end = start_time.elapsed();
let vm = Vm::new(chunk);

View File

@ -1,7 +1,7 @@
use std::time::Duration;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use dust_lang::run;
use dust_lang::{run, DustString};
const SOURCE: &str = r"
let mut i = 0
@ -12,7 +12,7 @@ const SOURCE: &str = r"
";
fn addictive_addition(source: &str) {
let _ = run(source).unwrap();
run(Some(DustString::from("addictive_addition")), source).unwrap();
}
fn criterion_benchmark(c: &mut Criterion) {

View File

@ -1,7 +1,7 @@
use std::time::Duration;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use dust_lang::run;
use dust_lang::{run, DustString};
const SOURCE: &str = r"
fn fib (n: int) -> int {
@ -15,7 +15,7 @@ const SOURCE: &str = r"
";
fn addictive_addition(source: &str) {
let _ = run(source).unwrap();
run(Some(DustString::from("fibonacci")), source).unwrap();
}
fn criterion_benchmark(c: &mut Criterion) {

View File

@ -277,10 +277,10 @@ impl<'a, W: Write> Disassembler<'a, W> {
self.write_center_border(&column_name_line)?;
self.write_center_border(INSTRUCTION_BORDERS[1])?;
for (index, instruction) in self.chunk.instructions().iter().enumerate() {
for (index, instruction) in self.chunk.instructions.iter().enumerate() {
let position = self
.chunk
.positions()
.positions
.get(index)
.map(|position| position.to_string())
.unwrap_or("stripped".to_string());
@ -317,11 +317,11 @@ impl<'a, W: Write> Disassembler<'a, W> {
scope,
is_mutable,
},
) in self.chunk.locals().iter().enumerate()
) in self.chunk.locals.iter().enumerate()
{
let identifier_display = self
.chunk
.constants()
.constants
.get(*identifier_index as usize)
.map(|value| value.to_string())
.unwrap_or_else(|| "unknown".to_string());
@ -352,7 +352,7 @@ impl<'a, W: Write> Disassembler<'a, W> {
self.write_center_border(&column_name_line)?;
self.write_center_border(CONSTANT_BORDERS[1])?;
for (index, value) in self.chunk.constants().iter().enumerate() {
for (index, value) in self.chunk.constants.iter().enumerate() {
let type_display = value.r#type().to_string();
let value_display = {
let mut value_string = value.to_string();

View File

@ -1,14 +1,13 @@
//! Representation of a Dust program or function.
//!
//! A chunk is output by the compiler to represent all of the information needed to execute a Dust
//! A chunk is output by the compiler to represent all the information needed to execute a Dust
//! program. In addition to the program itself, each function in the source is compiled into its own
//! chunk and stored in the `prototypes` field of its parent. Thus, a chunk is also the
//! representation of a function prototype, i.e. a function declaration, as opposed to an individual
//! instance.
//! chunk and stored in the `prototypes` field of its parent. Thus, a chunk can also represent a
//! function prototype.
//!
//! Chunks have a name when they belong to a named function. They also have a type, so the input
//! parameters and the type of the return value are statically known. The [`Chunk::stack_size`]
//! method can provide the necessary stack size that will be needed by the virtual machine. Chunks
//! field can provide the necessary stack size that will be needed by the virtual machine. Chunks
//! cannot be instantiated directly and must be created by the compiler. However, when the Rust
//! compiler is in the "test" configuration (used for all types of test), [`Chunk::with_data`] can
//! be used to create a chunk for comparison to the compiler output. Do not try to run these chunks
@ -27,7 +26,6 @@ use std::io::Write;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use crate::vm::{Record, RunAction};
use crate::{DustString, Function, FunctionType, Instruction, Span, Value};
/// Representation of a Dust program or function.
@ -35,45 +33,20 @@ use crate::{DustString, Function, FunctionType, Instruction, Span, Value};
/// See the [module-level documentation](index.html) for more information.
#[derive(Clone, PartialOrd, Serialize, Deserialize)]
pub struct Chunk {
name: Option<DustString>,
r#type: FunctionType,
pub(crate) name: Option<DustString>,
pub(crate) r#type: FunctionType,
instructions: SmallVec<[Instruction; 32]>,
positions: SmallVec<[Span; 32]>,
constants: SmallVec<[Value; 16]>,
locals: SmallVec<[Local; 8]>,
prototypes: Vec<Chunk>,
pub(crate) instructions: SmallVec<[Instruction; 32]>,
pub(crate) positions: SmallVec<[Span; 32]>,
pub(crate) constants: SmallVec<[Value; 16]>,
pub(crate) locals: SmallVec<[Local; 8]>,
pub(crate) prototypes: Vec<Chunk>,
stack_size: usize,
record_index: u8,
pub(crate) stack_size: usize,
pub(crate) prototype_index: u8,
}
impl Chunk {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
name: Option<DustString>,
r#type: FunctionType,
instructions: impl Into<SmallVec<[Instruction; 32]>>,
positions: impl Into<SmallVec<[Span; 32]>>,
constants: impl Into<SmallVec<[Value; 16]>>,
locals: impl Into<SmallVec<[Local; 8]>>,
prototypes: impl Into<Vec<Chunk>>,
stack_size: usize,
record_index: u8,
) -> Self {
Self {
name,
r#type,
instructions: instructions.into(),
positions: positions.into(),
constants: constants.into(),
locals: locals.into(),
prototypes: prototypes.into(),
stack_size,
record_index,
}
}
#[cfg(any(test, debug_assertions))]
pub fn with_data(
name: Option<DustString>,
@ -93,7 +66,7 @@ impl Chunk {
locals: locals.into(),
prototypes,
stack_size: 0,
record_index: 0,
prototype_index: 0,
}
}
@ -101,63 +74,10 @@ impl Chunk {
Function {
name: self.name.clone(),
r#type: self.r#type.clone(),
record_index: self.record_index,
prototype_index: self.prototype_index,
}
}
pub fn into_records(self, records: &mut Vec<Record>) {
let actions = self.instructions().iter().map(RunAction::from).collect();
let record = Record::new(
actions,
None,
self.name,
self.r#type,
self.positions,
self.constants,
self.locals,
self.stack_size,
self.record_index,
);
records.push(record);
for chunk in self.prototypes {
chunk.into_records(records);
}
}
pub fn name(&self) -> Option<&DustString> {
self.name.as_ref()
}
pub fn r#type(&self) -> &FunctionType {
&self.r#type
}
pub fn instructions(&self) -> &SmallVec<[Instruction; 32]> {
&self.instructions
}
pub fn positions(&self) -> &SmallVec<[Span; 32]> {
&self.positions
}
pub fn constants(&self) -> &SmallVec<[Value; 16]> {
&self.constants
}
pub fn locals(&self) -> &SmallVec<[Local; 8]> {
&self.locals
}
pub fn prototypes(&self) -> &Vec<Chunk> {
&self.prototypes
}
pub fn stack_size(&self) -> usize {
self.stack_size
}
pub fn disassembler<'a, W: Write>(&'a self, writer: &'a mut W) -> Disassembler<'a, W> {
Disassembler::new(self, writer)
}

View File

@ -231,6 +231,15 @@ impl AnnotatedError for CompileError {
)
]
}
Self::ReturnTypeConflict { conflict, position } => {
smallvec![(
format!(
"Expected type {} but found type {}",
conflict.expected, conflict.actual
),
*position
)]
}
_ => SmallVec::new(),
}
}

View File

@ -50,16 +50,16 @@ use crate::{
///
/// assert_eq!(chunk.instructions().len(), 3);
/// ```
pub fn compile(source: &str) -> Result<Chunk, DustError> {
pub fn compile(program_name: Option<DustString>, source: &str) -> Result<Chunk, DustError> {
let lexer = Lexer::new(source);
let mut compiler = Compiler::new(lexer).map_err(|error| DustError::compile(error, source))?;
compiler.is_main_chunk = true;
let mut compiler =
Compiler::new(lexer, program_name).map_err(|error| DustError::compile(error, source))?;
compiler
.compile()
.map_err(|error| DustError::compile(error, source))?;
let chunk = compiler.finish(None::<DustString>);
let chunk = compiler.finish();
Ok(chunk)
}
@ -112,21 +112,12 @@ pub struct Compiler<'src> {
/// incremented when a new block is entered.
block_index: u8,
/// The current scope of the compiler. This is used to test if a variable is in scope.
/// The current block scope of the compiler. This is used to test if a variable is in scope.
current_scope: Scope,
/// Index of the [`Record`] 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, its `next_record_index`
/// is assigned back to this chunk.
next_record_index: u8,
/// Whether the compiler is compiling the main chunk.
is_main_chunk: bool,
/// Index of the Chunk in its parent's prototype list. This is set to 0 for the main chunk but
/// that value is never read because the main chunk is not a callable function.
prototype_index: u8,
current_token: Token<'src>,
current_position: Span,
@ -135,12 +126,15 @@ pub struct Compiler<'src> {
}
impl<'src> Compiler<'src> {
/// Creates a new compiler with the given lexer.
pub fn new(mut lexer: Lexer<'src>) -> Result<Self, CompileError> {
/// Creates a new top-level compiler with the given lexer.
pub fn new(
mut lexer: Lexer<'src>,
function_name: Option<DustString>,
) -> Result<Self, CompileError> {
let (current_token, current_position) = lexer.next_token()?;
Ok(Compiler {
function_name: None,
function_name,
r#type: FunctionType {
type_parameters: None,
value_parameters: None,
@ -155,9 +149,7 @@ impl<'src> Compiler<'src> {
minimum_register: 0,
block_index: 0,
current_scope: Scope::default(),
record_index: 0,
next_record_index: 1,
is_main_chunk: true,
prototype_index: 0,
current_token,
current_position,
previous_token: Token::Eof,
@ -165,39 +157,6 @@ impl<'src> Compiler<'src> {
})
}
/// Creates a new chunk with the compiled data, optionally assigning a name to the chunk.
///
/// Note for maintainers: Do not give a name when compiling functions, only the main chunk. This
/// will allow [`Compiler::function_name`] to be both the name used for recursive calls and the
/// name of the function when it is compiled. The name can later be seen in the VM's call stack.
pub fn finish(self, name: Option<impl Into<DustString>>) -> Chunk {
let (instructions, positions): (SmallVec<[Instruction; 32]>, SmallVec<[Span; 32]>) = self
.instructions
.into_iter()
.map(|(instruction, _, position)| (instruction, position))
.unzip();
let locals = self
.locals
.into_iter()
.map(|(local, _)| local)
.collect::<SmallVec<[Local; 8]>>();
let chunk_name = name
.map(|into_name| into_name.into())
.or(self.function_name);
Chunk::new(
chunk_name,
self.r#type,
instructions,
positions,
self.constants,
locals,
self.prototypes,
self.stack_size,
self.record_index,
)
}
/// Compiles the source (which is in the lexer) while checking for errors and returning a
/// [`CompileError`] if any are found. After calling this function, check its return value for
/// an error, then call [`Compiler::finish`] to get the compiled chunk.
@ -230,6 +189,36 @@ impl<'src> Compiler<'src> {
Ok(())
}
/// Creates a new chunk with the compiled data, optionally assigning a name to the chunk.
///
/// Note for maintainers: Do not give a name when compiling functions, only the main chunk. This
/// will allow [`Compiler::function_name`] to be both the name used for recursive calls and the
/// name of the function when it is compiled. The name can later be seen in the VM's call stack.
pub fn finish(self) -> Chunk {
let (instructions, positions): (SmallVec<[Instruction; 32]>, SmallVec<[Span; 32]>) = self
.instructions
.into_iter()
.map(|(instruction, _, position)| (instruction, position))
.unzip();
let locals = self
.locals
.into_iter()
.map(|(local, _)| local)
.collect::<SmallVec<[Local; 8]>>();
Chunk {
name: self.function_name,
r#type: self.r#type,
instructions,
positions,
constants: self.constants,
locals,
prototypes: self.prototypes,
stack_size: self.stack_size,
prototype_index: self.prototype_index,
}
}
fn is_eof(&self) -> bool {
matches!(self.current_token, Token::Eof)
}
@ -417,7 +406,7 @@ impl<'src> Compiler<'src> {
let operation = instruction.operation();
if let Operation::LOAD_LIST = operation {
let LoadList { start_register, .. } = LoadList::from(instruction);
let LoadList { start_register, .. } = LoadList::from(*instruction);
let item_type = self.get_register_type(start_register)?;
return Ok(Type::List(Box::new(item_type)));
@ -442,22 +431,13 @@ impl<'src> Compiler<'src> {
///
/// 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 !self.is_main_chunk {
Type::function(self.r#type.clone())
.check(&new_return_type)
.map_err(|conflict| CompileError::ReturnTypeConflict {
conflict,
position: self.current_position,
})?;
}
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,
position: self.previous_position,
})?;
}
@ -749,7 +729,7 @@ impl<'src> Compiler<'src> {
})?;
let (left, push_back_left) = self.handle_binary_argument(&left_instruction)?;
let left_is_mutable_local = if let Operation::GET_LOCAL = left_instruction.operation() {
let GetLocal { local_index, .. } = GetLocal::from(&left_instruction);
let GetLocal { local_index, .. } = GetLocal::from(left_instruction);
self.locals
.get(local_index as usize)
@ -1020,7 +1000,7 @@ impl<'src> Compiler<'src> {
return self.parse_call_native(native_function);
} else if self.function_name.as_deref() == Some(identifier) {
let destination = self.next_register();
let load_function = Instruction::load_function(destination, self.record_index);
let load_function = Instruction::load_function(destination, self.prototype_index);
self.emit_instruction(load_function, Type::SelfFunction, start_position);
@ -1172,7 +1152,7 @@ impl<'src> Compiler<'src> {
self.advance()?;
self.parse_expression()?;
let emit_test_and_jump = if matches!(
if matches!(
self.get_last_operations(),
Some([
Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL,
@ -1183,13 +1163,8 @@ impl<'src> Compiler<'src> {
) {
self.instructions.pop();
self.instructions.pop();
false
self.instructions.pop();
} else {
true
};
if emit_test_and_jump {
let operand_register = self.next_register() - 1;
let test = Instruction::test(operand_register, true);
@ -1212,7 +1187,6 @@ impl<'src> Compiler<'src> {
let if_block_end = self.instructions.len();
let mut if_block_distance = (if_block_end - if_block_start) as u8;
let if_block_type = self.get_last_instruction_type();
// let if_last_register = self.next_register().saturating_sub(1);
if let Token::Else = self.current_token {
self.advance()?;
@ -1275,22 +1249,12 @@ impl<'src> Compiler<'src> {
}
}
if emit_test_and_jump {
let jump = Instruction::jump(if_block_distance, true);
self.instructions
.insert(if_block_start, (jump, Type::None, if_block_start_position));
}
let jump = Instruction::jump(if_block_distance, true);
self.instructions
.insert(if_block_start, (jump, Type::None, if_block_start_position));
control_flow_register_consolidation(self);
// let else_last_register = self.next_register().saturating_sub(1);
// let point = Instruction::point(else_last_register, if_last_register);
// if if_last_register < else_last_register {
// self.emit_instruction(point, else_block_type, self.current_position);
// }
Ok(())
}
@ -1456,10 +1420,10 @@ impl<'src> Compiler<'src> {
let Jump {
offset,
is_positive,
} = Jump::from(&*instruction);
} = Jump::from(*instruction);
let offset = offset as usize;
if is_positive && offset + index == instruction_length - 2 {
if is_positive && offset + index == instruction_length - 1 {
*instruction = Instruction::jump((offset + 1) as u8, true);
}
}
@ -1469,7 +1433,14 @@ impl<'src> Compiler<'src> {
}
fn parse_implicit_return(&mut self) -> Result<(), CompileError> {
if self.allow(Token::Semicolon)? {
if matches!(self.get_last_operation(), Some(Operation::RETURN))
|| matches!(
self.get_last_operations(),
Some([Operation::RETURN, Operation::JUMP])
)
{
// Do nothing if the last instruction is a return or a return followed by a jump
} else if self.allow(Token::Semicolon)? {
let r#return = Instruction::r#return(false, 0);
self.emit_instruction(r#return, Type::None, self.current_position);
@ -1503,10 +1474,10 @@ impl<'src> Compiler<'src> {
let Jump {
offset,
is_positive,
} = Jump::from(&*instruction);
} = Jump::from(*instruction);
let offset = offset as usize;
if is_positive && offset + index == instruction_length - 2 {
if is_positive && offset + index == instruction_length - 1 {
*instruction = Instruction::jump((offset + 1) as u8, true);
}
}
@ -1564,24 +1535,30 @@ impl<'src> Compiler<'src> {
fn parse_function(&mut self) -> Result<(), CompileError> {
let function_start = self.current_position.0;
let mut function_compiler = Compiler::new(self.lexer)?;
function_compiler.record_index = self.next_record_index;
function_compiler.next_record_index = self.next_record_index + 1;
self.advance()?;
let identifier_info = if let Token::Identifier(text) = function_compiler.current_token {
let position = function_compiler.current_position;
let identifier = if let Token::Identifier(text) = self.current_token {
self.advance()?;
function_compiler.advance()?;
function_compiler.function_name = Some(text.into());
Some((text, position))
Some(text)
} else {
None
};
function_compiler.expect(Token::LeftParenthesis)?;
let mut function_compiler = if self.current_token == Token::LeftParenthesis {
let function_name = identifier.map(DustString::from);
Compiler::new(self.lexer, function_name)? // This will consume the left parenthesis
} else {
return Err(CompileError::ExpectedToken {
expected: TokenKind::LeftParenthesis,
found: self.current_token.to_owned(),
position: self.current_position,
});
};
function_compiler.prototype_index = self.prototypes.len() as u8;
let mut value_parameters: Option<SmallVec<[(u8, Type); 4]>> = None;
@ -1656,18 +1633,17 @@ 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.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 record_index = function_compiler.record_index;
let chunk = function_compiler.finish(None::<&str>);
let prototype_index = function_compiler.prototype_index;
let chunk = function_compiler.finish();
let destination = self.next_register();
self.prototypes.push(chunk);
if let Some((identifier, _)) = identifier_info {
if let Some(identifier) = identifier {
self.declare_local(
identifier,
destination,
@ -1677,7 +1653,7 @@ impl<'src> Compiler<'src> {
);
}
let load_function = Instruction::load_function(destination, record_index);
let load_function = Instruction::load_function(destination, prototype_index);
self.emit_instruction(
load_function,

View File

@ -6,8 +6,8 @@ pub struct Add {
pub right: Argument,
}
impl From<&Instruction> for Add {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for Add {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let (left, right) = instruction.b_and_c_as_arguments();

View File

@ -1,15 +1,13 @@
use crate::{Instruction, Operation};
use super::InstructionData;
pub struct Call {
pub destination: u8,
pub function_register: u8,
pub argument_count: u8,
}
impl From<&Instruction> for Call {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for Call {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let function_register = instruction.b_field();
let argument_count = instruction.c_field();
@ -22,20 +20,6 @@ impl From<&Instruction> for Call {
}
}
impl From<InstructionData> 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,
}
}
}
impl From<Call> for Instruction {
fn from(call: Call) -> Self {
let a = call.destination;

View File

@ -1,15 +1,13 @@
use crate::{Instruction, NativeFunction, Operation};
use super::InstructionData;
pub struct CallNative {
pub destination: u8,
pub function: NativeFunction,
pub argument_count: u8,
}
impl From<&Instruction> for CallNative {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for CallNative {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let function = NativeFunction::from(instruction.b_field());
@ -21,16 +19,6 @@ impl From<&Instruction> for CallNative {
}
}
impl From<InstructionData> 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<CallNative> for Instruction {
fn from(call_native: CallNative) -> Self {
let operation = Operation::CALL_NATIVE;

View File

@ -1,14 +1,12 @@
use crate::{Instruction, Operation};
use super::InstructionData;
pub struct Close {
pub from: u8,
pub to: u8,
}
impl From<&Instruction> for Close {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for Close {
fn from(instruction: Instruction) -> Self {
Close {
from: instruction.b_field(),
to: instruction.c_field(),
@ -16,15 +14,6 @@ impl From<&Instruction> for Close {
}
}
impl From<InstructionData> for Close {
fn from(instruction: InstructionData) -> Self {
Close {
from: instruction.b_field,
to: instruction.c_field,
}
}
}
impl From<Close> for Instruction {
fn from(close: Close) -> Self {
let operation = Operation::CLOSE;

View File

@ -6,8 +6,8 @@ pub struct Divide {
pub right: Argument,
}
impl From<&Instruction> for Divide {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for Divide {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let (left, right) = instruction.b_and_c_as_arguments();

View File

@ -6,8 +6,8 @@ pub struct Equal {
pub right: Argument,
}
impl From<&Instruction> for Equal {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for Equal {
fn from(instruction: Instruction) -> Self {
let value = instruction.d_field();
let (left, right) = instruction.b_and_c_as_arguments();

View File

@ -1,27 +1,12 @@
use crate::{Instruction, Operation};
use super::InstructionData;
pub struct GetLocal {
pub destination: u8,
pub local_index: u8,
}
impl From<InstructionData> for GetLocal {
fn from(data: InstructionData) -> Self {
let InstructionData {
a_field, b_field, ..
} = data;
GetLocal {
destination: a_field,
local_index: b_field,
}
}
}
impl From<&Instruction> for GetLocal {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for GetLocal {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let local_index = instruction.b_field();

View File

@ -5,8 +5,8 @@ pub struct Jump {
pub is_positive: bool,
}
impl From<&Instruction> for Jump {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for Jump {
fn from(instruction: Instruction) -> Self {
Jump {
offset: instruction.b_field(),
is_positive: instruction.c_field() != 0,

View File

@ -6,8 +6,8 @@ pub struct Less {
pub right: Argument,
}
impl From<&Instruction> for Less {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for Less {
fn from(instruction: Instruction) -> Self {
let value = instruction.d_field();
let (left, right) = instruction.b_and_c_as_arguments();

View File

@ -6,8 +6,8 @@ pub struct LessEqual {
pub right: Argument,
}
impl From<&Instruction> for LessEqual {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for LessEqual {
fn from(instruction: Instruction) -> Self {
let value = instruction.d_field();
let (left, right) = instruction.b_and_c_as_arguments();

View File

@ -1,23 +1,17 @@
use crate::{Instruction, Operation};
use super::InstructionData;
pub struct LoadBoolean {
pub destination: u8,
pub value: bool,
pub jump_next: bool,
}
impl From<InstructionData> for LoadBoolean {
fn from(instruction: InstructionData) -> Self {
let destination = instruction.a_field;
let value = instruction.b_field != 0;
let jump_next = instruction.c_field != 0;
impl From<Instruction> for LoadBoolean {
fn from(instruction: Instruction) -> Self {
LoadBoolean {
destination,
value,
jump_next,
destination: instruction.a_field(),
value: instruction.b_field() != 0,
jump_next: instruction.c_field() != 0,
}
}
}

View File

@ -2,16 +2,14 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionData;
pub struct LoadConstant {
pub destination: u8,
pub constant_index: u8,
pub jump_next: bool,
}
impl From<&Instruction> for LoadConstant {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for LoadConstant {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let constant_index = instruction.b_field();
let jump_next = instruction.c_field() != 0;
@ -24,20 +22,6 @@ impl From<&Instruction> for LoadConstant {
}
}
impl From<InstructionData> for LoadConstant {
fn from(instruction: InstructionData) -> Self {
let destination = instruction.a_field;
let constant_index = instruction.b_field;
let jump_next = instruction.c_field != 0;
LoadConstant {
destination,
constant_index,
jump_next,
}
}
}
impl From<LoadConstant> for Instruction {
fn from(load_constant: LoadConstant) -> Self {
let operation = Operation::LOAD_CONSTANT;

View File

@ -1,29 +1,20 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionData, Operation};
use super::{Instruction, Operation};
pub struct LoadFunction {
pub destination: u8,
pub record_index: u8,
pub prototype_index: u8,
}
impl From<&Instruction> for LoadFunction {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for LoadFunction {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let record_index = instruction.b_field();
LoadFunction {
destination,
record_index,
}
}
}
impl From<InstructionData> for LoadFunction {
fn from(instruction: InstructionData) -> Self {
LoadFunction {
destination: instruction.a_field,
record_index: instruction.b_field,
prototype_index: record_index,
}
}
}
@ -33,7 +24,7 @@ impl From<LoadFunction> for Instruction {
Instruction::new(
Operation::LOAD_FUNCTION,
load_function.destination,
load_function.record_index,
load_function.prototype_index,
0,
false,
false,
@ -44,6 +35,6 @@ impl From<LoadFunction> for Instruction {
impl Display for LoadFunction {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "R{} = F{}", self.destination, self.record_index)
write!(f, "R{} = F{}", self.destination, self.prototype_index)
}
}

View File

@ -1,14 +1,12 @@
use crate::{Instruction, Operation};
use super::InstructionData;
pub struct LoadList {
pub destination: u8,
pub start_register: u8,
}
impl From<&Instruction> for LoadList {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for LoadList {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let start_register = instruction.b_field();
@ -19,18 +17,6 @@ impl From<&Instruction> for LoadList {
}
}
impl From<InstructionData> for LoadList {
fn from(instruction: InstructionData) -> Self {
let destination = instruction.a_field;
let start_register = instruction.b_field;
LoadList {
destination,
start_register,
}
}
}
impl From<LoadList> for Instruction {
fn from(load_list: LoadList) -> Self {
let operation = Operation::LOAD_LIST;

View File

@ -1,27 +1,17 @@
use crate::{Instruction, Operation};
use super::InstructionData;
pub struct LoadSelf {
pub destination: u8,
}
impl From<&Instruction> for LoadSelf {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for LoadSelf {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
LoadSelf { destination }
}
}
impl From<InstructionData> for LoadSelf {
fn from(instruction: InstructionData) -> Self {
let destination = instruction.a_field;
LoadSelf { destination }
}
}
impl From<LoadSelf> for Instruction {
fn from(load_self: LoadSelf) -> Self {
let operation = Operation::LOAD_SELF;

View File

@ -255,10 +255,10 @@ impl Instruction {
})
}
pub fn load_function(destination: u8, record_index: u8) -> Instruction {
pub fn load_function(destination: u8, prototype_index: u8) -> Instruction {
Instruction::from(LoadFunction {
destination,
record_index,
prototype_index,
})
}
@ -510,12 +510,12 @@ impl Instruction {
}
pub fn disassembly_info(&self) -> String {
let (operation, data) = self.decode();
let operation = self.operation();
match operation {
Operation::POINT => Point::from(data).to_string(),
Operation::POINT => Point::from(*self).to_string(),
Operation::CLOSE => {
let Close { from, to } = Close::from(data);
let Close { from, to } = Close::from(*self);
format!("R{from}..R{to}")
}
@ -524,7 +524,7 @@ impl Instruction {
destination,
value,
jump_next,
} = LoadBoolean::from(data);
} = LoadBoolean::from(*self);
if jump_next {
format!("R{destination} = {value} && JUMP +1")
@ -537,7 +537,7 @@ impl Instruction {
destination,
constant_index,
jump_next,
} = LoadConstant::from(self);
} = LoadConstant::from(*self);
if jump_next {
format!("R{destination} = C{constant_index} JUMP +1")
@ -545,18 +545,18 @@ impl Instruction {
format!("R{destination} = C{constant_index}")
}
}
Operation::LOAD_FUNCTION => LoadFunction::from(self).to_string(),
Operation::LOAD_FUNCTION => LoadFunction::from(*self).to_string(),
Operation::LOAD_LIST => {
let LoadList {
destination,
start_register,
} = LoadList::from(self);
} = LoadList::from(*self);
let end_register = destination.saturating_sub(1);
format!("R{destination} = [R{start_register}..=R{end_register}]",)
}
Operation::LOAD_SELF => {
let LoadSelf { destination } = LoadSelf::from(self);
let LoadSelf { destination } = LoadSelf::from(*self);
format!("R{destination} = self")
}
@ -564,7 +564,7 @@ impl Instruction {
let GetLocal {
destination,
local_index,
} = GetLocal::from(self);
} = GetLocal::from(*self);
format!("R{destination} = L{local_index}")
}
@ -572,7 +572,7 @@ impl Instruction {
let SetLocal {
register_index,
local_index,
} = SetLocal::from(self);
} = SetLocal::from(*self);
format!("L{local_index} = R{register_index}")
}
@ -581,7 +581,7 @@ impl Instruction {
destination,
left,
right,
} = Add::from(self);
} = Add::from(*self);
format!("R{destination} = {left} + {right}")
}
@ -590,7 +590,7 @@ impl Instruction {
destination,
left,
right,
} = Subtract::from(self);
} = Subtract::from(*self);
format!("R{destination} = {left} - {right}")
}
@ -599,7 +599,7 @@ impl Instruction {
destination,
left,
right,
} = Multiply::from(self);
} = Multiply::from(*self);
format!("R{destination} = {left} * {right}")
}
@ -608,7 +608,7 @@ impl Instruction {
destination,
left,
right,
} = Divide::from(self);
} = Divide::from(*self);
format!("R{destination} = {left} / {right}")
}
@ -617,7 +617,7 @@ impl Instruction {
destination,
left,
right,
} = Modulo::from(self);
} = Modulo::from(*self);
format!("R{destination} = {left} % {right}")
}
@ -625,7 +625,7 @@ impl Instruction {
let Test {
operand_register,
test_value,
} = Test::from(self);
} = Test::from(*self);
let bang = if test_value { "" } else { "!" };
format!("if {bang}R{operand_register} {{ JUMP +1 }}",)
@ -635,25 +635,25 @@ impl Instruction {
destination,
argument,
test_value,
} = TestSet::from(self);
} = TestSet::from(*self);
let bang = if test_value { "" } else { "!" };
format!("if {bang}{argument} {{ JUMP +1 }} else {{ R{destination} = {argument} }}")
}
Operation::EQUAL => {
let Equal { value, left, right } = Equal::from(self);
let Equal { value, left, right } = Equal::from(*self);
let comparison_symbol = if value { "==" } else { "!=" };
format!("if {left} {comparison_symbol} {right} {{ JUMP +1 }}")
}
Operation::LESS => {
let Less { value, left, right } = Less::from(self);
let Less { value, left, right } = Less::from(*self);
let comparison_symbol = if value { "<" } else { ">=" };
format!("if {left} {comparison_symbol} {right} {{ JUMP +1 }}")
}
Operation::LESS_EQUAL => {
let LessEqual { value, left, right } = LessEqual::from(self);
let LessEqual { value, left, right } = LessEqual::from(*self);
let comparison_symbol = if value { "<=" } else { ">" };
format!("if {left} {comparison_symbol} {right} {{ JUMP +1 }}")
@ -662,7 +662,7 @@ impl Instruction {
let Negate {
destination,
argument,
} = Negate::from(self);
} = Negate::from(*self);
format!("R{destination} = -{argument}")
}
@ -670,7 +670,7 @@ impl Instruction {
let Not {
destination,
argument,
} = Not::from(self);
} = Not::from(*self);
format!("R{destination} = !{argument}")
}
@ -678,7 +678,7 @@ impl Instruction {
let Jump {
offset,
is_positive,
} = Jump::from(self);
} = Jump::from(*self);
if is_positive {
format!("JUMP +{offset}")
@ -691,7 +691,7 @@ impl Instruction {
destination,
function_register,
argument_count,
} = Call::from(self);
} = Call::from(*self);
let arguments_start = destination.saturating_sub(argument_count);
match argument_count {
@ -709,7 +709,7 @@ impl Instruction {
destination,
function,
argument_count,
} = CallNative::from(self);
} = CallNative::from(*self);
let arguments_start = destination.saturating_sub(argument_count);
let arguments_end = arguments_start + argument_count;
let mut info_string = if function.returns_value() {
@ -734,7 +734,7 @@ impl Instruction {
let Return {
should_return_value,
return_register,
} = Return::from(self);
} = Return::from(*self);
if should_return_value {
format!("RETURN R{return_register}")

View File

@ -6,8 +6,8 @@ pub struct Modulo {
pub right: Argument,
}
impl From<&Instruction> for Modulo {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for Modulo {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let (left, right) = instruction.b_and_c_as_arguments();

View File

@ -6,8 +6,8 @@ pub struct Multiply {
pub right: Argument,
}
impl From<&Instruction> for Multiply {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for Multiply {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let (left, right) = instruction.b_and_c_as_arguments();

View File

@ -5,8 +5,8 @@ pub struct Negate {
pub argument: Argument,
}
impl From<&Instruction> for Negate {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for Negate {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let argument = instruction.b_as_argument();

View File

@ -5,8 +5,8 @@ pub struct Not {
pub argument: Argument,
}
impl From<&Instruction> for Not {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for Not {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let argument = instruction.b_as_argument();

View File

@ -2,15 +2,13 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionData;
pub struct Point {
pub from: u8,
pub to: u8,
}
impl From<&Instruction> for Point {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for Point {
fn from(instruction: Instruction) -> Self {
Point {
from: instruction.b_field(),
to: instruction.c_field(),
@ -18,15 +16,6 @@ impl From<&Instruction> for Point {
}
}
impl From<InstructionData> for Point {
fn from(instruction: InstructionData) -> Self {
Point {
from: instruction.b_field,
to: instruction.c_field,
}
}
}
impl Display for Point {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let Point { from, to } = self;

View File

@ -1,27 +1,12 @@
use crate::{Instruction, Operation};
use super::InstructionData;
pub struct Return {
pub should_return_value: bool,
pub return_register: u8,
}
impl From<InstructionData> for Return {
fn from(data: InstructionData) -> Self {
let InstructionData {
b_field, c_field, ..
} = data;
Return {
should_return_value: b_field != 0,
return_register: c_field,
}
}
}
impl From<&Instruction> for Return {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for Return {
fn from(instruction: Instruction) -> Self {
let should_return_value = instruction.b_field() != 0;
let return_register = instruction.c_field();

View File

@ -5,8 +5,8 @@ pub struct SetLocal {
pub local_index: u8,
}
impl From<&Instruction> for SetLocal {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for SetLocal {
fn from(instruction: Instruction) -> Self {
let register_index = instruction.b_field();
let local_index = instruction.c_field();

View File

@ -6,8 +6,8 @@ pub struct Subtract {
pub right: Argument,
}
impl From<&Instruction> for Subtract {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for Subtract {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let (left, right) = instruction.b_and_c_as_arguments();

View File

@ -5,8 +5,8 @@ pub struct Test {
pub test_value: bool,
}
impl From<&Instruction> for Test {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for Test {
fn from(instruction: Instruction) -> Self {
let operand_register = instruction.b_field();
let test_value = instruction.c_field() != 0;

View File

@ -6,8 +6,8 @@ pub struct TestSet {
pub test_value: bool,
}
impl From<&Instruction> for TestSet {
fn from(instruction: &Instruction) -> Self {
impl From<Instruction> for TestSet {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let argument = instruction.b_as_argument();
let test_value = instruction.c_field() != 0;

View File

@ -64,7 +64,6 @@ impl ConcreteValue {
}
}
#[inline(always)]
pub fn add(&self, other: &Self) -> ConcreteValue {
use ConcreteValue::*;
@ -120,22 +119,21 @@ impl ConcreteValue {
}
}
pub fn subtract(&self, other: &Self) -> Result<ConcreteValue, ValueError> {
pub fn subtract(&self, other: &Self) -> ConcreteValue {
use ConcreteValue::*;
let difference = match (self, other) {
match (self, other) {
(Byte(left), Byte(right)) => ConcreteValue::Byte(left.saturating_sub(*right)),
(Float(left), Float(right)) => ConcreteValue::Float(left - right),
(Integer(left), Integer(right)) => ConcreteValue::Integer(left.saturating_sub(*right)),
_ => {
return Err(ValueError::CannotSubtract(
self.clone().to_value(),
other.clone().to_value(),
))
}
};
Ok(difference)
_ => panic!(
"{}",
ValueError::CannotSubtract(
Value::Concrete(self.clone()),
Value::Concrete(other.clone())
)
),
}
}
pub fn multiply(&self, other: &Self) -> Result<ConcreteValue, ValueError> {
@ -217,30 +215,30 @@ impl ConcreteValue {
Ok(not)
}
pub fn equal(&self, other: &ConcreteValue) -> Result<ConcreteValue, ValueError> {
pub fn equals(&self, other: &ConcreteValue) -> bool {
use ConcreteValue::*;
let equal = match (self, other) {
(Boolean(left), Boolean(right)) => ConcreteValue::Boolean(left == right),
(Byte(left), Byte(right)) => ConcreteValue::Boolean(left == right),
(Character(left), Character(right)) => ConcreteValue::Boolean(left == right),
(Float(left), Float(right)) => ConcreteValue::Boolean(left == right),
(Integer(left), Integer(right)) => ConcreteValue::Boolean(left == right),
(List(left), List(right)) => ConcreteValue::Boolean(left == right),
(Range(left), Range(right)) => ConcreteValue::Boolean(left == right),
(String(left), String(right)) => ConcreteValue::Boolean(left == right),
match (self, other) {
(Boolean(left), Boolean(right)) => left == right,
(Byte(left), Byte(right)) => left == right,
(Character(left), Character(right)) => left == right,
(Float(left), Float(right)) => left == right,
(Integer(left), Integer(right)) => left == right,
(List(left), List(right)) => left == right,
(Range(left), Range(right)) => left == right,
(String(left), String(right)) => left == right,
_ => {
return Err(ValueError::CannotCompare(
self.clone().to_value(),
other.clone().to_value(),
))
panic!(
"{}",
ValueError::CannotCompare(
Value::Concrete(self.clone()),
Value::Concrete(other.clone())
)
)
}
};
Ok(equal)
}
}
#[inline(always)]
pub fn less_than(&self, other: &ConcreteValue) -> Result<ConcreteValue, ValueError> {
use ConcreteValue::*;
@ -264,7 +262,7 @@ impl ConcreteValue {
Ok(less_than)
}
pub fn less_than_or_equal(&self, other: &ConcreteValue) -> Result<ConcreteValue, ValueError> {
pub fn less_than_or_equals(&self, other: &ConcreteValue) -> Result<ConcreteValue, ValueError> {
use ConcreteValue::*;
let less_than_or_equal = match (self, other) {

View File

@ -8,7 +8,7 @@ use super::DustString;
pub struct Function {
pub name: Option<DustString>,
pub r#type: FunctionType,
pub record_index: u8,
pub prototype_index: u8,
}
impl Display for Function {

View File

@ -85,24 +85,24 @@ impl Value {
}
pub fn add(&self, other: &Value) -> Value {
let concrete = match (self, other) {
let sum = match (self, other) {
(Value::Concrete(left), Value::Concrete(right)) => left.add(right),
_ => panic!("{}", ValueError::CannotAdd(self.clone(), other.clone())),
};
Value::Concrete(concrete)
Value::Concrete(sum)
}
pub fn subtract(&self, other: &Value) -> Result<Value, ValueError> {
match (self, other) {
(Value::Concrete(left), Value::Concrete(right)) => {
left.subtract(right).map(Value::Concrete)
}
_ => Err(ValueError::CannotSubtract(
self.to_owned(),
other.to_owned(),
)),
}
pub fn subtract(&self, other: &Value) -> Value {
let difference = match (self, other) {
(Value::Concrete(left), Value::Concrete(right)) => left.subtract(right),
_ => panic!(
"{}",
ValueError::CannotSubtract(self.clone(), other.clone())
),
};
Value::Concrete(difference)
}
pub fn multiply(&self, other: &Value) -> Result<Value, ValueError> {
@ -151,12 +151,13 @@ impl Value {
}
}
pub fn equal(&self, other: &Value) -> Result<Value, ValueError> {
pub fn equals(&self, other: &Value) -> bool {
match (self, other) {
(Value::Concrete(left), Value::Concrete(right)) => {
left.equal(right).map(Value::Concrete)
}
_ => Err(ValueError::CannotCompare(self.to_owned(), other.to_owned())),
(Value::Concrete(left), Value::Concrete(right)) => left.equals(right),
_ => panic!(
"{}",
ValueError::CannotCompare(self.to_owned(), other.to_owned())
),
}
}
@ -169,10 +170,10 @@ impl Value {
}
}
pub fn less_equal(&self, other: &Value) -> Result<Value, ValueError> {
pub fn less_than_or_equals(&self, other: &Value) -> Result<Value, ValueError> {
match (self, other) {
(Value::Concrete(left), Value::Concrete(right)) => {
left.less_than_or_equal(right).map(Value::Concrete)
left.less_than_or_equals(right).map(Value::Concrete)
}
_ => Err(ValueError::CannotCompare(self.to_owned(), other.to_owned())),
}

View File

@ -81,18 +81,17 @@ impl Display for CallStack {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
writeln!(f, "-- DUST CALL STACK --")?;
for function_call in &self.calls {
for function_call in self.calls.iter().rev() {
writeln!(f, "{function_call}")?;
}
Ok(())
writeln!(f, "--")
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct FunctionCall {
pub name: Option<DustString>,
pub record_index: u8,
pub return_register: u8,
pub ip: usize,
}
@ -101,7 +100,6 @@ impl Display for FunctionCall {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let FunctionCall {
name,
record_index,
return_register,
..
} = self;
@ -110,9 +108,6 @@ impl Display for FunctionCall {
.map(|name| name.as_str())
.unwrap_or("anonymous");
write!(
f,
"{name} (Record: {record_index}, Return register: {return_register})"
)
write!(f, "{name} (Return register: {return_register})")
}
}

View File

@ -18,10 +18,10 @@ pub use run_action::RunAction;
pub use thread::{Thread, ThreadSignal};
use tracing::{span, Level};
use crate::{compile, Chunk, DustError, Value};
use crate::{compile, Chunk, DustError, DustString, Value};
pub fn run(source: &str) -> Result<Option<Value>, DustError> {
let chunk = compile(source)?;
pub fn run(program_name: Option<DustString>, source: &str) -> Result<Option<Value>, DustError> {
let chunk = compile(program_name, source)?;
let vm = Vm::new(chunk);
Ok(vm.run())

View File

@ -1,63 +1,30 @@
use std::mem::replace;
use smallvec::SmallVec;
use tracing::trace;
use crate::{DustString, Function, FunctionType, Local, Span, Value};
use crate::{Argument, Chunk, DustString, Function, Span, Value};
use super::{run_action::RunAction, Pointer, Register};
use super::{Pointer, Register};
#[derive(Debug)]
pub struct Record {
pub struct Record<'a> {
pub ip: usize,
pub actions: SmallVec<[RunAction; 32]>,
pub chunk: &'a Chunk,
stack: Vec<Register>,
last_assigned_register: Option<u8>,
name: Option<DustString>,
r#type: FunctionType,
positions: SmallVec<[Span; 32]>,
constants: SmallVec<[Value; 16]>,
locals: SmallVec<[Local; 8]>,
index: u8,
}
impl Record {
impl<'a> Record<'a> {
#[allow(clippy::too_many_arguments)]
pub fn new(
actions: SmallVec<[RunAction; 32]>,
last_assigned_register: Option<u8>,
name: Option<DustString>,
r#type: FunctionType,
positions: SmallVec<[Span; 32]>,
constants: SmallVec<[Value; 16]>,
locals: SmallVec<[Local; 8]>,
stack_size: usize,
index: u8,
) -> Self {
pub fn new(chunk: &'a Chunk) -> Self {
Self {
ip: 0,
actions,
stack: vec![Register::Empty; stack_size],
last_assigned_register,
name,
r#type,
positions,
constants,
locals,
index,
stack: vec![Register::Empty; chunk.stack_size],
chunk,
}
}
pub fn name(&self) -> Option<&DustString> {
self.name.as_ref()
}
pub fn index(&self) -> u8 {
self.index
self.chunk.name.as_ref()
}
pub fn stack_size(&self) -> usize {
@ -65,19 +32,11 @@ impl Record {
}
pub fn current_position(&self) -> Span {
self.positions[self.ip]
}
pub fn last_assigned_register(&self) -> Option<u8> {
self.last_assigned_register
self.chunk.positions[self.ip]
}
pub fn as_function(&self) -> Function {
Function {
name: self.name.clone(),
r#type: self.r#type.clone(),
record_index: self.index,
}
self.chunk.as_function()
}
pub(crate) fn follow_pointer(&self, pointer: Pointer) -> &Value {
@ -103,7 +62,6 @@ impl Record {
}
pub fn set_register(&mut self, to_register: u8, register: Register) {
self.last_assigned_register = Some(to_register);
let to_register = to_register as usize;
assert!(
@ -215,11 +173,10 @@ impl Record {
}
/// DRY helper to get a value from an Argument
pub fn get_argument(&self, index: u8, is_constant: bool) -> &Value {
if is_constant {
self.get_constant(index)
} else {
self.open_register(index)
pub fn get_argument(&self, argument: Argument) -> &Value {
match argument {
Argument::Constant(constant_index) => self.get_constant(constant_index),
Argument::Register(register_index) => self.open_register(register_index),
}
}
@ -227,21 +184,21 @@ impl Record {
let constant_index = constant_index as usize;
assert!(
constant_index < self.constants.len(),
constant_index < self.chunk.constants.len(),
"VM Error: Constant index out of bounds"
);
&self.constants[constant_index]
&self.chunk.constants[constant_index]
}
pub fn get_local_register(&self, local_index: u8) -> u8 {
let local_index = local_index as usize;
assert!(
local_index < self.locals.len(),
local_index < self.chunk.locals.len(),
"VM Error: Local index out of bounds"
);
self.locals[local_index].register_index
self.chunk.locals[local_index].register_index
}
}

View File

@ -2,11 +2,11 @@ use tracing::trace;
use crate::{
instruction::{
Call, CallNative, Close, GetLocal, LoadBoolean, LoadConstant, LoadFunction, LoadList,
LoadSelf, Point, Return,
Add, Call, CallNative, Close, Divide, Equal, GetLocal, Jump, Less, LessEqual, LoadBoolean,
LoadConstant, LoadFunction, LoadList, LoadSelf, Modulo, Multiply, Negate, Not, Point,
Return, SetLocal, Subtract, Test, TestSet,
},
vm::VmError,
AbstractList, ConcreteValue, Instruction, InstructionData, Type, Value,
AbstractList, Argument, ConcreteValue, Instruction, Type, Value,
};
use super::{thread::ThreadSignal, Pointer, Record, Register};
@ -14,31 +14,22 @@ use super::{thread::ThreadSignal, Pointer, Record, Register};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct RunAction {
pub logic: RunnerLogic,
pub data: InstructionData,
}
impl From<&Instruction> for RunAction {
fn from(instruction: &Instruction) -> Self {
let (operation, data) = instruction.decode();
let logic = RUNNER_LOGIC_TABLE[operation.0 as usize];
RunAction { logic, data }
}
pub instruction: Instruction,
}
impl From<Instruction> for RunAction {
fn from(instruction: Instruction) -> Self {
let (operation, data) = instruction.decode();
let operation = instruction.operation();
let logic = RUNNER_LOGIC_TABLE[operation.0 as usize];
RunAction { logic, data }
RunAction { logic, instruction }
}
}
pub type RunnerLogic = fn(InstructionData, &mut Record) -> ThreadSignal;
pub type RunnerLogic = fn(Instruction, &mut Record) -> ThreadSignal;
pub const RUNNER_LOGIC_TABLE: [RunnerLogic; 25] = [
r#move,
point,
close,
load_boolean,
load_constant,
@ -65,8 +56,8 @@ pub const RUNNER_LOGIC_TABLE: [RunnerLogic; 25] = [
r#return,
];
pub fn r#move(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let Point { from, to } = instruction_data.into();
pub fn point(instruction: Instruction, record: &mut Record) -> ThreadSignal {
let Point { from, to } = instruction.into();
let from_register = record.get_register(from);
let from_register_is_empty = matches!(from_register, Register::Empty);
@ -79,8 +70,8 @@ pub fn r#move(instruction_data: InstructionData, record: &mut Record) -> ThreadS
ThreadSignal::Continue
}
pub fn close(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let Close { from, to } = instruction_data.into();
pub fn close(instruction: Instruction, record: &mut Record) -> ThreadSignal {
let Close { from, to } = instruction.into();
assert!(from < to, "Runtime Error: Malformed instruction");
@ -96,12 +87,12 @@ pub fn close(instruction_data: InstructionData, record: &mut Record) -> ThreadSi
ThreadSignal::Continue
}
pub fn load_boolean(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
pub fn load_boolean(instruction: Instruction, record: &mut Record) -> ThreadSignal {
let LoadBoolean {
destination,
value,
jump_next,
} = instruction_data.into();
} = instruction.into();
let boolean = Value::Concrete(ConcreteValue::Boolean(value));
let register = Register::Value(boolean);
@ -114,12 +105,12 @@ pub fn load_boolean(instruction_data: InstructionData, record: &mut Record) -> T
ThreadSignal::Continue
}
pub fn load_constant(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
pub fn load_constant(instruction: Instruction, record: &mut Record) -> ThreadSignal {
let LoadConstant {
destination,
constant_index,
jump_next,
} = instruction_data.into();
} = instruction.into();
let register = Register::Pointer(Pointer::Constant(constant_index));
trace!("Load constant {constant_index} into R{destination}");
@ -133,11 +124,11 @@ pub fn load_constant(instruction_data: InstructionData, record: &mut Record) ->
ThreadSignal::Continue
}
pub fn load_list(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
pub fn load_list(instruction: Instruction, record: &mut Record) -> ThreadSignal {
let LoadList {
destination,
start_register,
} = instruction_data.into();
} = instruction.into();
let mut item_pointers = Vec::with_capacity((destination - start_register) as usize);
let mut item_type = Type::Any;
@ -172,20 +163,20 @@ pub fn load_list(instruction_data: InstructionData, record: &mut Record) -> Thre
ThreadSignal::Continue
}
pub fn load_function(instruction_data: InstructionData, _: &mut Record) -> ThreadSignal {
pub fn load_function(instruction: Instruction, _: &mut Record) -> ThreadSignal {
let LoadFunction {
destination,
record_index,
} = instruction_data.into();
prototype_index,
} = instruction.into();
ThreadSignal::LoadFunction {
from_record_index: record_index,
to_register_index: destination,
destination,
prototype_index,
}
}
pub fn load_self(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let LoadSelf { destination } = instruction_data.into();
pub fn load_self(instruction: Instruction, record: &mut Record) -> ThreadSignal {
let LoadSelf { destination } = instruction.into();
let function = record.as_function();
let register = Register::Value(Value::Function(function));
@ -194,11 +185,11 @@ pub fn load_self(instruction_data: InstructionData, record: &mut Record) -> Thre
ThreadSignal::Continue
}
pub fn get_local(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
pub fn get_local(instruction: Instruction, record: &mut Record) -> ThreadSignal {
let GetLocal {
destination,
local_index,
} = instruction_data.into();
} = instruction.into();
let local_register_index = record.get_local_register(local_index);
let register = Register::Pointer(Pointer::Stack(local_register_index));
@ -207,77 +198,59 @@ pub fn get_local(instruction_data: InstructionData, record: &mut Record) -> Thre
ThreadSignal::Continue
}
pub fn set_local(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
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));
pub fn set_local(instruction: Instruction, record: &mut Record) -> ThreadSignal {
let SetLocal {
register_index,
local_index,
} = instruction.into();
let local_register_index = record.get_local_register(local_index);
let register = Register::Pointer(Pointer::Stack(register_index));
record.set_register(local_register_index, register);
ThreadSignal::Continue
}
pub fn add(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
a_field: a,
b_field: b,
c_field: c,
b_is_constant,
c_is_constant,
..
} = instruction_data;
let left = record.get_argument(b, b_is_constant);
let right = record.get_argument(c, c_is_constant);
pub fn add(instruction: Instruction, record: &mut Record) -> ThreadSignal {
let Add {
destination,
left,
right,
} = instruction.into();
let left = record.get_argument(left);
let right = record.get_argument(right);
let sum = left.add(right);
let register = Register::Value(sum);
record.set_register(a, register);
record.set_register(destination, register);
ThreadSignal::Continue
}
pub fn subtract(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
a_field: a,
b_field: b,
c_field: c,
b_is_constant,
c_is_constant,
..
} = instruction_data;
let left = record.get_argument(b, b_is_constant);
let right = record.get_argument(c, c_is_constant);
let difference = match (left, right) {
(Value::Concrete(left), Value::Concrete(right)) => match (left, right) {
(ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => {
ConcreteValue::Integer(left - right).to_value()
}
_ => panic!("Value Error: Cannot subtract values {left} and {right}"),
},
_ => panic!("Value Error: Cannot subtract values {left} and {right}"),
};
pub fn subtract(instruction: Instruction, record: &mut Record) -> ThreadSignal {
let Subtract {
destination,
left,
right,
} = instruction.into();
let left = record.get_argument(left);
let right = record.get_argument(right);
let difference = left.subtract(right);
let register = Register::Value(difference);
record.set_register(a, register);
record.set_register(destination, register);
ThreadSignal::Continue
}
pub fn multiply(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
a_field: a,
b_field: b,
c_field: c,
b_is_constant,
c_is_constant,
..
} = instruction_data;
let left = record.get_argument(b, b_is_constant);
let right = record.get_argument(c, c_is_constant);
pub fn multiply(instruction: Instruction, record: &mut Record) -> ThreadSignal {
let Multiply {
destination,
left,
right,
} = instruction.into();
let left = record.get_argument(left);
let right = record.get_argument(right);
let product = match (left, right) {
(Value::Concrete(left), Value::Concrete(right)) => match (left, right) {
(ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => {
@ -289,22 +262,19 @@ pub fn multiply(instruction_data: InstructionData, record: &mut Record) -> Threa
};
let register = Register::Value(product);
record.set_register(a, register);
record.set_register(destination, register);
ThreadSignal::Continue
}
pub fn divide(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
a_field: a,
b_field: b,
c_field: c,
b_is_constant,
c_is_constant,
..
} = instruction_data;
let left = record.get_argument(b, b_is_constant);
let right = record.get_argument(c, c_is_constant);
pub fn divide(instruction: Instruction, record: &mut Record) -> ThreadSignal {
let Divide {
destination,
left,
right,
} = instruction.into();
let left = record.get_argument(left);
let right = record.get_argument(right);
let quotient = match (left, right) {
(Value::Concrete(left), Value::Concrete(right)) => match (left, right) {
(ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => {
@ -316,22 +286,19 @@ pub fn divide(instruction_data: InstructionData, record: &mut Record) -> ThreadS
};
let register = Register::Value(quotient);
record.set_register(a, register);
record.set_register(destination, register);
ThreadSignal::Continue
}
pub fn modulo(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
a_field: a,
b_field: b,
c_field: c,
b_is_constant,
c_is_constant,
..
} = instruction_data;
let left = record.get_argument(b, b_is_constant);
let right = record.get_argument(c, c_is_constant);
pub fn modulo(instruction: Instruction, record: &mut Record) -> ThreadSignal {
let Modulo {
destination,
left,
right,
} = instruction.into();
let left = record.get_argument(left);
let right = record.get_argument(right);
let remainder = match (left, right) {
(Value::Concrete(left), Value::Concrete(right)) => match (left, right) {
(ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => {
@ -343,22 +310,22 @@ pub fn modulo(instruction_data: InstructionData, record: &mut Record) -> ThreadS
};
let register = Register::Value(remainder);
record.set_register(a, register);
record.set_register(destination, register);
ThreadSignal::Continue
}
pub fn test(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
b_field, c_field, ..
} = instruction_data;
let value = record.open_register(b_field);
pub fn test(instruction: Instruction, record: &mut Record) -> ThreadSignal {
let Test {
operand_register,
test_value,
} = instruction.into();
let value = record.open_register(operand_register);
let boolean = if let Value::Concrete(ConcreteValue::Boolean(boolean)) = value {
*boolean
} else {
panic!("VM Error: Expected boolean value for TEST operation",);
};
let test_value = c_field != 0;
if boolean == test_value {
record.ip += 1;
@ -367,141 +334,110 @@ pub fn test(instruction_data: InstructionData, record: &mut Record) -> ThreadSig
ThreadSignal::Continue
}
pub fn test_set(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
a_field: a,
b_field: b,
c_field: c,
b_is_constant,
..
} = instruction_data;
let value = record.get_argument(b, b_is_constant);
pub fn test_set(instruction: Instruction, record: &mut Record) -> ThreadSignal {
let TestSet {
destination,
argument,
test_value,
} = instruction.into();
let value = record.get_argument(argument);
let boolean = if let Value::Concrete(ConcreteValue::Boolean(boolean)) = value {
*boolean
} else {
panic!("VM Error: Expected boolean value for TEST_SET operation",);
};
let test_value = c != 0;
if boolean == test_value {
record.ip += 1;
} else {
let pointer = if b_is_constant {
Pointer::Constant(b)
} else {
Pointer::Stack(b)
let pointer = match argument {
Argument::Constant(constant_index) => Pointer::Constant(constant_index),
Argument::Register(register_index) => Pointer::Stack(register_index),
};
let register = Register::Pointer(pointer);
record.set_register(a, register);
record.set_register(destination, register);
}
ThreadSignal::Continue
}
pub fn equal(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
b_field: b,
c_field: c,
b_is_constant,
c_is_constant,
d_field: d,
..
} = instruction_data;
let left = record.get_argument(b, b_is_constant);
let right = record.get_argument(c, c_is_constant);
let is_equal = left == right;
pub fn equal(instruction: Instruction, record: &mut Record) -> ThreadSignal {
let Equal { value, left, right } = instruction.into();
let left = record.get_argument(left);
let right = record.get_argument(right);
let is_equal = left.equals(right);
if is_equal == d {
if is_equal == value {
record.ip += 1;
}
ThreadSignal::Continue
}
pub fn less(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
b_field: b,
c_field: c,
b_is_constant,
c_is_constant,
d_field: d,
..
} = instruction_data;
let left = record.get_argument(b, b_is_constant);
let right = record.get_argument(c, c_is_constant);
pub fn less(instruction: Instruction, record: &mut Record) -> ThreadSignal {
let Less { value, left, right } = instruction.into();
let left = record.get_argument(left);
let right = record.get_argument(right);
let is_less = left < right;
if is_less == d {
if is_less == value {
record.ip += 1;
}
ThreadSignal::Continue
}
pub fn less_equal(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
b_field: b,
c_field: c,
b_is_constant,
c_is_constant,
d_field: d,
..
} = instruction_data;
let left = record.get_argument(b, b_is_constant);
let right = record.get_argument(c, c_is_constant);
pub fn less_equal(instruction: Instruction, record: &mut Record) -> ThreadSignal {
let LessEqual { value, left, right } = instruction.into();
let left = record.get_argument(left);
let right = record.get_argument(right);
let is_less_or_equal = left <= right;
if is_less_or_equal == d {
if is_less_or_equal == value {
record.ip += 1;
}
ThreadSignal::Continue
}
pub fn negate(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
a_field: a,
b_field: b,
b_is_constant,
..
} = instruction_data;
let argument = record.get_argument(b, b_is_constant);
pub fn negate(instruction: Instruction, record: &mut Record) -> ThreadSignal {
let Negate {
destination,
argument,
} = instruction.into();
let argument = record.get_argument(argument);
let negated = argument.negate();
let register = Register::Value(negated);
record.set_register(a, register);
record.set_register(destination, register);
ThreadSignal::Continue
}
pub fn not(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
a_field: a,
b_field: b,
b_is_constant,
..
} = instruction_data;
let argument = record.get_argument(b, b_is_constant);
pub fn not(instruction: Instruction, record: &mut Record) -> ThreadSignal {
let Not {
destination,
argument,
} = instruction.into();
let argument = record.get_argument(argument);
let not = match argument {
Value::Concrete(ConcreteValue::Boolean(boolean)) => ConcreteValue::Boolean(!boolean),
_ => panic!("VM Error: Expected boolean value for NOT operation"),
};
let register = Register::Value(Value::Concrete(not));
record.set_register(a, register);
record.set_register(destination, register);
ThreadSignal::Continue
}
pub fn jump(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
b_field: b,
c_field: c,
..
} = instruction_data;
let offset = b as usize;
let is_positive = c != 0;
pub fn jump(instruction: Instruction, record: &mut Record) -> ThreadSignal {
let Jump {
offset,
is_positive,
} = instruction.into();
let offset = offset as usize;
if is_positive {
record.ip += offset;
@ -512,36 +448,26 @@ pub fn jump(instruction_data: InstructionData, record: &mut Record) -> ThreadSig
ThreadSignal::Continue
}
pub fn call(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
pub fn call(instruction: Instruction, _: &mut Record) -> ThreadSignal {
let Call {
destination,
destination: return_register,
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()
}
),
};
} = instruction.into();
ThreadSignal::Call {
record_index: function.record_index,
return_register: destination,
function_register,
return_register,
argument_count,
}
}
pub fn call_native(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
pub fn call_native(instruction: Instruction, record: &mut Record) -> ThreadSignal {
let CallNative {
destination,
function,
argument_count,
} = instruction_data.into();
} = instruction.into();
let first_argument_index = destination - argument_count;
let argument_range = first_argument_index..destination;
@ -550,11 +476,11 @@ pub fn call_native(instruction_data: InstructionData, record: &mut Record) -> Th
.unwrap_or_else(|error| panic!("{error:?}"))
}
pub fn r#return(instruction_data: InstructionData, _: &mut Record) -> ThreadSignal {
pub fn r#return(instruction: Instruction, _: &mut Record) -> ThreadSignal {
let Return {
should_return_value,
return_register,
} = instruction_data.into();
} = instruction.into();
ThreadSignal::Return {
should_return_value,
@ -570,7 +496,7 @@ mod tests {
use super::*;
const ALL_OPERATIONS: [(Operation, RunnerLogic); 24] = [
(Operation::POINT, r#move),
(Operation::POINT, point),
(Operation::CLOSE, close),
(Operation::LOAD_BOOLEAN, load_boolean),
(Operation::LOAD_CONSTANT, load_constant),

View File

@ -1,3 +1,5 @@
use std::fmt::{self, Display, Formatter};
use tracing::{info, trace};
use crate::{
@ -5,38 +7,36 @@ use crate::{
Chunk, DustString, Value,
};
use super::{record::Record, CallStack};
use super::{record::Record, CallStack, RunAction};
pub struct Thread {
call_stack: CallStack,
records: Vec<Record>,
chunk: Chunk,
}
impl Thread {
pub fn new(chunk: Chunk) -> Self {
let call_stack = CallStack::with_capacity(chunk.prototypes().len() + 1);
let mut records = Vec::with_capacity(chunk.prototypes().len() + 1);
chunk.into_records(&mut records);
Thread {
call_stack,
records,
}
Thread { chunk }
}
pub fn run(&mut self) -> Option<Value> {
let mut active = &mut self.records[0];
let mut current_call = FunctionCall {
name: active.name().cloned(),
record_index: active.index(),
return_register: active.stack_size() as u8 - 1,
ip: active.ip,
let mut call_stack = CallStack::with_capacity(self.chunk.prototypes.len() + 1);
let mut records = Vec::with_capacity(self.chunk.prototypes.len() + 1);
let main_call = FunctionCall {
name: self.chunk.name.clone(),
return_register: 0,
ip: 0,
};
let main_record = Record::new(&self.chunk);
call_stack.push(main_call);
records.push(main_record);
let mut active_record = &mut records[0];
info!(
"Starting thread with {}",
active
active_record
.as_function()
.name
.unwrap_or_else(|| DustString::from("anonymous"))
@ -44,138 +44,106 @@ impl Thread {
loop {
trace!(
"Run \"{}\" | Record = {} | IP = {}",
active
"Run \"{}\" | IP = {}",
active_record
.name()
.cloned()
.unwrap_or_else(|| DustString::from("anonymous")),
active.index(),
active.ip
active_record.ip
);
if active.ip >= active.actions.len() {
return None;
}
let instruction = active_record.chunk.instructions[active_record.ip];
let action = RunAction::from(instruction);
let signal = (action.logic)(action.instruction, active_record);
let action = active.actions[active.ip];
let signal = (action.logic)(action.data, active);
trace!("Thread Signal: {}", signal);
trace!("Thread Signal: {:?}", signal);
active.ip += 1;
active_record.ip += 1;
match signal {
ThreadSignal::Continue => {}
ThreadSignal::Call {
record_index,
function_register,
return_register,
argument_count,
} => {
let record_index = record_index as usize;
let function = active_record
.open_register(function_register)
.as_function()
.unwrap();
let first_argument_register = return_register - argument_count;
let mut arguments = Vec::with_capacity(argument_count as usize);
for register_index in first_argument_register..return_register {
let value = active.clone_register_value_or_constant(register_index);
let value = active_record.clone_register_value_or_constant(register_index);
arguments.push(value);
}
if record_index == active.index() as usize {
trace!("Recursion detected");
if let Some(record) = self.call_stack.last_mut() {
record.ip = active.ip;
}
active.ip = 0;
}
trace!("Passing arguments: {arguments:?}");
let prototype = &self.chunk.prototypes[function.prototype_index as usize];
let next_record = Record::new(prototype);
let next_call = FunctionCall {
name: active.name().cloned(),
record_index: active.index(),
name: next_record.name().cloned(),
return_register,
ip: active.ip,
ip: active_record.ip,
};
if self
.call_stack
.last()
.is_some_and(|call| call != &next_call)
|| self.call_stack.is_empty()
{
self.call_stack.push(current_call);
}
call_stack.push(next_call);
records.push(next_record);
current_call = next_call;
active = &mut self.records[record_index];
active_record = records.last_mut().unwrap();
for (index, argument) in arguments.into_iter().enumerate() {
active.set_register(index as u8, Register::Value(argument));
active_record.set_register(index as u8, Register::Value(argument));
}
}
ThreadSignal::LoadFunction {
from_record_index,
to_register_index,
destination,
prototype_index,
} => {
let function_record_index = from_record_index as usize;
let original_record_index = active.index() as usize;
active = &mut self.records[function_record_index];
let function = active.as_function();
let function_record_index = prototype_index as usize;
let function = self.chunk.prototypes[function_record_index].as_function();
let register = Register::Value(Value::Function(function));
active = &mut self.records[original_record_index];
active.set_register(to_register_index, register);
active_record.set_register(destination, register);
}
ThreadSignal::Return {
should_return_value,
return_register,
} => {
trace!("\n{:#?}{}", self.call_stack, current_call);
trace!("Returning with call stack:\n{call_stack}");
let outer_call = if let Some(call) = self.call_stack.pop() {
call
} else if should_return_value {
let return_value = active
.empty_register_or_clone_constant(return_register, Register::Empty);
let next_call = self.call_stack.pop();
if next_call.is_none() {
return Some(return_value);
}
let next_index = active.index() as usize - 1;
let next_record = &mut self.records[next_index];
next_record.set_register(
current_call.return_register,
Register::Value(return_value),
);
current_call = next_call.unwrap();
active = next_record;
continue;
let return_value = if should_return_value {
Some(
active_record
.empty_register_or_clone_constant(return_register, Register::Empty),
)
} else {
return None;
None
};
let record_index = outer_call.record_index as usize;
let current_call = call_stack.pop_or_panic();
let _current_record = records.pop().unwrap();
let destination = current_call.return_register;
if should_return_value {
let return_value = active
.empty_register_or_clone_constant(return_register, Register::Empty);
let outer_record = &mut self.records[record_index];
outer_record.set_register(destination, Register::Value(return_value));
if call_stack.is_empty() {
return if should_return_value {
Some(return_value.unwrap())
} else {
None
};
}
active = &mut self.records[record_index];
current_call = outer_call;
let outer_record = records.last_mut().unwrap();
if should_return_value {
outer_record
.set_register(destination, Register::Value(return_value.unwrap()));
}
active_record = outer_record;
}
}
}
@ -186,7 +154,7 @@ impl Thread {
pub enum ThreadSignal {
Continue,
Call {
record_index: u8,
function_register: u8,
return_register: u8,
argument_count: u8,
},
@ -195,7 +163,13 @@ pub enum ThreadSignal {
return_register: u8,
},
LoadFunction {
from_record_index: u8,
to_register_index: u8,
prototype_index: u8,
destination: u8,
},
}
impl Display for ThreadSignal {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}

View File

@ -5,4 +5,4 @@ fn fib (n: int) -> int {
fib(n - 1) + fib(n - 2)
}
write_line(fib(10))
write_line(fib(25))