Make runtime improvements
This commit is contained in:
parent
4473ea5b23
commit
e9bd9b37b0
22
README.md
22
README.md
@ -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
|
||||
|
@ -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 |
|
@ -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 |
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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}")
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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())),
|
||||
}
|
||||
|
@ -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})")
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -5,4 +5,4 @@ fn fib (n: int) -> int {
|
||||
fib(n - 1) + fib(n - 2)
|
||||
}
|
||||
|
||||
write_line(fib(10))
|
||||
write_line(fib(25))
|
||||
|
Loading…
x
Reference in New Issue
Block a user