1
0

Merge branch 'dev'

This commit is contained in:
Jeff 2025-01-13 10:44:42 -05:00
commit 7cfd60d281
67 changed files with 2361 additions and 1182 deletions

10
Cargo.lock generated
View File

@ -269,6 +269,15 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "crossbeam-channel"
version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.5"
@ -335,6 +344,7 @@ dependencies = [
"annotate-snippets",
"colored",
"criterion",
"crossbeam-channel",
"getrandom",
"rand",
"serde",

268
README.md
View File

@ -1,24 +1,9 @@
# The Dust Programming Language
# Dust Programming Language
A **fast**, **safe** and **easy to use** language for general-purpose programming.
Dust is **statically typed** to ensure that each program is valid before it is run. Compiling is
fast due to the purpose-built lexer and parser. Execution is fast because Dust uses a custom
bytecode that runs in a multi-threaded VM. Dust combines compile-time safety guarantees and
optimizations with negligible compile times and satisfying runtime speed to deliver a unique set of
features. It offers the best qualities of two disparate categories of programming language: the
highly optimized but slow-to-compile languages like Rust and C++ and the quick-to-start but often
slow and error-prone languages like Python and JavaScript.
Dust's syntax, safety features and evaluation model are based on Rust. Its instruction set,
optimization strategies and virtual machine are based on Lua. Unlike Rust and other languages that
compile to machine code, Dust has a very low time to execution. Unlike Lua and most other
interpreted languages, Dust enforces static typing to improve clarity and prevent bugs.
**Dust is under active development and is not yet ready for general use.**
**Fast**, **safe** and **easy-to-use** general-purpose programming language.
```rust
// "Hello, world" using Dust's built-in I/O functions
// An interactive "Hello, world" using Dust's built-in I/O functions
write_line("Enter your name...")
let name = read_line()
@ -38,7 +23,23 @@ fn fib (n: int) -> int {
write_line(fib(25))
```
## Goals
## Highlights
- Easy to read and write
- Single-pass, self-optimizing compiler
- Static typing with extensive type inference
- Multi-threaded register-based virtual machine with concurrent garbage collection
- Beautiful, helpful error messages from the compiler
- Safe execution, runtime errors are treated as bugs
## Overview
Dust's syntax, safety features and evaluation model are based on Rust. Its instruction set
and optimization strategies are based on Lua. Unlike Rust and other languages that compile to
machine code, Dust has a very low time to execution. Unlike Lua and most other interpreted
languages, Dust enforces static typing to improve clarity and prevent bugs.
### Project Goals
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
@ -56,10 +57,12 @@ aspirations are to be **fast**, **safe** and **easy**.
superior development experience despite some additional constraints. Like any good statically
typed language, users should feel confident in the type-consistency of their code and not want
to go back to a dynamically typed language.
- **Null-Free** Dust has no "null" or "undefined" values. All values are initialized and have a
type. This eliminates a whole class of bugs that are common in other languages.
- **Memory Safety** Dust should be free of memory bugs. Being implemented in Rust makes this easy
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.
Dust's design is to use a separate thread for garbage collection, allowing other threads to
continue executing instructions while the garbage collector looks for unused memory.
- **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
@ -72,211 +75,70 @@ aspirations are to be **fast**, **safe** and **easy**.
- **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.
## Language Overview
### Author
This is a quick overview of Dust's syntax features. It skips over the aspects that are familiar to
most programmers such as creating variables, using binary operators and printing to the console.
Eventually there should be a complete reference for the syntax.
### Syntax and Evaluation
Dust belongs to the C-like family of languages[^5], with an imperative syntax that will be familiar
to many programmers. Dust code looks a lot like Ruby, JavaScript, TypeScript and other members of
the family but Rust is its primary point of reference for syntax. Rust was chosen as a syntax model
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 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
coincidence.
### Statements and Expressions
Dust is composed of statements and expressions. If a statement ends in an expression without a
trailing semicolon, the statement evaluates to the value produced by that expression. However, if
the expression's value is suppressed with a semicolon, the statement does not evaluate to a value.
This is identical to Rust's evaluation model. That means that the following code will not compile:
```rust
// !!! Compile Error !!!
let a = { 40 + 2; }
```
The `a` variable is assigned to the value produced by a block. The block contains an expression that
is suppressed by a semicolon, so the block does not evaluate to a value. Therefore, the `a` variable
would have to be uninitialized (which Dust does not allow) or result in a runtime error (which Dust
avoids at all costs). We can fix this code by moving the semicolon to the end of the block. In this
position it suppresses the value of the entire `let` statement. As we saw above, a `let` statement
never evaluates to a value, so the semicolon has no effect on the program's behavior and could be
omitted altogether.
```rust
let a = { 40 + 2 }; // This is fine
let a = { 40 + 2 } // This is also fine
```
Only the final expression in a block is returned. When a `let` statement is combined with an
`if/else` statement, the program can perform conditional side effects before assigning the variable.
```rust
let random: int = random(0..100)
let is_even = if random == 99 {
write_line("We got a 99!")
false
} else {
random % 2 == 0
}
is_even
```
If the above example were passed to Dust as a complete program, it would return a boolean value and
might print a message to the console (if the user is especially lucky). However, note that the
program could be modified to return no value by simply adding a semicolon at the end.
Compared to JavaScript, Dust's evaluation model is more predictable, less error-prone and will never
trap the user into a frustating hunt for a missing semicolon. Compared to Rust, Dust's evaluation
model is more accomodating without sacrificing expressiveness. In Rust, semicolons are *required*
and *meaningful*, which provides excellent consistency but lacks flexibility. In JavaScript,
semicolons are *required* and *meaningless*, which is a source of confusion for many developers.
Dust borrowed Rust's approach to semicolons and their effect on evaluation and relaxed the rules to
accommodate different styles of coding. Rust isn't designed for command lines or REPLs but Dust is
well-suited to those applications. Dust needs to work in a source file or in an ad-hoc one-liner
sent to the CLI. Thus, semicolons are optional in most cases.
There are two things you need to know about semicolons in Dust:
- Semicolons suppress the value of whatever they follow. The preceding statement or expression will
have the type `none` and will not evaluate to a value.
- If a semicolon does not change how the program runs, it is optional.
This example shows three statements with semicolons. The compiler knows that a `let` statement
cannot produce a value and will always have the type `none`. Thanks to static typing, it also knows
that the `write_line` function has no return value so the function call also has the type `none`.
Therefore, these semicolons are optional.
```rust
let a = 40;
let b = 2;
write_line("The answer is ", a + b);
```
Removing the semicolons does not alter the execution pattern or the return value.
```rust
let x = 10
let y = 3
write_line("The remainder is ", x % y)
```
### Type System
All variables have a type that is established when the variable is declared. This usually does not
require that the type be explicitly stated, Dust can infer the type from the value.
The next example produces a compiler error because the `if` block evaluates to and `int` but the
`else` block evaluates to a `str`. Dust does not allow branches of the same `if/else` statement to
have different types.
```rust
// !!! Compile Error !!!
let input = read_line()
let reward = if input == "42" {
write_line("You got it! Here's your reward.")
777 // <- This is an int
} else {
write_line(input, " is not the answer.")
"777" // <- This is a string
}
```
### Basic Values
Dust supports the following basic values:
- Boolean: `true` or `false`
- Byte: An unsigned 8-bit integer
- Character: A Unicode scalar value
- Float: A 64-bit floating-point number
- Function: An executable chunk of code
- Integer: A signed 64-bit integer
- String: A UTF-8 encoded byte sequence
Dust's "basic" values are conceptually similar because they are singular as opposed to composite.
Most of these values are stored on the stack but some are heap-allocated. A Dust string is a
sequence of bytes that are encoded in UTF-8. Even though it could be seen as a composite of byte
values, strings are considered "basic" because they are parsed directly from tokens and behave as
singular values. Shorter strings are stored on the stack while longer strings are heap-allocated.
Dust offers built-in native functions that can manipulate strings by accessing their bytes or
reading them as a sequence of characters.
There is no `null` or `undefined` value in Dust. All values and variables must be initialized to one
of the supported value types. This eliminates a whole class of bugs that permeate many other
I'm Jeff 🦀 and I started this project as simple expession evaluator. Initially, the project used an
external parser and a tree-walking interpreter. After several books, a few papers, countless
articles and a lot of experimentation, Dust has evolved to an ambitious project that aims to
implement lucrative features with a high-quality implementation that competes with established
languages.
> I call it my billion-dollar mistake. It was the invention of the null reference in 1965.
> - Tony Hoare
## Usage
Dust *does* have a `none` type, which should not be confused for being `null`-like. Like the `()` or
"unit" type in Rust, `none` exists as a type but not as a value. It indicates the lack of a value
from a function, expression or statement. A variable cannot be assigned to `none`.
**Dust is under active development and is not yet ready for general use.**
## Previous Implementations
## Installation
Dust has gone through several iterations, each with its own design choices. It was originally
implemented with a syntax tree generated by an external parser, then a parser generator, and finally
a custom parser. Eventually the language was rewritten to use bytecode instructions and a virtual
machine. The current implementation: compiling to bytecode with custom lexing and parsing for a
register-based VM, is by far the most performant and the general design is unlikely to change.
Dust previously had a more complex type system with type arguments (or "generics") and a simple
model for asynchronous execution of statements. Both of these features were removed to simplify the
language when it was rewritten to use bytecode instructions. Both features are planned to be
reintroduced in the future.
Eventually, Dust should be available via package managers and as an embeddable library. For now,
the only way to use Dust is to clone the repository and build it from source.
## Inspiration
[Crafting Interpreters] by Bob Nystrom was a great resource for writing the compiler, especially the
Pratt parser. The book is a great introduction to writing interpreters. Had it been discovered
*Crafting Interpreters*[^0] by Bob Nystrom was a great resource for writing the compiler, especially
the Pratt parser. The book is a great introduction to writing interpreters. Had it been discovered
sooner, some early implementations of Dust would have been both simpler in design and more ambitious
in scope.
[The Implementation of Lua 5.0] by Roberto Ierusalimschy, Luiz Henrique de Figueiredo, and Waldemar
Celes was a great resource for understanding register-based virtual machines and their instructions.
This paper was recommended by Bob Nystrom in [Crafting Interpreters].
*The Implementation of Lua 5.0*[^1] by Roberto Ierusalimschy, Luiz Henrique de Figueiredo, and
Waldemar Celes was a great resource for understanding register-based virtual machines and their
instructions. This paper was recommended by Bob Nystrom in [Crafting Interpreters].
[A No-Frills Introduction to Lua 5.1 VM Instructions] by Kein-Hong Man has a wealth of detailed
*A No-Frills Introduction to Lua 5.1 VM Instructions*[^2] by Kein-Hong Man has a wealth of detailed
information on how Lua uses terse instructions to create dense chunks that execute quickly. This was
essential in the design of Dust's instructions. Dust uses compile-time optimizations that are based
on Lua optimizations covered in this paper.
[A Performance Survey on Stack-based and Register-based Virtual Machines] by Ruijie Fang and Siqi
"A Performance Survey on Stack-based and Register-based Virtual Machines"[^3] by Ruijie Fang and Siqi
Liup was helpful for a quick yet efficient primer on getting stack-based and register-based virtual
machines up and running. The included code examples show how to implement both types of VMs in C.
The performance comparison between the two types of VMs is worth reading for anyone who is trying to
choose between the two. Some of the benchmarks described in the paper inspired similar benchmarks
used in this project to compare Dust to other languages.
used in this project to compare Dust to other languages and inform design decisions.
*Writing a Compiler in Go*[^6] by Thorsten Ball is a lot like *Crafting Interpreters*, they are the
where I look for a generalized approach to solving a problem. Filled with code examples, this book
helps the reader make the turn from evaluating a syntax tree to thinking about how problems are
solved on physical hardware and how that informs the design of a virtual machine.
> Let me get straight to the point: a virtual machine is a computer built with software.
> -- Thorsten Ball, *Writing a Compiler in Go*
*Structure and Interpretation of Computer Programs, Second Edition*[^7] by Harold Abelson and Gerald
Jay Sussman with Julie Sussman is a classic text on computer science. It encourages an abstract
view of programming, sometimes using diagrams to explain programs as though they were physical
devices. It requires more effort than the books that immediately show you how to write a program,
but the takeaway is a deep understanding of the the process a computer (or a VM) goes through to
execute a program.
## License
Dust is licensed under the GNU General Public License v3.0. See the `LICENSE` file for details.
## References
[^1]: [Crafting Interpreters](https://craftinginterpreters.com/)
[^2]: [The Implementation of Lua 5.0](https://www.lua.org/doc/jucs05.pdf)
[^3]: [A No-Frills Introduction to Lua 5.1 VM Instructions](https://www.mcours.net/cours/pdf/hasclic3/hasssclic818.pdf)
[^4]: [A Performance Survey on Stack-based and Register-based Virtual Machines](https://arxiv.org/abs/1611.00467)
[^5]: [List of C-family programming languages](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages)
[^6]: [ripgrep is faster than {grep, ag, git grep, ucg, pt, sift}](https://blog.burntsushi.net/ripgrep/#mechanics)
[^0]: [Crafting Interpreters](https://craftinginterpreters.com/)
[^1]: [The Implementation of Lua 5.0](https://www.lua.org/doc/jucs05.pdf)
[^2]: [A No-Frills Introduction to Lua 5.1 VM Instructions](https://www.mcours.net/cours/pdf/hasclic3/hasssclic818.pdf)
[^3]: [A Performance Survey on Stack-based and Register-based Virtual Machines](https://arxiv.org/abs/1611.00467)
[^4]: [List of C-family programming languages](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages)
[^5]: [ripgrep is faster than {grep, ag, git grep, ucg, pt, sift}](https://blog.burntsushi.net/ripgrep/#mechanics)
[^6]: [Writing a Compiler in Go](https://compilerbook.com/)
[^7]: [Structure and Interpretation of Computer Programs, Second Edition](https://mitpress.mit.edu/9780262510875/structure-and-interpretation-of-computer-programs/)

View File

@ -12,7 +12,7 @@ version.workspace = true
annotate-snippets = "0.11.4"
colored = "2.1.0"
rand = "0.8.5"
serde = { version = "1.0.203", features = ["derive"] }
serde = { version = "1.0.203", features = ["derive", "rc"] }
serde_json = "1.0.117"
getrandom = { version = "0.2", features = [
"js",
@ -21,6 +21,7 @@ smartstring = { version = "1.0.1", features = [
"serde",
], default-features = false }
tracing = "0.1.41"
crossbeam-channel = "0.5.14"
[dev-dependencies]
criterion = { version = "0.3.4", features = ["html_reports"] }
@ -33,6 +34,10 @@ harness = false
name = "fibonacci"
harness = false
[[bench]]
name = "threads"
harness = false
[[test]]
name = "logic_and"
path = "tests/logic/and.rs"

View File

@ -0,0 +1,31 @@
use std::time::Duration;
use criterion::{Criterion, black_box, criterion_group, criterion_main};
use dust_lang::run;
const SOURCE: &str = r#"
let mut i = 0
while i < 1_000 {
i += 1
spawn(
fn () { random_int(0, 10); }
)
}
"#;
fn threads(source: &str) {
run(source).unwrap();
}
fn criterion_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("threads");
group.measurement_time(Duration::from_secs(15));
group.bench_function("threads", |b| b.iter(|| threads(black_box(SOURCE))));
group.finish();
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View File

@ -46,11 +46,11 @@ use colored::{ColoredString, Colorize};
use crate::{Chunk, Local};
const INSTRUCTION_COLUMNS: [(&str, usize); 4] =
[("i", 5), ("POSITION", 12), ("OPERATION", 17), ("INFO", 24)];
[("i", 5), ("POSITION", 12), ("OPERATION", 17), ("INFO", 41)];
const INSTRUCTION_BORDERS: [&str; 3] = [
"╭─────┬────────────┬─────────────────┬────────────────────────",
"├─────┼────────────┼─────────────────┼────────────────────────",
"╰─────┴────────────┴─────────────────┴────────────────────────",
"╭─────┬────────────┬─────────────────┬─────────────────────────────────────────",
"├─────┼────────────┼─────────────────┼─────────────────────────────────────────",
"╰─────┴────────────┴─────────────────┴─────────────────────────────────────────",
];
const LOCAL_COLUMNS: [(&str, usize); 5] = [
@ -286,7 +286,7 @@ impl<'a, W: Write> Disassembler<'a, W> {
.unwrap_or("stripped".to_string());
let operation = instruction.operation().to_string();
let info = instruction.disassembly_info();
let row = format!("{index:^5}{position:^12}{operation:^17}{info:^24}");
let row = format!("{index:^5}{position:^12}{operation:^17}{info:^41}");
self.write_center_border(&row)?;
}

View File

@ -1,15 +1,17 @@
//! Scoped variable.
use serde::{Deserialize, Serialize};
use crate::Scope;
/// A scoped variable.
/// Scoped variable.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Local {
/// The index of the identifier in the constants table.
pub identifier_index: u8,
/// Index of the identifier in the constants list.
pub identifier_index: u16,
/// Stack index where the local's value is stored.
pub register_index: u8,
/// Index of the register where the variable's value is stored.
pub register_index: u16,
/// Whether the local is mutable.
pub is_mutable: bool,
@ -20,7 +22,7 @@ pub struct Local {
impl Local {
/// Creates a new Local instance.
pub fn new(identifier_index: u8, register_index: u8, is_mutable: bool, scope: Scope) -> Self {
pub fn new(identifier_index: u16, register_index: u16, is_mutable: bool, scope: Scope) -> Self {
Self {
identifier_index,
register_index,

View File

@ -23,6 +23,7 @@ pub use scope::Scope;
use std::fmt::{self, Debug, Display, Formatter, Write as FmtWrite};
use std::io::Write;
use std::sync::Arc;
use serde::{Deserialize, Serialize};
@ -40,10 +41,10 @@ pub struct Chunk {
pub(crate) positions: Vec<Span>,
pub(crate) constants: Vec<Value>,
pub(crate) locals: Vec<Local>,
pub(crate) prototypes: Vec<Chunk>,
pub(crate) prototypes: Vec<Arc<Chunk>>,
pub(crate) register_count: usize,
pub(crate) prototype_index: u8,
pub(crate) prototype_index: u16,
}
impl Chunk {
@ -55,7 +56,7 @@ impl Chunk {
positions: impl Into<Vec<Span>>,
constants: impl Into<Vec<Value>>,
locals: impl Into<Vec<Local>>,
prototypes: Vec<Chunk>,
prototypes: impl IntoIterator<Item = Chunk>,
) -> Self {
Self {
name,
@ -64,7 +65,7 @@ impl Chunk {
positions: positions.into(),
constants: constants.into(),
locals: locals.into(),
prototypes,
prototypes: prototypes.into_iter().map(Arc::new).collect(),
register_count: 0,
prototype_index: 0,
}

View File

@ -104,6 +104,14 @@ pub enum CompileError {
right_type: Type,
position: Span,
},
CannotNegateType {
argument_type: Type,
position: Span,
},
CannotNotType {
argument_type: Type,
position: Span,
},
CannotSubtractType {
argument_type: Type,
position: Span,
@ -182,6 +190,8 @@ impl AnnotatedError for CompileError {
Self::CannotMutateImmutableVariable { .. } => "Cannot mutate immutable variable",
Self::CannotMultiplyArguments { .. } => "Cannot multiply these types",
Self::CannotMultiplyType { .. } => "Cannot multiply this type",
Self::CannotNegateType { .. } => "Cannot negate this type",
Self::CannotNotType { .. } => "Cannot apply \"not\" operation to this type",
Self::CannotResolveRegisterType { .. } => "Cannot resolve register type",
Self::CannotResolveVariableType { .. } => "Cannot resolve type",
Self::CannotSubtractType { .. } => "Cannot subtract from this type",
@ -251,8 +261,10 @@ impl AnnotatedError for CompileError {
right_position,
} => {
vec![(
format!("Type \"{left_type}\" cannot be added to type \"{right_type}\". Try converting one of the values to the other type."),
Span(left_position.0, right_position.1)
format!(
"Type \"{left_type}\" cannot be, added to type \"{right_type}\". Try converting one of the values to the other type."
),
Span(left_position.0, right_position.1),
)]
}
_ => Vec::with_capacity(0),

View File

@ -28,14 +28,14 @@ use parse_rule::{ParseRule, Precedence};
use tracing::{Level, debug, info, span};
use type_checks::{check_math_type, check_math_types};
use std::mem::replace;
use std::{mem::replace, sync::Arc};
use optimize::control_flow_register_consolidation;
use crate::{
Argument, Chunk, ConcreteValue, DustError, DustString, FunctionType, Instruction, Lexer, Local,
NativeFunction, Operation, Scope, Span, Token, TokenKind, Type, Value,
instruction::{CallNative, Close, GetLocal, Jump, LoadList, Negate, Not, Return, SetLocal},
Chunk, ConcreteValue, DustError, DustString, FunctionType, Instruction, Lexer, Local,
NativeFunction, Operand, Operation, Scope, Span, Token, TokenKind, Type, Value,
instruction::{CallNative, Close, GetLocal, Jump, LoadList, Return, SetLocal, TypeCode},
};
/// Compiles the input and returns a chunk.
@ -96,7 +96,7 @@ pub struct Compiler<'src> {
/// Prototypes that have been compiled. These are assigned to the chunk when
/// [`Compiler::finish`] is called.
prototypes: Vec<Chunk>,
prototypes: Vec<Arc<Chunk>>,
/// Maximum stack size required by the chunk. This is assigned to the chunk when
/// [`Compiler::finish`] is called.
@ -104,7 +104,7 @@ pub struct Compiler<'src> {
/// The first register index that the compiler should use. This is used to avoid reusing the
/// registers that are used for the function's arguments.
minimum_register: u8,
minimum_register: u16,
/// Index of the current block. This is used to determine the scope of locals and is incremented
/// when a new block is entered.
@ -115,7 +115,7 @@ pub struct Compiler<'src> {
/// 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,
prototype_index: u16,
/// Whether the chunk is the program's main chunk. This is used to prevent recursive calls to
/// the main chunk.
@ -227,7 +227,7 @@ impl<'src> Compiler<'src> {
matches!(self.current_token, Token::Eof)
}
fn next_register(&self) -> u8 {
fn next_register(&self) -> u16 {
self.instructions
.iter()
.rev()
@ -260,7 +260,7 @@ impl<'src> Compiler<'src> {
Ok(())
}
fn get_local(&self, index: u8) -> Result<&(Local, Type), CompileError> {
fn get_local(&self, index: u16) -> Result<&(Local, Type), CompileError> {
self.locals
.get(index as usize)
.ok_or(CompileError::UndeclaredVariable {
@ -269,7 +269,7 @@ impl<'src> Compiler<'src> {
})
}
fn get_local_index(&self, identifier_text: &str) -> Result<u8, CompileError> {
fn get_local_index(&self, identifier_text: &str) -> Result<u16, CompileError> {
self.locals
.iter()
.enumerate()
@ -284,7 +284,7 @@ impl<'src> Compiler<'src> {
};
if identifier == identifier_text {
Some(index as u8)
Some(index as u16)
} else {
None
}
@ -298,16 +298,16 @@ impl<'src> Compiler<'src> {
fn declare_local(
&mut self,
identifier: &str,
register_index: u8,
register_index: u16,
r#type: Type,
is_mutable: bool,
scope: Scope,
) -> (u8, u8) {
) -> (u16, u16) {
info!("Declaring local {identifier}");
let identifier = Value::Concrete(ConcreteValue::string(identifier));
let identifier_index = self.push_or_get_constant(identifier);
let local_index = self.locals.len() as u8;
let local_index = self.locals.len() as u16;
self.locals.push((
Local::new(identifier_index, register_index, is_mutable, scope),
@ -317,7 +317,7 @@ impl<'src> Compiler<'src> {
(local_index, identifier_index)
}
fn get_identifier(&self, local_index: u8) -> Option<String> {
fn get_identifier(&self, local_index: u16) -> Option<String> {
self.locals
.get(local_index as usize)
.and_then(|(local, _)| {
@ -327,15 +327,15 @@ impl<'src> Compiler<'src> {
})
}
fn push_or_get_constant(&mut self, value: Value) -> u8 {
fn push_or_get_constant(&mut self, value: Value) -> u16 {
if let Some(index) = self
.constants
.iter()
.position(|constant| constant == &value)
{
index as u8
index as u16
} else {
let index = self.constants.len() as u8;
let index = self.constants.len() as u16;
self.constants.push(value);
@ -393,7 +393,7 @@ impl<'src> Compiler<'src> {
.unwrap_or(Type::None)
}
fn get_register_type(&self, register_index: u8) -> Result<Type, CompileError> {
fn get_register_type(&self, register_index: u16) -> Result<Type, CompileError> {
if let Some((_, r#type)) = self
.locals
.iter()
@ -651,22 +651,39 @@ impl<'src> Compiler<'src> {
}
let destination = self.next_register();
let instruction = match operator.kind() {
TokenKind::Bang => Instruction::from(Not {
destination,
argument,
}),
TokenKind::Minus => Instruction::from(Negate {
destination,
argument,
}),
_ => {
return Err(CompileError::ExpectedTokenMultiple {
expected: &[TokenKind::Bang, TokenKind::Minus],
found: operator.to_owned(),
position: operator_position,
});
}
let type_code = match previous_type {
Type::Boolean => TypeCode::BOOLEAN,
Type::Byte => TypeCode::BYTE,
Type::Character => TypeCode::CHARACTER,
Type::Float => TypeCode::FLOAT,
Type::Integer => TypeCode::INTEGER,
Type::String => TypeCode::STRING,
_ => match operator {
Token::Minus => {
return Err(CompileError::CannotNegateType {
argument_type: previous_type,
position: previous_position,
});
}
Token::Bang => {
return Err(CompileError::CannotNotType {
argument_type: previous_type,
position: previous_position,
});
}
_ => {
return Err(CompileError::ExpectedTokenMultiple {
expected: &[TokenKind::Bang, TokenKind::Minus],
found: operator.to_owned(),
position: operator_position,
});
}
},
};
let instruction = match operator {
Token::Bang => Instruction::not(destination, argument),
Token::Minus => Instruction::negate(destination, argument, type_code),
_ => unreachable!(),
};
self.emit_instruction(instruction, previous_type, operator_position);
@ -677,14 +694,14 @@ impl<'src> Compiler<'src> {
fn handle_binary_argument(
&mut self,
instruction: &Instruction,
) -> Result<(Argument, bool), CompileError> {
) -> Result<(Operand, bool), CompileError> {
let (argument, push_back) = match instruction.operation() {
Operation::LOAD_CONSTANT => (Argument::Constant(instruction.b_field()), false),
Operation::LOAD_CONSTANT => (Operand::Constant(instruction.b_field()), false),
Operation::GET_LOCAL => {
let local_index = instruction.b_field();
let (local, _) = self.get_local(local_index)?;
(Argument::Register(local.register_index), false)
(Operand::Register(local.register_index), false)
}
Operation::LOAD_BOOLEAN
| Operation::LOAD_LIST
@ -699,12 +716,12 @@ impl<'src> Compiler<'src> {
| Operation::LESS_EQUAL
| Operation::NEGATE
| Operation::NOT
| Operation::CALL => (Argument::Register(instruction.a_field()), true),
| Operation::CALL => (Operand::Register(instruction.a_field()), true),
Operation::CALL_NATIVE => {
let function = NativeFunction::from(instruction.b_field());
if function.returns_value() {
(Argument::Register(instruction.a_field()), true)
(Operand::Register(instruction.a_field()), true)
} else {
return Err(CompileError::ExpectedExpression {
found: self.previous_token.to_owned(),
@ -762,6 +779,16 @@ impl<'src> Compiler<'src> {
check_math_type(&left_type, operator, &left_position)?;
let left_type_code = match left_type {
Type::Boolean => TypeCode::BOOLEAN,
Type::Byte => TypeCode::BYTE,
Type::Character => TypeCode::CHARACTER,
Type::Float => TypeCode::FLOAT,
Type::Integer => TypeCode::INTEGER,
Type::String => TypeCode::STRING,
_ => unreachable!(),
};
if is_assignment && !left_is_mutable_local {
return Err(CompileError::ExpectedMutableVariable {
found: self.previous_token.to_owned(),
@ -784,6 +811,16 @@ impl<'src> Compiler<'src> {
&right_position,
)?;
let right_type_code = match right_type {
Type::Boolean => TypeCode::BOOLEAN,
Type::Byte => TypeCode::BYTE,
Type::Character => TypeCode::CHARACTER,
Type::Float => TypeCode::FLOAT,
Type::Integer => TypeCode::INTEGER,
Type::String => TypeCode::STRING,
_ => unreachable!(),
};
if push_back_right {
self.instructions
.push((right_instruction, right_type, right_position));
@ -798,18 +835,28 @@ impl<'src> Compiler<'src> {
};
let destination = if is_assignment {
match left {
Argument::Register(register) => register,
Argument::Constant(_) => self.next_register(),
Operand::Register(register) => register,
Operand::Constant(_) => self.next_register(),
}
} else {
self.next_register()
};
let instruction = match operator {
Token::Plus | Token::PlusEqual => Instruction::add(destination, left, right),
Token::Minus | Token::MinusEqual => Instruction::subtract(destination, left, right),
Token::Star | Token::StarEqual => Instruction::multiply(destination, left, right),
Token::Slash | Token::SlashEqual => Instruction::divide(destination, left, right),
Token::Percent | Token::PercentEqual => Instruction::modulo(destination, left, right),
Token::Plus | Token::PlusEqual => {
Instruction::add(destination, left, left_type_code, right, right_type_code)
}
Token::Minus | Token::MinusEqual => {
Instruction::subtract(destination, left, left_type_code, right, right_type_code)
}
Token::Star | Token::StarEqual => {
Instruction::multiply(destination, left, left_type_code, right, right_type_code)
}
Token::Slash | Token::SlashEqual => {
Instruction::divide(destination, left, left_type_code, right, right_type_code)
}
Token::Percent | Token::PercentEqual => {
Instruction::modulo(destination, left, left_type_code, right, right_type_code)
}
_ => {
return Err(CompileError::ExpectedTokenMultiple {
expected: &[
@ -861,6 +908,18 @@ impl<'src> Compiler<'src> {
let operator_position = self.current_position;
let rule = ParseRule::from(&operator);
// TODO: Check if the left type is a valid type for comparison
let left_type_code = match left_type {
Type::Boolean => TypeCode::BOOLEAN,
Type::Byte => TypeCode::BYTE,
Type::Character => TypeCode::CHARACTER,
Type::Float => TypeCode::FLOAT,
Type::Integer => TypeCode::INTEGER,
Type::String => TypeCode::STRING,
_ => unreachable!(),
};
if push_back_left {
self.instructions
.push((left_instruction, left_type, left_position));
@ -878,6 +937,19 @@ impl<'src> Compiler<'src> {
})?;
let (right, push_back_right) = self.handle_binary_argument(&right_instruction)?;
// TODO: Check if the right type is a valid type for comparison
// TODO: Check if the left and right types are compatible
let right_type_code = match right_type {
Type::Boolean => TypeCode::BOOLEAN,
Type::Byte => TypeCode::BYTE,
Type::Character => TypeCode::CHARACTER,
Type::Float => TypeCode::FLOAT,
Type::Integer => TypeCode::INTEGER,
Type::String => TypeCode::STRING,
_ => unreachable!(),
};
if push_back_right {
self.instructions
.push((right_instruction, right_type, right_position));
@ -885,12 +957,22 @@ impl<'src> Compiler<'src> {
let destination = self.next_register();
let comparison = match operator {
Token::DoubleEqual => Instruction::equal(true, left, right),
Token::BangEqual => Instruction::equal(false, left, right),
Token::Less => Instruction::less(true, left, right),
Token::LessEqual => Instruction::less_equal(true, left, right),
Token::Greater => Instruction::less_equal(false, left, right),
Token::GreaterEqual => Instruction::less(false, left, right),
Token::DoubleEqual => {
Instruction::equal(true, left, left_type_code, right, right_type_code)
}
Token::BangEqual => {
Instruction::equal(false, left, left_type_code, right, right_type_code)
}
Token::Less => Instruction::less(true, left, left_type_code, right, right_type_code),
Token::LessEqual => {
Instruction::less_equal(true, left, left_type_code, right, right_type_code)
}
Token::Greater => {
Instruction::less_equal(false, left, left_type_code, right, right_type_code)
}
Token::GreaterEqual => {
Instruction::less(false, left, left_type_code, right, right_type_code)
}
_ => {
return Err(CompileError::ExpectedTokenMultiple {
expected: &[
@ -982,7 +1064,7 @@ impl<'src> Compiler<'src> {
let old_jump = &mut instructions[1].0;
let jump_index = instructions_length - group_index * 3 - 1;
let short_circuit_distance = (instructions_length - jump_index) as u8;
let short_circuit_distance = (instructions_length - jump_index) as u16;
*old_jump = Instruction::jump(short_circuit_distance, true);
}
@ -1009,7 +1091,7 @@ impl<'src> Compiler<'src> {
return self.parse_call_native(native_function);
} else if self.function_name.as_deref() == Some(identifier) && !self.is_main {
let destination = self.next_register();
let load_self = Instruction::load_self(destination);
let load_self = Instruction::load_self(destination, false);
self.emit_instruction(load_self, Type::SelfFunction, start_position);
@ -1147,10 +1229,7 @@ impl<'src> Compiler<'src> {
let destination = self.next_register();
let end = self.previous_position.1;
let load_list = Instruction::from(LoadList {
destination,
start_register,
});
let load_list = Instruction::load_list(destination, start_register, false);
self.emit_instruction(load_list, Type::List(Box::new(item_type)), Span(start, end));
@ -1194,7 +1273,7 @@ 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 mut if_block_distance = (if_block_end - if_block_start) as u16;
let if_block_type = self.get_last_instruction_type();
if let Token::Else = self.current_token {
@ -1216,7 +1295,7 @@ impl<'src> Compiler<'src> {
}
let else_block_end = self.instructions.len();
let else_block_distance = (else_block_end - if_block_end) as u8;
let else_block_distance = (else_block_end - if_block_end) as u16;
let else_block_type = self.get_last_instruction_type();
if let Err(conflict) = if_block_type.check(&else_block_type) {
@ -1234,7 +1313,7 @@ impl<'src> Compiler<'src> {
{
let (loader, _, _) = self.instructions.last_mut().unwrap();
loader.set_c_field(true as u8);
loader.set_c_field(true as u16);
} else {
if_block_distance += 1;
let jump = Instruction::from(Jump {
@ -1270,7 +1349,7 @@ impl<'src> Compiler<'src> {
fn parse_while(&mut self) -> Result<(), CompileError> {
self.advance()?;
let expression_start = self.instructions.len() as u8;
let expression_start = self.instructions.len();
self.parse_expression()?;
@ -1297,8 +1376,8 @@ impl<'src> Compiler<'src> {
self.parse_block()?;
let block_end = self.instructions.len() as u8;
let jump_distance = block_end - block_start as u8 + 1;
let block_end = self.instructions.len();
let jump_distance = (block_end - block_start + 1) as u16;
let jump = Instruction::from(Jump {
offset: jump_distance,
is_positive: true,
@ -1307,7 +1386,7 @@ impl<'src> Compiler<'src> {
self.instructions
.insert(block_start, (jump, Type::None, self.current_position));
let jump_back_distance = block_end - expression_start + 1;
let jump_back_distance = (block_end - expression_start + 1) as u16;
let jump_back = Instruction::from(Jump {
offset: jump_back_distance,
is_positive: false,
@ -1433,7 +1512,7 @@ impl<'src> Compiler<'src> {
let offset = offset as usize;
if is_positive && offset + index == instruction_length - 1 {
*instruction = Instruction::jump((offset + 1) as u8, true);
*instruction = Instruction::jump((offset + 1) as u16, true);
}
}
}
@ -1487,7 +1566,7 @@ impl<'src> Compiler<'src> {
let offset = offset as usize;
if is_positive && offset + index == instruction_length - 1 {
*instruction = Instruction::jump((offset + 1) as u8, true);
*instruction = Instruction::jump((offset + 1) as u16, true);
}
}
}
@ -1567,9 +1646,9 @@ impl<'src> Compiler<'src> {
});
};
function_compiler.prototype_index = self.prototypes.len() as u8;
function_compiler.prototype_index = self.prototypes.len() as u16;
let mut value_parameters: Vec<(u8, Type)> = Vec::with_capacity(3);
let mut value_parameters: Vec<(u16, Type)> = Vec::with_capacity(3);
while !function_compiler.allow(Token::RightParenthesis)? {
let is_mutable = function_compiler.allow(Token::Mut)?;
@ -1645,7 +1724,7 @@ impl<'src> Compiler<'src> {
let chunk = function_compiler.finish();
let destination = self.next_register();
self.prototypes.push(chunk);
self.prototypes.push(Arc::new(chunk));
if let Some(identifier) = identifier {
self.declare_local(
@ -1657,7 +1736,7 @@ impl<'src> Compiler<'src> {
);
}
let load_function = Instruction::load_function(destination, prototype_index);
let load_function = Instruction::load_function(destination, prototype_index, false);
self.emit_instruction(
load_function,

View File

@ -26,7 +26,7 @@ use crate::{Compiler, Instruction, Operation};
/// a `POINT` instruction to prevent the VM from encountering an empty register.
///
/// The instructions must be in the following order:
/// - `EQUAL` | `LESS` | `LESS_EQUAL` | `TEST`
/// - `TEST` or any of the `EQUAL`, `LESS` or `LESS_EQUAL` instructions
/// - `JUMP`
/// - `LOAD_BOOLEAN` or `LOAD_CONSTANT`
/// - `LOAD_BOOLEAN` or `LOAD_CONSTANT`
@ -37,7 +37,7 @@ pub fn control_flow_register_consolidation(compiler: &mut Compiler) {
if !matches!(
compiler.get_last_operations(),
Some([
Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL | Operation::TEST,
Operation::TEST | Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL,
Operation::JUMP,
Operation::LOAD_BOOLEAN | Operation::LOAD_CONSTANT,
Operation::LOAD_BOOLEAN | Operation::LOAD_CONSTANT,

View File

@ -1,20 +1,28 @@
use crate::{Argument, Instruction, Operation};
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
pub struct Add {
pub destination: u8,
pub left: Argument,
pub right: Argument,
pub destination: u16,
pub left: Operand,
pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
}
impl From<Instruction> for Add {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let (left, right) = instruction.b_and_c_as_arguments();
let (left, right) = instruction.b_and_c_as_operands();
let left_type = instruction.b_type();
let right_type = instruction.c_type();
Add {
destination,
left,
left_type,
right,
right_type,
}
}
}
@ -22,10 +30,40 @@ impl From<Instruction> for Add {
impl From<Add> for Instruction {
fn from(add: Add) -> Self {
let operation = Operation::ADD;
let a = add.destination;
let (b, b_is_constant) = add.left.as_index_and_constant_flag();
let (c, c_is_constant) = add.right.as_index_and_constant_flag();
let a_field = add.destination;
let (b_field, b_is_constant) = add.left.as_index_and_constant_flag();
let (c_field, c_is_constant) = add.right.as_index_and_constant_flag();
let b_type = add.left_type;
let c_type = add.right_type;
Instruction::new(operation, a, b, c, b_is_constant, c_is_constant, false)
InstructionBuilder {
operation,
a_field,
b_field,
c_field,
b_is_constant,
c_is_constant,
b_type,
c_type,
..Default::default()
}
.build()
}
}
impl Display for Add {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let Add {
destination,
left,
left_type,
right,
right_type,
} = self;
write!(
f,
"R{destination} = {left}({left_type}) + {right}({right_type})",
)
}
}

View File

@ -1,9 +1,13 @@
use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct Call {
pub destination: u8,
pub function_register: u8,
pub argument_count: u8,
pub destination: u16,
pub function_register: u16,
pub argument_count: u16,
pub is_recursive: bool,
}
@ -25,11 +29,45 @@ impl From<Instruction> for Call {
impl From<Call> for Instruction {
fn from(call: Call) -> Self {
let a = call.destination;
let b = call.function_register;
let c = call.argument_count;
let d = call.is_recursive;
let a_field = call.destination;
let b_field = call.function_register;
let c_field = call.argument_count;
let d_field = call.is_recursive;
Instruction::new(Operation::CALL, a, b, c, false, false, d)
InstructionBuilder {
operation: Operation::CALL,
a_field,
b_field,
c_field,
d_field,
..Default::default()
}
.build()
}
}
impl Display for Call {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let Call {
destination,
function_register,
argument_count,
..
} = self;
let arguments_start = destination.saturating_sub(*argument_count);
match argument_count {
0 => write!(f, "R{destination} = R{function_register}()"),
1 => write!(
f,
"R{destination} = R{function_register}(R{arguments_start})"
),
_ => {
write!(
f,
"R{destination} = R{function_register}(R{arguments_start}..R{destination})"
)
}
}
}
}

View File

@ -1,9 +1,13 @@
use std::fmt::Display;
use crate::{Instruction, NativeFunction, Operation};
use super::InstructionBuilder;
pub struct CallNative {
pub destination: u8,
pub destination: u16,
pub function: NativeFunction,
pub argument_count: u8,
pub argument_count: u16,
}
impl From<Instruction> for CallNative {
@ -22,10 +26,41 @@ impl From<Instruction> for CallNative {
impl From<CallNative> for Instruction {
fn from(call_native: CallNative) -> Self {
let operation = Operation::CALL_NATIVE;
let a = call_native.destination;
let b = call_native.function as u8;
let c = call_native.argument_count;
let a_field = call_native.destination;
let b_field = call_native.function as u16;
let c_field = call_native.argument_count;
Instruction::new(operation, a, b, c, false, false, false)
InstructionBuilder {
operation,
a_field,
b_field,
c_field,
..Default::default()
}
.build()
}
}
impl Display for CallNative {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let CallNative {
destination,
function,
argument_count,
} = self;
let arguments_start = destination.saturating_sub(*argument_count);
let arguments_end = arguments_start + argument_count;
if function.returns_value() {
write!(f, "R{destination} = ")?;
}
match argument_count {
0 => {
write!(f, "{function}()")
}
1 => write!(f, "{function}(R{arguments_start})"),
_ => write!(f, "{function}(R{arguments_start}..R{arguments_end})"),
}
}
}

View File

@ -1,8 +1,12 @@
use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct Close {
pub from: u8,
pub to: u8,
pub from: u16,
pub to: u16,
}
impl From<Instruction> for Close {
@ -17,8 +21,23 @@ impl From<Instruction> for Close {
impl From<Close> for Instruction {
fn from(close: Close) -> Self {
let operation = Operation::CLOSE;
let (a, b, c) = (0, close.from, close.to);
let b_field = close.from;
let c_field = close.to;
Instruction::new(operation, a, b, c, false, false, false)
InstructionBuilder {
operation,
b_field,
c_field,
..Default::default()
}
.build()
}
}
impl Display for Close {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let Close { from, to } = self;
write!(f, "{from}..={to}")
}
}

View File

@ -1,20 +1,28 @@
use crate::{Argument, Instruction, Operation};
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
pub struct Divide {
pub destination: u8,
pub left: Argument,
pub right: Argument,
pub destination: u16,
pub left: Operand,
pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
}
impl From<Instruction> for Divide {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let (left, right) = instruction.b_and_c_as_arguments();
let (left, right) = instruction.b_and_c_as_operands();
let left_type = instruction.b_type();
let right_type = instruction.c_type();
Divide {
destination,
left,
left_type,
right,
right_type,
}
}
}
@ -22,10 +30,40 @@ impl From<Instruction> for Divide {
impl From<Divide> for Instruction {
fn from(divide: Divide) -> Self {
let operation = Operation::DIVIDE;
let a = divide.destination;
let (b, b_is_constant) = divide.left.as_index_and_constant_flag();
let (c, c_is_constant) = divide.right.as_index_and_constant_flag();
let a_field = divide.destination;
let (b_field, b_is_constant) = divide.left.as_index_and_constant_flag();
let (c_field, c_is_constant) = divide.right.as_index_and_constant_flag();
let b_type = divide.left_type;
let c_type = divide.right_type;
Instruction::new(operation, a, b, c, b_is_constant, c_is_constant, false)
InstructionBuilder {
operation,
a_field,
b_field,
c_field,
b_is_constant,
c_is_constant,
b_type,
c_type,
..Default::default()
}
.build()
}
}
impl Display for Divide {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let Divide {
destination,
left,
left_type,
right,
right_type,
} = self;
write!(
f,
"R{destination} = {left_type}({left}) ÷ {right_type}({right})",
)
}
}

View File

@ -1,27 +1,70 @@
use crate::{Argument, Instruction, Operation};
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
pub struct Equal {
pub value: bool,
pub left: Argument,
pub right: Argument,
pub comparator: bool,
pub left: Operand,
pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
}
impl From<Instruction> for Equal {
fn from(instruction: Instruction) -> Self {
let value = instruction.d_field();
let (left, right) = instruction.b_and_c_as_arguments();
let comparator = instruction.d_field();
let (left, right) = instruction.b_and_c_as_operands();
let left_type = instruction.b_type();
let right_type = instruction.c_type();
Equal { value, left, right }
Equal {
comparator,
left,
left_type,
right,
right_type,
}
}
}
impl From<Equal> for Instruction {
fn from(equal: Equal) -> Self {
fn from(equal_bool: Equal) -> Self {
let operation = Operation::EQUAL;
let (b, b_is_constant) = equal.left.as_index_and_constant_flag();
let (c, c_is_constant) = equal.right.as_index_and_constant_flag();
let d = equal.value;
let (b_field, b_is_constant) = equal_bool.left.as_index_and_constant_flag();
let (c_field, c_is_constant) = equal_bool.right.as_index_and_constant_flag();
let d_field = equal_bool.comparator;
let b_type = equal_bool.left_type;
let c_type = equal_bool.right_type;
Instruction::new(operation, 0, b, c, b_is_constant, c_is_constant, d)
InstructionBuilder {
operation,
b_field,
c_field,
d_field,
b_is_constant,
c_is_constant,
b_type,
c_type,
..Default::default()
}
.build()
}
}
impl Display for Equal {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let Equal {
comparator,
left,
left_type,
right,
right_type,
} = self;
let operator = if *comparator { "==" } else { "" };
write!(
f,
"if {left}({left_type}) {operator} {right}({right_type}) {{ JUMP +1 }}"
)
}
}

View File

@ -1,8 +1,12 @@
use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct GetLocal {
pub destination: u8,
pub local_index: u8,
pub destination: u16,
pub local_index: u16,
}
impl From<Instruction> for GetLocal {
@ -20,9 +24,26 @@ impl From<Instruction> for GetLocal {
impl From<GetLocal> for Instruction {
fn from(get_local: GetLocal) -> Self {
let operation = Operation::GET_LOCAL;
let a = get_local.destination;
let b = get_local.local_index;
let a_field = get_local.destination;
let b_field = get_local.local_index;
Instruction::new(operation, a, b, 0, false, false, false)
InstructionBuilder {
operation,
a_field,
b_field,
..Default::default()
}
.build()
}
}
impl Display for GetLocal {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let GetLocal {
destination,
local_index,
} = self;
write!(f, "R{destination} = L{local_index}")
}
}

View File

@ -1,7 +1,11 @@
use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct Jump {
pub offset: u8,
pub offset: u16,
pub is_positive: bool,
}
@ -17,9 +21,27 @@ impl From<Instruction> for Jump {
impl From<Jump> for Instruction {
fn from(jump: Jump) -> Self {
let operation = Operation::JUMP;
let b = jump.offset;
let c = jump.is_positive as u8;
let b_field = jump.offset;
let c_field = jump.is_positive as u16;
Instruction::new(operation, 0, b, c, false, false, false)
InstructionBuilder {
operation,
b_field,
c_field,
..Default::default()
}
.build()
}
}
impl Display for Jump {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let Jump {
offset,
is_positive,
} = self;
let sign = if *is_positive { "+" } else { "-" };
write!(f, "JUMP {sign}{offset}")
}
}

View File

@ -1,27 +1,70 @@
use crate::{Argument, Instruction, Operation};
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
pub struct Less {
pub value: bool,
pub left: Argument,
pub right: Argument,
pub comparator: bool,
pub left: Operand,
pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
}
impl From<Instruction> for Less {
fn from(instruction: Instruction) -> Self {
let value = instruction.d_field();
let (left, right) = instruction.b_and_c_as_arguments();
let comparator = instruction.d_field();
let (left, right) = instruction.b_and_c_as_operands();
let left_type = instruction.b_type();
let right_type = instruction.c_type();
Less { value, left, right }
Less {
comparator,
left,
left_type,
right,
right_type,
}
}
}
impl From<Less> for Instruction {
fn from(less: Less) -> Self {
let operation = Operation::LESS;
let (b, b_is_constant) = less.left.as_index_and_constant_flag();
let (c, c_is_constant) = less.right.as_index_and_constant_flag();
let d = less.value;
let (b_field, b_is_constant) = less.left.as_index_and_constant_flag();
let (c_field, c_is_constant) = less.right.as_index_and_constant_flag();
let d_field = less.comparator;
let b_type = less.left_type;
let c_type = less.right_type;
Instruction::new(operation, 0, b, c, b_is_constant, c_is_constant, d)
InstructionBuilder {
operation,
b_field,
c_field,
d_field,
b_is_constant,
c_is_constant,
b_type,
c_type,
..Default::default()
}
.build()
}
}
impl Display for Less {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let Less {
comparator,
left,
left_type,
right,
right_type,
} = self;
let operator = if *comparator { "<" } else { "" };
write!(
f,
"if {left_type}({left}) {operator} {right_type}({right}) {{ JUMP +1 }}"
)
}
}

View File

@ -1,27 +1,70 @@
use crate::{Argument, Instruction, Operation};
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
pub struct LessEqual {
pub value: bool,
pub left: Argument,
pub right: Argument,
pub comparator: bool,
pub left: Operand,
pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
}
impl From<Instruction> for LessEqual {
fn from(instruction: Instruction) -> Self {
let value = instruction.d_field();
let (left, right) = instruction.b_and_c_as_arguments();
let comparator = instruction.d_field();
let (left, right) = instruction.b_and_c_as_operands();
let left_type = instruction.b_type();
let right_type = instruction.c_type();
LessEqual { value, left, right }
LessEqual {
comparator,
left,
left_type,
right,
right_type,
}
}
}
impl From<LessEqual> for Instruction {
fn from(less_equal: LessEqual) -> Self {
fn from(less_equal_byte: LessEqual) -> Self {
let operation = Operation::LESS_EQUAL;
let (b, b_options) = less_equal.left.as_index_and_constant_flag();
let (c, c_options) = less_equal.right.as_index_and_constant_flag();
let d = less_equal.value;
let (b_field, b_is_constant) = less_equal_byte.left.as_index_and_constant_flag();
let (c_field, c_is_constant) = less_equal_byte.right.as_index_and_constant_flag();
let d_field = less_equal_byte.comparator;
let b_type = less_equal_byte.left_type;
let c_type = less_equal_byte.right_type;
Instruction::new(operation, 0, b, c, b_options, c_options, d)
InstructionBuilder {
operation,
b_field,
c_field,
d_field,
b_is_constant,
c_is_constant,
b_type,
c_type,
..Default::default()
}
.build()
}
}
impl Display for LessEqual {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let LessEqual {
comparator,
left,
left_type,
right,
right_type,
} = self;
let operator = if *comparator { "" } else { ">" };
write!(
f,
"if {left_type}({left}) {operator} {right_type}({right}) {{ JUMP +1 }}"
)
}
}

View File

@ -1,7 +1,11 @@
use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct LoadBoolean {
pub destination: u8,
pub destination: u16,
pub value: bool,
pub jump_next: bool,
}
@ -19,10 +23,35 @@ impl From<Instruction> for LoadBoolean {
impl From<LoadBoolean> for Instruction {
fn from(load_boolean: LoadBoolean) -> Self {
let operation = Operation::LOAD_BOOLEAN;
let a = load_boolean.destination;
let b = load_boolean.value as u8;
let c = load_boolean.jump_next as u8;
let a_field = load_boolean.destination;
let b_field = load_boolean.value as u16;
let c_field = load_boolean.jump_next as u16;
Instruction::new(operation, a, b, c, false, false, false)
InstructionBuilder {
operation,
a_field,
b_field,
c_field,
..Default::default()
}
.build()
}
}
impl Display for LoadBoolean {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let LoadBoolean {
destination,
value,
jump_next,
} = self;
write!(f, "R{destination} = {value}")?;
if *jump_next {
write!(f, " JUMP +1")?;
}
Ok(())
}
}

View File

@ -2,9 +2,11 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct LoadConstant {
pub destination: u8,
pub constant_index: u8,
pub destination: u16,
pub constant_index: u16,
pub jump_next: bool,
}
@ -24,12 +26,14 @@ impl From<Instruction> for LoadConstant {
impl From<LoadConstant> for Instruction {
fn from(load_constant: LoadConstant) -> Self {
let operation = Operation::LOAD_CONSTANT;
let a = load_constant.destination;
let b = load_constant.constant_index;
let c = load_constant.jump_next as u8;
Instruction::new(operation, a, b, c, false, false, false)
InstructionBuilder {
operation: Operation::LOAD_CONSTANT,
a_field: load_constant.destination,
b_field: load_constant.constant_index,
c_field: load_constant.jump_next as u16,
..Default::default()
}
.build()
}
}
@ -41,12 +45,12 @@ impl Display for LoadConstant {
jump_next,
} = self;
write!(f, "R{destination} = Constant {constant_index}")?;
write!(f, "R{destination} = C{constant_index}")?;
if *jump_next {
write!(f, " JUMP +1")
} else {
Ok(())
write!(f, " JUMP +1")?;
}
Ok(())
}
}

View File

@ -1,40 +1,54 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, Operation};
use super::{Instruction, InstructionBuilder, Operation};
pub struct LoadFunction {
pub destination: u8,
pub prototype_index: u8,
pub destination: u16,
pub prototype_index: u16,
pub jump_next: bool,
}
impl From<Instruction> for LoadFunction {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let record_index = instruction.b_field();
let prototype_index = instruction.b_field();
let jump_next = instruction.c_field() != 0;
LoadFunction {
destination,
prototype_index: record_index,
prototype_index,
jump_next,
}
}
}
impl From<LoadFunction> for Instruction {
fn from(load_function: LoadFunction) -> Self {
Instruction::new(
Operation::LOAD_FUNCTION,
load_function.destination,
load_function.prototype_index,
0,
false,
false,
false,
)
InstructionBuilder {
operation: Operation::LOAD_FUNCTION,
a_field: load_function.destination,
b_field: load_function.prototype_index,
c_field: load_function.jump_next as u16,
..Default::default()
}
.build()
}
}
impl Display for LoadFunction {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "R{} = F{}", self.destination, self.prototype_index)
let LoadFunction {
destination,
prototype_index,
jump_next,
} = self;
write!(f, "R{destination} = P{prototype_index}")?;
if *jump_next {
write!(f, " JUMP +1")?;
}
Ok(())
}
}

View File

@ -1,28 +1,56 @@
use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct LoadList {
pub destination: u8,
pub start_register: u8,
pub destination: u16,
pub start_register: u16,
pub jump_next: bool,
}
impl From<Instruction> for LoadList {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let start_register = instruction.b_field();
let jump_next = instruction.c_field() != 0;
LoadList {
destination,
start_register,
jump_next,
}
}
}
impl From<LoadList> for Instruction {
fn from(load_list: LoadList) -> Self {
let operation = Operation::LOAD_LIST;
let a = load_list.destination;
let b = load_list.start_register;
Instruction::new(operation, a, b, 0, false, false, false)
InstructionBuilder {
operation: Operation::LOAD_LIST,
a_field: load_list.destination,
b_field: load_list.start_register,
c_field: load_list.jump_next as u16,
..Default::default()
}
.build()
}
}
impl Display for LoadList {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let LoadList {
destination,
start_register,
jump_next,
} = self;
write!(f, "R{destination} = [R{start_register}..R{destination}]")?;
if *jump_next {
write!(f, " JUMP +1")?;
}
Ok(())
}
}

View File

@ -1,22 +1,51 @@
use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct LoadSelf {
pub destination: u8,
pub destination: u16,
pub jump_next: bool,
}
impl From<Instruction> for LoadSelf {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let jump_next = instruction.c_field() != 0;
LoadSelf { destination }
LoadSelf {
destination,
jump_next,
}
}
}
impl From<LoadSelf> for Instruction {
fn from(load_self: LoadSelf) -> Self {
let operation = Operation::LOAD_SELF;
let a = load_self.destination;
Instruction::new(operation, a, 0, 0, false, false, false)
InstructionBuilder {
operation: Operation::LOAD_SELF,
a_field: load_self.destination,
c_field: load_self.jump_next as u16,
..Default::default()
}
.build()
}
}
impl Display for LoadSelf {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let LoadSelf {
destination,
jump_next,
} = self;
write!(f, "R{destination} = SELF")?;
if *jump_next {
write!(f, " JUMP +1")?;
}
Ok(())
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,28 @@
use crate::{Argument, Instruction, Operation};
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
pub struct Modulo {
pub destination: u8,
pub left: Argument,
pub right: Argument,
pub destination: u16,
pub left: Operand,
pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
}
impl From<Instruction> for Modulo {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let (left, right) = instruction.b_and_c_as_arguments();
let (left, right) = instruction.b_and_c_as_operands();
let left_type = instruction.b_type();
let right_type = instruction.c_type();
Modulo {
destination,
left,
left_type,
right,
right_type,
}
}
}
@ -22,10 +30,40 @@ impl From<Instruction> for Modulo {
impl From<Modulo> for Instruction {
fn from(modulo: Modulo) -> Self {
let operation = Operation::MODULO;
let a = modulo.destination;
let (b, b_is_constant) = modulo.left.as_index_and_constant_flag();
let (c, c_is_constant) = modulo.right.as_index_and_constant_flag();
let a_field = modulo.destination;
let (b_field, b_is_constant) = modulo.left.as_index_and_constant_flag();
let (c_field, c_is_constant) = modulo.right.as_index_and_constant_flag();
let b_type = modulo.left_type;
let c_type = modulo.right_type;
Instruction::new(operation, a, b, c, b_is_constant, c_is_constant, false)
InstructionBuilder {
operation,
a_field,
b_field,
c_field,
b_is_constant,
c_is_constant,
b_type,
c_type,
..Default::default()
}
.build()
}
}
impl Display for Modulo {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let Modulo {
destination,
left,
left_type,
right,
right_type,
} = self;
write!(
f,
"R{destination} = {left_type}({left}) % {right_type}({right})",
)
}
}

View File

@ -1,20 +1,28 @@
use crate::{Argument, Instruction, Operation};
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
pub struct Multiply {
pub destination: u8,
pub left: Argument,
pub right: Argument,
pub destination: u16,
pub left: Operand,
pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
}
impl From<Instruction> for Multiply {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let (left, right) = instruction.b_and_c_as_arguments();
let (left, right) = instruction.b_and_c_as_operands();
let left_type = instruction.b_type();
let right_type = instruction.c_type();
Multiply {
destination,
left,
left_type,
right,
right_type,
}
}
}
@ -22,10 +30,40 @@ impl From<Instruction> for Multiply {
impl From<Multiply> for Instruction {
fn from(multiply: Multiply) -> Self {
let operation = Operation::MULTIPLY;
let a = multiply.destination;
let (b, b_options) = multiply.left.as_index_and_constant_flag();
let (c, c_options) = multiply.right.as_index_and_constant_flag();
let a_field = multiply.destination;
let (b_field, b_is_constant) = multiply.left.as_index_and_constant_flag();
let (c_field, c_is_constant) = multiply.right.as_index_and_constant_flag();
let b_type = multiply.left_type;
let c_type = multiply.right_type;
Instruction::new(operation, a, b, c, b_options, c_options, false)
InstructionBuilder {
operation,
a_field,
b_field,
c_field,
b_is_constant,
c_is_constant,
b_type,
c_type,
..Default::default()
}
.build()
}
}
impl Display for Multiply {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let Multiply {
destination,
left,
left_type,
right,
right_type,
} = self;
write!(
f,
"R{destination} = {left_type}({left}) ✕ {right_type}({right})",
)
}
}

View File

@ -1,18 +1,23 @@
use crate::{Argument, Instruction, Operation};
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
pub struct Negate {
pub destination: u8,
pub argument: Argument,
pub destination: u16,
pub argument: Operand,
pub argument_type: TypeCode,
}
impl From<Instruction> for Negate {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let argument = instruction.b_as_argument();
let argument_type = instruction.b_type();
Negate {
destination,
argument,
argument_type,
}
}
}
@ -20,10 +25,30 @@ impl From<Instruction> for Negate {
impl From<Negate> for Instruction {
fn from(negate: Negate) -> Self {
let operation = Operation::NEGATE;
let a = negate.destination;
let (b, b_is_constant) = negate.argument.as_index_and_constant_flag();
let c = 0;
let a_field = negate.destination;
let (b_field, b_is_constant) = negate.argument.as_index_and_constant_flag();
let b_type = negate.argument_type;
Instruction::new(operation, a, b, c, b_is_constant, false, false)
InstructionBuilder {
operation,
a_field,
b_field,
b_is_constant,
b_type,
..Default::default()
}
.build()
}
}
impl Display for Negate {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let Negate {
destination,
argument,
argument_type,
} = self;
write!(f, "R{destination} = -{argument_type}({argument})")
}
}

View File

@ -1,8 +1,12 @@
use crate::{Argument, Instruction, Operation};
use std::fmt::Display;
use crate::{Instruction, Operand, Operation};
use super::InstructionBuilder;
pub struct Not {
pub destination: u8,
pub argument: Argument,
pub destination: u16,
pub argument: Operand,
}
impl From<Instruction> for Not {
@ -20,9 +24,27 @@ impl From<Instruction> for Not {
impl From<Not> for Instruction {
fn from(not: Not) -> Self {
let operation = Operation::NOT;
let a = not.destination;
let (b, b_is_constant) = not.argument.as_index_and_constant_flag();
let a_field = not.destination;
let (b_field, b_is_constant) = not.argument.as_index_and_constant_flag();
Instruction::new(operation, a, b, 0, b_is_constant, false, false)
InstructionBuilder {
operation,
a_field,
b_field,
b_is_constant,
..Default::default()
}
.build()
}
}
impl Display for Not {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Not {
destination,
argument,
} = self;
write!(f, "R{destination} = !{argument}")
}
}

View File

@ -9,29 +9,46 @@ use serde::{Deserialize, Serialize};
pub struct Operation(pub u8);
impl Operation {
// Stack manipulation
pub const POINT: Operation = Operation(0);
pub const CLOSE: Operation = Operation(1);
// Loaders
pub const LOAD_BOOLEAN: Operation = Operation(2);
pub const LOAD_CONSTANT: Operation = Operation(3);
pub const LOAD_FUNCTION: Operation = Operation(4);
pub const LOAD_LIST: Operation = Operation(5);
pub const LOAD_SELF: Operation = Operation(6);
// Locals
pub const GET_LOCAL: Operation = Operation(7);
pub const SET_LOCAL: Operation = Operation(8);
// Arithmetic
pub const ADD: Operation = Operation(9);
pub const SUBTRACT: Operation = Operation(10);
pub const MULTIPLY: Operation = Operation(11);
pub const DIVIDE: Operation = Operation(12);
pub const MODULO: Operation = Operation(13);
pub const TEST: Operation = Operation(14);
pub const TEST_SET: Operation = Operation(15);
pub const EQUAL: Operation = Operation(16);
pub const LESS: Operation = Operation(17);
pub const LESS_EQUAL: Operation = Operation(18);
pub const NEGATE: Operation = Operation(19);
pub const NOT: Operation = Operation(20);
// Comparison
pub const EQUAL: Operation = Operation(14);
pub const LESS: Operation = Operation(15);
pub const LESS_EQUAL: Operation = Operation(16);
// Unary operations
pub const NEGATE: Operation = Operation(17);
pub const NOT: Operation = Operation(18);
// Logical operations
pub const TEST: Operation = Operation(19);
pub const TEST_SET: Operation = Operation(20);
// Function calls
pub const CALL: Operation = Operation(21);
pub const CALL_NATIVE: Operation = Operation(22);
// Control flow
pub const JUMP: Operation = Operation(23);
pub const RETURN: Operation = Operation(24);
}
@ -53,23 +70,41 @@ impl Operation {
Self::MULTIPLY => "MULTIPLY",
Self::DIVIDE => "DIVIDE",
Self::MODULO => "MODULO",
Self::TEST => "TEST",
Self::TEST_SET => "TEST_SET",
Self::EQUAL => "EQUAL",
Self::LESS => "LESS",
Self::LESS_EQUAL => "LESS_EQUAL",
Self::NEGATE => "NEGATE",
Self::NOT => "NOT",
Self::TEST => "TEST",
Self::TEST_SET => "TEST_SET",
Self::CALL => "CALL",
Self::CALL_NATIVE => "CALL_NATIVE",
Self::JUMP => "JUMP",
Self::RETURN => "RETURN",
_ => Self::panic_from_unknown_code(self.0),
_ => self.panic_from_unknown_code(),
}
}
pub fn panic_from_unknown_code(code: u8) -> ! {
panic!("Unknown operation code: {code}");
pub fn is_math(self) -> bool {
matches!(
self,
Operation::ADD
| Operation::SUBTRACT
| Operation::MULTIPLY
| Operation::DIVIDE
| Operation::MODULO
)
}
pub fn is_comparison(self) -> bool {
matches!(
self,
Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL
)
}
pub fn panic_from_unknown_code(self) -> ! {
panic!("Unknown operation code: {}", self.0);
}
}

View File

@ -2,9 +2,11 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct Point {
pub from: u8,
pub to: u8,
pub from: u16,
pub to: u16,
}
impl From<Instruction> for Point {
@ -16,6 +18,22 @@ impl From<Instruction> for Point {
}
}
impl From<Point> for Instruction {
fn from(r#move: Point) -> Self {
let operation = Operation::POINT;
let b_field = r#move.from;
let c_field = r#move.to;
InstructionBuilder {
operation,
b_field,
c_field,
..Default::default()
}
.build()
}
}
impl Display for Point {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let Point { from, to } = self;
@ -23,13 +41,3 @@ impl Display for Point {
write!(f, "{from} -> {to}")
}
}
impl From<Point> for Instruction {
fn from(r#move: Point) -> Self {
let operation = Operation::POINT;
let b = r#move.from;
let c = r#move.to;
Instruction::new(operation, 0, b, c, false, false, false)
}
}

View File

@ -1,8 +1,12 @@
use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct Return {
pub should_return_value: bool,
pub return_register: u8,
pub return_register: u16,
}
impl From<Instruction> for Return {
@ -20,9 +24,30 @@ impl From<Instruction> for Return {
impl From<Return> for Instruction {
fn from(r#return: Return) -> Self {
let operation = Operation::RETURN;
let b = r#return.should_return_value as u8;
let c = r#return.return_register;
let b_field = r#return.should_return_value as u16;
let c_field = r#return.return_register;
Instruction::new(operation, 0, b, c, false, false, false)
InstructionBuilder {
operation,
b_field,
c_field,
..Default::default()
}
.build()
}
}
impl Display for Return {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let Return {
should_return_value,
return_register,
} = self;
if *should_return_value {
write!(f, "RETURN R{return_register}")
} else {
write!(f, "RETURN")
}
}
}

View File

@ -1,8 +1,12 @@
use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct SetLocal {
pub register_index: u8,
pub local_index: u8,
pub register_index: u16,
pub local_index: u16,
}
impl From<Instruction> for SetLocal {
@ -20,9 +24,26 @@ impl From<Instruction> for SetLocal {
impl From<SetLocal> for Instruction {
fn from(set_local: SetLocal) -> Self {
let operation = Operation::SET_LOCAL;
let b = set_local.register_index;
let c = set_local.local_index;
let b_field = set_local.register_index;
let c_field = set_local.local_index;
Instruction::new(operation, 0, b, c, false, false, false)
InstructionBuilder {
operation,
b_field,
c_field,
..Default::default()
}
.build()
}
}
impl Display for SetLocal {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let SetLocal {
register_index,
local_index,
} = self;
write!(f, "L{local_index} = R{register_index}")
}
}

View File

@ -1,20 +1,28 @@
use crate::{Argument, Instruction, Operation};
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
pub struct Subtract {
pub destination: u8,
pub left: Argument,
pub right: Argument,
pub destination: u16,
pub left: Operand,
pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
}
impl From<Instruction> for Subtract {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let (left, right) = instruction.b_and_c_as_arguments();
let (left, right) = instruction.b_and_c_as_operands();
let left_type = instruction.b_type();
let right_type = instruction.c_type();
Subtract {
destination,
left,
left_type,
right,
right_type,
}
}
}
@ -22,10 +30,40 @@ impl From<Instruction> for Subtract {
impl From<Subtract> for Instruction {
fn from(subtract: Subtract) -> Self {
let operation = Operation::SUBTRACT;
let a = subtract.destination;
let (b, b_is_constant) = subtract.left.as_index_and_constant_flag();
let (c, c_is_constant) = subtract.right.as_index_and_constant_flag();
let a_field = subtract.destination;
let (b_field, b_is_constant) = subtract.left.as_index_and_constant_flag();
let (c_field, c_is_constant) = subtract.right.as_index_and_constant_flag();
let b_type = subtract.left_type;
let c_type = subtract.right_type;
Instruction::new(operation, a, b, c, b_is_constant, c_is_constant, false)
InstructionBuilder {
operation,
a_field,
b_field,
c_field,
b_is_constant,
c_is_constant,
b_type,
c_type,
..Default::default()
}
.build()
}
}
impl Display for Subtract {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let Subtract {
destination,
left,
left_type,
right,
right_type,
} = self;
write!(
f,
"R{destination} = {left_type}({left}) - {right_type}({right})",
)
}
}

View File

@ -1,7 +1,11 @@
use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct Test {
pub operand_register: u8,
pub operand_register: u16,
pub test_value: bool,
}
@ -19,14 +23,27 @@ impl From<Instruction> for Test {
impl From<Test> for Instruction {
fn from(test: Test) -> Self {
Instruction::new(
Operation::TEST,
0,
test.operand_register,
test.test_value as u8,
false,
false,
false,
)
let b_field = test.operand_register;
let c_field = test.test_value as u16;
InstructionBuilder {
operation: Operation::TEST,
b_field,
c_field,
..Default::default()
}
.build()
}
}
impl Display for Test {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let Test {
operand_register,
test_value,
} = self;
let bang = if *test_value { "" } else { "!" };
write!(f, "if {bang}R{operand_register} {{ JUMP +1 }}")
}
}

View File

@ -1,8 +1,12 @@
use crate::{Argument, Instruction, Operation};
use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operand, Operation};
use super::InstructionBuilder;
pub struct TestSet {
pub destination: u8,
pub argument: Argument,
pub destination: u16,
pub argument: Operand,
pub test_value: bool,
}
@ -23,10 +27,34 @@ impl From<Instruction> for TestSet {
impl From<TestSet> for Instruction {
fn from(test_set: TestSet) -> Self {
let operation = Operation::TEST;
let a = test_set.destination;
let (b, b_is_constant) = test_set.argument.as_index_and_constant_flag();
let c = test_set.test_value as u8;
let a_field = test_set.destination;
let (b_field, b_is_constant) = test_set.argument.as_index_and_constant_flag();
let c_field = test_set.test_value as u16;
Instruction::new(operation, a, b, c, b_is_constant, false, false)
InstructionBuilder {
operation,
a_field,
b_field,
c_field,
b_is_constant,
..Default::default()
}
.build()
}
}
impl Display for TestSet {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let TestSet {
destination,
argument,
test_value,
} = self;
let bang = if *test_value { "" } else { "!" };
write!(
f,
"if {bang}{argument} {{ JUMP +1 }} else {{ R{destination} = {argument} }}"
)
}
}

View File

@ -0,0 +1,31 @@
use std::fmt::Display;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TypeCode(pub u8);
impl TypeCode {
pub const BOOLEAN: TypeCode = TypeCode(0);
pub const BYTE: TypeCode = TypeCode(1);
pub const CHARACTER: TypeCode = TypeCode(2);
pub const FLOAT: TypeCode = TypeCode(3);
pub const INTEGER: TypeCode = TypeCode(4);
pub const STRING: TypeCode = TypeCode(5);
pub fn panic_from_unknown_code(self) -> ! {
panic!("Unknown type code: {}", self.0);
}
}
impl Display for TypeCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
TypeCode::BOOLEAN => write!(f, "bool"),
TypeCode::BYTE => write!(f, "byte"),
TypeCode::CHARACTER => write!(f, "char"),
TypeCode::FLOAT => write!(f, "float"),
TypeCode::INTEGER => write!(f, "int"),
TypeCode::STRING => write!(f, "str"),
_ => self.panic_from_unknown_code(),
}
}
}

View File

@ -5,7 +5,7 @@
//! - [`Lexer`], which lexes the input a token at a time
use serde::{Deserialize, Serialize};
use crate::{dust_error::AnnotatedError, CompileError, DustError, Span, Token};
use crate::{CompileError, DustError, Span, Token, dust_error::AnnotatedError};
/// Lexes the input and returns a vector of tokens and their positions.
///
@ -83,7 +83,7 @@ impl<'src> Lexer<'src> {
self.skip_whitespace();
let (token, span) = if let Some(character) = self.peek_char() {
let lexer = LexRule::from(&character).lexer;
let lexer = LexRule::from(&character).lex_action;
lexer(self)?
} else {
@ -613,92 +613,92 @@ impl<'src> Lexer<'src> {
}
}
type LexerFn<'src> = fn(&mut Lexer<'src>) -> Result<(Token<'src>, Span), LexError>;
type LexAction<'src> = fn(&mut Lexer<'src>) -> Result<(Token<'src>, Span), LexError>;
pub struct LexRule<'src> {
lexer: LexerFn<'src>,
lex_action: LexAction<'src>,
}
impl From<&char> for LexRule<'_> {
fn from(char: &char) -> Self {
match char {
'0'..='9' => LexRule {
lexer: Lexer::lex_numeric,
lex_action: Lexer::lex_numeric,
},
char if char.is_alphabetic() => LexRule {
lexer: Lexer::lex_keyword_or_identifier,
lex_action: Lexer::lex_keyword_or_identifier,
},
'"' => LexRule {
lexer: Lexer::lex_string,
lex_action: Lexer::lex_string,
},
'\'' => LexRule {
lexer: Lexer::lex_char,
lex_action: Lexer::lex_char,
},
'+' => LexRule {
lexer: Lexer::lex_plus,
lex_action: Lexer::lex_plus,
},
'-' => LexRule {
lexer: Lexer::lex_minus,
lex_action: Lexer::lex_minus,
},
'*' => LexRule {
lexer: Lexer::lex_star,
lex_action: Lexer::lex_star,
},
'/' => LexRule {
lexer: Lexer::lex_slash,
lex_action: Lexer::lex_slash,
},
'%' => LexRule {
lexer: Lexer::lex_percent,
lex_action: Lexer::lex_percent,
},
'!' => LexRule {
lexer: Lexer::lex_exclamation_mark,
lex_action: Lexer::lex_exclamation_mark,
},
'=' => LexRule {
lexer: Lexer::lex_equal,
lex_action: Lexer::lex_equal,
},
'<' => LexRule {
lexer: Lexer::lex_less_than,
lex_action: Lexer::lex_less_than,
},
'>' => LexRule {
lexer: Lexer::lex_greater_than,
lex_action: Lexer::lex_greater_than,
},
'&' => LexRule {
lexer: Lexer::lex_ampersand,
lex_action: Lexer::lex_ampersand,
},
'|' => LexRule {
lexer: Lexer::lex_pipe,
lex_action: Lexer::lex_pipe,
},
'(' => LexRule {
lexer: Lexer::lex_left_parenthesis,
lex_action: Lexer::lex_left_parenthesis,
},
')' => LexRule {
lexer: Lexer::lex_right_parenthesis,
lex_action: Lexer::lex_right_parenthesis,
},
'[' => LexRule {
lexer: Lexer::lex_left_bracket,
lex_action: Lexer::lex_left_bracket,
},
']' => LexRule {
lexer: Lexer::lex_right_bracket,
lex_action: Lexer::lex_right_bracket,
},
'{' => LexRule {
lexer: Lexer::lex_left_brace,
lex_action: Lexer::lex_left_brace,
},
'}' => LexRule {
lexer: Lexer::lex_right_brace,
lex_action: Lexer::lex_right_brace,
},
';' => LexRule {
lexer: Lexer::lex_semicolon,
lex_action: Lexer::lex_semicolon,
},
':' => LexRule {
lexer: Lexer::lex_colon,
lex_action: Lexer::lex_colon,
},
',' => LexRule {
lexer: Lexer::lex_comma,
lex_action: Lexer::lex_comma,
},
'.' => LexRule {
lexer: Lexer::lex_dot,
lex_action: Lexer::lex_dot,
},
_ => LexRule {
lexer: Lexer::lex_unexpected,
lex_action: Lexer::lex_unexpected,
},
}
}

View File

@ -40,17 +40,17 @@ pub mod value;
pub mod vm;
pub use crate::chunk::{Chunk, Disassembler, Local, Scope};
pub use crate::compiler::{compile, CompileError, Compiler};
pub use crate::compiler::{CompileError, Compiler, compile};
pub use crate::dust_error::{AnnotatedError, DustError};
pub use crate::instruction::{Argument, Instruction, InstructionData, Operation};
pub use crate::lexer::{lex, LexError, Lexer};
pub use crate::instruction::{Operand, Instruction, Operation};
pub use crate::lexer::{LexError, Lexer, lex};
pub use crate::native_function::{NativeFunction, NativeFunctionError};
pub use crate::r#type::{EnumType, FunctionType, StructType, Type, TypeConflict};
pub use crate::token::{Token, TokenKind, TokenOwned};
pub use crate::r#type::{EnumType, FunctionType, StructType, Type, TypeConflict};
pub use crate::value::{
AbstractList, ConcreteValue, DustString, Function, RangeValue, Value, ValueError,
};
pub use crate::vm::{run, Pointer, Vm};
pub use crate::vm::{Pointer, Vm, run};
use std::fmt::Display;

View File

@ -2,7 +2,7 @@ use std::{ops::Range, panic};
use crate::vm::ThreadData;
pub fn panic(data: &mut ThreadData, _: Option<u8>, argument_range: Range<u8>) -> bool {
pub fn panic(data: &mut ThreadData, _: u16, argument_range: Range<u16>) -> bool {
let position = data.current_position();
let mut message = format!("Dust panic at {position}!");

View File

@ -1,17 +1,12 @@
use std::io::{stdin, stdout, Write};
use std::io::{Write, stdin, stdout};
use std::ops::Range;
use crate::{
vm::{get_next_action, Register, ThreadData},
ConcreteValue, Value,
vm::{Register, ThreadData, get_next_action},
};
pub fn read_line(
data: &mut ThreadData,
destination: Option<u8>,
_argument_range: Range<u8>,
) -> bool {
let destination = destination.unwrap();
pub fn read_line(data: &mut ThreadData, destination: u16, _argument_range: Range<u16>) -> bool {
let mut buffer = String::new();
if stdin().read_line(&mut buffer).is_ok() {
@ -29,7 +24,7 @@ pub fn read_line(
false
}
pub fn write(data: &mut ThreadData, _destination: Option<u8>, argument_range: Range<u8>) -> bool {
pub fn write(data: &mut ThreadData, _: u16, argument_range: Range<u16>) -> bool {
let mut stdout = stdout();
for register_index in argument_range {
@ -45,11 +40,7 @@ pub fn write(data: &mut ThreadData, _destination: Option<u8>, argument_range: Ra
false
}
pub fn write_line(
data: &mut ThreadData,
_destination: Option<u8>,
argument_range: Range<u8>,
) -> bool {
pub fn write_line(data: &mut ThreadData, _: u16, argument_range: Range<u16>) -> bool {
let mut stdout = stdout().lock();
for register_index in argument_range {

View File

@ -4,7 +4,9 @@
//! itself or that are more efficient to implement in Rust.
mod assert;
mod io;
mod random;
mod string;
mod thread;
use std::{
fmt::{self, Display, Formatter},
@ -15,7 +17,7 @@ use std::{
use serde::{Deserialize, Serialize};
use crate::{vm::ThreadData, AnnotatedError, FunctionType, Span, Type};
use crate::{AnnotatedError, FunctionType, Span, Type, vm::ThreadData};
macro_rules! define_native_function {
($(($name:ident, $bytes:literal, $str:expr, $type:expr, $function:expr)),*) => {
@ -33,8 +35,8 @@ macro_rules! define_native_function {
pub fn call(
&self,
data: &mut ThreadData,
destination: Option<u8>,
argument_range: Range<u8>,
destination: u16,
argument_range: Range<u16>,
) -> bool {
match self {
$(
@ -78,8 +80,8 @@ macro_rules! define_native_function {
}
}
impl From<u8> for NativeFunction {
fn from(bytes: u8) -> Self {
impl From<u16> for NativeFunction {
fn from(bytes: u16) -> Self {
match bytes {
$(
$bytes => NativeFunction::$name,
@ -244,11 +246,42 @@ define_native_function! {
return_type: Type::None
},
io::write_line
)
),
// // Random
// (Random, 58_u8, "random", true),
// (RandomInRange, 59_u8, "random_in_range", true)
(
RandomInteger,
58,
"random_int",
FunctionType {
type_parameters: Vec::with_capacity(0),
value_parameters: vec![(0, Type::Integer), (1, Type::Integer)],
return_type: Type::Integer
},
random::random_int
),
// Thread
(
Spawn,
60,
"spawn",
FunctionType {
type_parameters: Vec::with_capacity(0),
value_parameters: vec![
(
0,
Type::Function(Box::new(FunctionType {
type_parameters: Vec::with_capacity(0),
value_parameters: Vec::with_capacity(0),
return_type: Type::Any
}))
)
],
return_type: Type::None
},
thread::spawn
)
}
#[derive(Debug, Clone, PartialEq)]

View File

@ -0,0 +1,40 @@
use std::ops::Range;
use rand::Rng;
use crate::{
Value,
vm::{Register, ThreadData, get_next_action},
};
pub fn random_int(data: &mut ThreadData, destination: u16, argument_range: Range<u16>) -> bool {
let mut argument_range_iter = argument_range.into_iter();
let (min, max) = {
let mut min = None;
loop {
let register_index = argument_range_iter
.next()
.unwrap_or_else(|| panic!("No argument was passed to \"random_int\""));
let value_option = data.open_register_allow_empty_unchecked(register_index);
if let Some(argument) = value_option {
if let Some(integer) = argument.as_integer() {
if min.is_none() {
min = Some(integer);
} else {
break (min, integer);
}
}
}
}
};
let random_integer = rand::thread_rng().gen_range(min.unwrap()..max);
data.set_register(destination, Register::Value(Value::integer(random_integer)));
data.next_action = get_next_action(data);
false
}

View File

@ -1,18 +1,13 @@
use std::ops::Range;
use crate::{
vm::{get_next_action, Register, ThreadData},
ConcreteValue, Value,
vm::{Register, ThreadData, get_next_action},
};
pub fn to_string(
data: &mut ThreadData,
destination: Option<u8>,
argument_range: Range<u8>,
) -> bool {
pub fn to_string(data: &mut ThreadData, destination: u16, argument_range: Range<u16>) -> bool {
let argument_value = data.open_register_unchecked(argument_range.start);
let argument_string = argument_value.display(data);
let destination = destination.unwrap();
let register = Register::Value(Value::Concrete(ConcreteValue::string(argument_string)));
data.set_register(destination, register);

View File

@ -0,0 +1,64 @@
use std::{
ops::Range,
thread::{Builder, JoinHandle},
};
use tracing::{Level, info, span};
use crate::{
DustString,
vm::{Thread, ThreadData, get_next_action},
};
fn start_thread(data: &mut ThreadData, argument_range: Range<u16>) -> JoinHandle<()> {
let mut argument_range_iter = argument_range.into_iter();
let function_argument = {
loop {
let register_index = argument_range_iter
.next()
.unwrap_or_else(|| panic!("No argument was passed to \"spawn\""));
let value_option = data.open_register_allow_empty_unchecked(register_index);
if let Some(argument) = value_option {
break argument;
}
}
};
let function = function_argument.as_function().unwrap();
let prototype_index = function.prototype_index as usize;
let current_call = data.call_stack.last_unchecked();
let prototype = current_call.chunk.prototypes[prototype_index].clone();
info!(
"Spawning thread for \"{}\"",
function
.name
.as_ref()
.cloned()
.unwrap_or_else(|| DustString::from("anonymous"))
);
let thread_name = prototype
.name
.as_ref()
.map(|name| name.to_string())
.unwrap_or_else(|| "anonymous".to_string());
let mut thread = Thread::new(prototype);
Builder::new()
.name(thread_name)
.spawn(move || {
let span = span!(Level::INFO, "Spawned thread");
let _enter = span.enter();
thread.run();
})
.expect("Critical VM Error: Failed to spawn thread")
}
pub fn spawn(data: &mut ThreadData, _: u16, argument_range: Range<u16>) -> bool {
let _ = start_thread(data, argument_range);
data.next_action = get_next_action(data);
false
}

View File

@ -273,13 +273,13 @@ impl Ord for Type {
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct FunctionType {
pub type_parameters: Vec<u8>,
pub value_parameters: Vec<(u8, Type)>,
pub type_parameters: Vec<u16>,
pub value_parameters: Vec<(u16, Type)>,
pub return_type: Type,
}
impl FunctionType {
pub fn new<T: Into<Vec<u8>>, U: Into<Vec<(u8, Type)>>>(
pub fn new<T: Into<Vec<u16>>, U: Into<Vec<(u16, Type)>>>(
type_parameters: T,
value_parameters: U,
return_type: Type,

View File

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

View File

@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize};
use std::fmt::{self, Debug, Display, Formatter};
use crate::{vm::ThreadData, Type};
use crate::{Type, vm::ThreadData};
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
pub enum Value {
@ -50,9 +50,33 @@ impl Value {
Value::Concrete(ConcreteValue::String(string.into()))
}
pub fn as_boolean(&self) -> Option<&bool> {
if let Value::Concrete(ConcreteValue::Boolean(value)) = self {
Some(value)
pub fn as_boolean(&self) -> Option<bool> {
if let Value::Concrete(ConcreteValue::Boolean(boolean)) = self {
Some(*boolean)
} else {
None
}
}
pub fn as_byte(&self) -> Option<u8> {
if let Value::Concrete(ConcreteValue::Byte(byte)) = self {
Some(*byte)
} else {
None
}
}
pub fn as_character(&self) -> Option<char> {
if let Value::Concrete(ConcreteValue::Character(character)) = self {
Some(*character)
} else {
None
}
}
pub fn as_float(&self) -> Option<f64> {
if let Value::Concrete(ConcreteValue::Float(float)) = self {
Some(*float)
} else {
None
}
@ -66,6 +90,14 @@ impl Value {
}
}
pub fn as_integer(&self) -> Option<i64> {
if let Value::Concrete(ConcreteValue::Integer(integer)) = self {
Some(*integer)
} else {
None
}
}
pub fn as_string(&self) -> Option<&DustString> {
if let Value::Concrete(ConcreteValue::String(value)) = self {
Some(value)
@ -74,6 +106,14 @@ impl Value {
}
}
pub fn is_string(&self) -> bool {
matches!(self, Value::Concrete(ConcreteValue::String(_)))
}
pub fn is_function(&self) -> bool {
matches!(self, Value::Function(_))
}
pub fn r#type(&self) -> Type {
match self {
Value::Concrete(concrete_value) => concrete_value.r#type(),

View File

@ -1,29 +1,34 @@
use std::fmt::{self, Debug, Display, Formatter};
use std::{
fmt::{self, Debug, Display, Formatter},
sync::Arc,
};
use crate::{Chunk, DustString};
use super::Register;
#[derive(Debug)]
pub struct FunctionCall<'a> {
pub chunk: &'a Chunk,
pub struct FunctionCall {
pub chunk: Arc<Chunk>,
pub ip: usize,
pub return_register: u8,
pub return_register: u16,
pub registers: Vec<Register>,
}
impl<'a> FunctionCall<'a> {
pub fn new(chunk: &'a Chunk, return_register: u8) -> Self {
impl FunctionCall {
pub fn new(chunk: Arc<Chunk>, return_register: u16) -> Self {
let register_count = chunk.register_count;
Self {
chunk,
ip: 0,
return_register,
registers: vec![Register::Empty; chunk.register_count],
registers: vec![Register::Empty; register_count],
}
}
}
impl Display for FunctionCall<'_> {
impl Display for FunctionCall {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,

View File

@ -6,19 +6,20 @@ mod thread;
use std::{
fmt::{self, Debug, Display, Formatter},
sync::mpsc,
thread::spawn,
sync::Arc,
thread::Builder,
};
pub use function_call::FunctionCall;
pub(crate) use run_action::get_next_action;
pub use run_action::RunAction;
pub(crate) use run_action::get_next_action;
pub use stack::Stack;
pub use thread::{Thread, ThreadData};
use tracing::{span, Level};
use crossbeam_channel::bounded;
use tracing::{Level, span};
use crate::{compile, Chunk, DustError, Value};
use crate::{Chunk, DustError, Value, compile};
pub fn run(source: &str) -> Result<Option<Value>, DustError> {
let chunk = compile(source)?;
@ -28,41 +29,37 @@ pub fn run(source: &str) -> Result<Option<Value>, DustError> {
}
pub struct Vm {
threads: Vec<Thread>,
main_chunk: Chunk,
}
impl Vm {
pub fn new(chunk: Chunk) -> Self {
let threads = vec![Thread::new(chunk)];
debug_assert_eq!(1, threads.capacity());
Self { threads }
pub fn new(main_chunk: Chunk) -> Self {
Self { main_chunk }
}
pub fn run(mut self) -> Option<Value> {
pub fn run(self) -> Option<Value> {
let span = span!(Level::INFO, "Run");
let _enter = span.enter();
let thread_name = self
.main_chunk
.name
.as_ref()
.map(|name| name.to_string())
.unwrap_or_else(|| "anonymous".to_string());
let mut main_thread = Thread::new(Arc::new(self.main_chunk));
let (tx, rx) = bounded(1);
if self.threads.len() == 1 {
return self.threads[0].run();
}
Builder::new()
.name(thread_name)
.spawn(move || {
let value_option = main_thread.run();
let _ = tx.send(value_option);
})
.unwrap()
.join()
.unwrap();
let (tx, rx) = mpsc::channel();
for mut thread in self.threads {
let tx = tx.clone();
spawn(move || {
let return_value = thread.run();
if let Some(value) = return_value {
tx.send(value).unwrap();
}
});
}
rx.into_iter().last()
rx.recv().unwrap_or(None)
}
}
@ -85,9 +82,9 @@ impl Display for Register {
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum Pointer {
Register(u8),
Constant(u8),
Stack(usize, u8),
Register(u16),
Constant(u16),
Stack(usize, u16),
}
impl Display for Pointer {

View File

@ -1,16 +1,16 @@
use tracing::trace;
use crate::{
AbstractList, ConcreteValue, Instruction, Operand, Type, Value,
instruction::{
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,
Return, SetLocal, Subtract, Test, TestSet, TypeCode,
},
vm::FunctionCall,
AbstractList, Argument, ConcreteValue, Instruction, Type, Value,
};
use super::{thread::ThreadData, Pointer, Register};
use super::{Pointer, Register, thread::ThreadData};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct RunAction {
@ -44,13 +44,13 @@ pub const RUNNER_LOGIC_TABLE: [RunnerLogic; 25] = [
multiply,
divide,
modulo,
test,
test_set,
equal,
less,
less_equal,
negate,
not,
test,
test_set,
call,
call_native,
jump,
@ -145,6 +145,7 @@ pub fn load_list(instruction: Instruction, data: &mut ThreadData) -> bool {
let LoadList {
destination,
start_register,
jump_next,
} = instruction.into();
let mut item_pointers = Vec::with_capacity((destination - start_register) as usize);
let mut item_type = Type::Any;
@ -186,6 +187,7 @@ pub fn load_function(instruction: Instruction, data: &mut ThreadData) -> bool {
let LoadFunction {
destination,
prototype_index,
jump_next,
} = instruction.into();
let prototype_index = prototype_index as usize;
let current_call = data.call_stack.last_mut_unchecked();
@ -201,7 +203,10 @@ pub fn load_function(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn load_self(instruction: Instruction, data: &mut ThreadData) -> bool {
let LoadSelf { destination } = instruction.into();
let LoadSelf {
destination,
jump_next,
} = instruction.into();
let current_call = data.call_stack.last_mut_unchecked();
let prototype = &current_call.chunk;
let function = prototype.as_function();
@ -248,12 +253,42 @@ pub fn add(instruction: Instruction, data: &mut ThreadData) -> bool {
let Add {
destination,
left,
left_type,
right,
right_type,
} = instruction.into();
let left = data.get_argument_unchecked(left);
let right = data.get_argument_unchecked(right);
let sum = left.add(right);
let register = Register::Value(sum);
let sum = match (left_type, right_type) {
(TypeCode::INTEGER, TypeCode::INTEGER) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_integer()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_integer()
.unwrap_unchecked()
};
ConcreteValue::Integer(left + right)
}
(TypeCode::FLOAT, TypeCode::FLOAT) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_float()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_float()
.unwrap_unchecked()
};
ConcreteValue::Float(left + right)
}
_ => panic!("VM Error: Cannot add values"),
};
let register = Register::Value(Value::Concrete(sum));
data.set_register(destination, register);
@ -266,12 +301,42 @@ pub fn subtract(instruction: Instruction, data: &mut ThreadData) -> bool {
let Subtract {
destination,
left,
left_type,
right,
right_type,
} = instruction.into();
let left = data.get_argument_unchecked(left);
let right = data.get_argument_unchecked(right);
let difference = left.subtract(right);
let register = Register::Value(difference);
let difference = match (left_type, right_type) {
(TypeCode::INTEGER, TypeCode::INTEGER) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_integer()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_integer()
.unwrap_unchecked()
};
ConcreteValue::Integer(left - right)
}
(TypeCode::FLOAT, TypeCode::FLOAT) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_float()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_float()
.unwrap_unchecked()
};
ConcreteValue::Float(left - right)
}
_ => panic!("VM Error: Cannot subtract values"),
};
let register = Register::Value(Value::Concrete(difference));
data.set_register(destination, register);
@ -284,20 +349,42 @@ pub fn multiply(instruction: Instruction, data: &mut ThreadData) -> bool {
let Multiply {
destination,
left,
left_type,
right,
right_type,
} = instruction.into();
let left = data.get_argument_unchecked(left);
let right = data.get_argument_unchecked(right);
let product = 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 multiply values"),
},
_ => panic!("Value Error: Cannot multiply values"),
let product = match (left_type, right_type) {
(TypeCode::INTEGER, TypeCode::INTEGER) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_integer()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_integer()
.unwrap_unchecked()
};
ConcreteValue::Integer(left * right)
}
(TypeCode::FLOAT, TypeCode::FLOAT) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_float()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_float()
.unwrap_unchecked()
};
ConcreteValue::Float(left * right)
}
_ => panic!("VM Error: Cannot multiply values"),
};
let register = Register::Value(product);
let register = Register::Value(Value::Concrete(product));
data.set_register(destination, register);
@ -310,20 +397,42 @@ pub fn divide(instruction: Instruction, data: &mut ThreadData) -> bool {
let Divide {
destination,
left,
left_type,
right,
right_type,
} = instruction.into();
let left = data.get_argument_unchecked(left);
let right = data.get_argument_unchecked(right);
let quotient = 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 divide values"),
},
_ => panic!("Value Error: Cannot divide values"),
let quotient = match (left_type, right_type) {
(TypeCode::INTEGER, TypeCode::INTEGER) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_integer()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_integer()
.unwrap_unchecked()
};
ConcreteValue::Integer(left / right)
}
(TypeCode::FLOAT, TypeCode::FLOAT) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_float()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_float()
.unwrap_unchecked()
};
ConcreteValue::Float(left / right)
}
_ => panic!("VM Error: Cannot divide values"),
};
let register = Register::Value(quotient);
let register = Register::Value(Value::Concrete(quotient));
data.set_register(destination, register);
@ -336,20 +445,28 @@ pub fn modulo(instruction: Instruction, data: &mut ThreadData) -> bool {
let Modulo {
destination,
left,
left_type,
right,
right_type,
} = instruction.into();
let left = data.get_argument_unchecked(left);
let right = data.get_argument_unchecked(right);
let remainder = 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 modulo values"),
},
_ => panic!("Value Error: Cannot modulo values"),
let remainder = match (left_type, right_type) {
(TypeCode::INTEGER, TypeCode::INTEGER) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_integer()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_integer()
.unwrap_unchecked()
};
ConcreteValue::Integer(left % right)
}
_ => panic!("VM Error: Cannot modulo values"),
};
let register = Register::Value(remainder);
let register = Register::Value(Value::Concrete(remainder));
data.set_register(destination, register);
@ -397,8 +514,8 @@ pub fn test_set(instruction: Instruction, data: &mut ThreadData) -> bool {
if boolean == test_value {
} else {
let pointer = match argument {
Argument::Constant(constant_index) => Pointer::Constant(constant_index),
Argument::Register(register_index) => Pointer::Register(register_index),
Operand::Constant(constant_index) => Pointer::Constant(constant_index),
Operand::Register(register_index) => Pointer::Register(register_index),
};
let register = Register::Pointer(pointer);
@ -411,12 +528,74 @@ pub fn test_set(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn equal(instruction: Instruction, data: &mut ThreadData) -> bool {
let Equal { value, left, right } = instruction.into();
let left = data.get_argument_unchecked(left);
let right = data.get_argument_unchecked(right);
let is_equal = left.equals(right);
let Equal {
comparator,
left,
left_type,
right,
right_type,
} = instruction.into();
let is_equal = match (left_type, right_type) {
(TypeCode::INTEGER, TypeCode::INTEGER) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_integer()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_integer()
.unwrap_unchecked()
};
if is_equal == value {
left == right
}
(TypeCode::FLOAT, TypeCode::FLOAT) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_float()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_float()
.unwrap_unchecked()
};
left == right
}
(TypeCode::BOOLEAN, TypeCode::BOOLEAN) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_boolean()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_boolean()
.unwrap_unchecked()
};
left == right
}
(TypeCode::STRING, TypeCode::STRING) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_string()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_string()
.unwrap_unchecked()
};
left == right
}
_ => panic!("VM Error: Cannot compare values"),
};
if is_equal == comparator {
let current_call = data.call_stack.last_mut_unchecked();
current_call.ip += 1;
@ -428,12 +607,46 @@ pub fn equal(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn less(instruction: Instruction, data: &mut ThreadData) -> bool {
let Less { value, left, right } = instruction.into();
let left = data.get_argument_unchecked(left);
let right = data.get_argument_unchecked(right);
let is_less = left < right;
let Less {
comparator,
left,
left_type,
right,
right_type,
} = instruction.into();
let is_less = match (left_type, right_type) {
(TypeCode::INTEGER, TypeCode::INTEGER) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_integer()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_integer()
.unwrap_unchecked()
};
if is_less == value {
left < right
}
(TypeCode::FLOAT, TypeCode::FLOAT) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_float()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_float()
.unwrap_unchecked()
};
left < right
}
_ => panic!("VM Error: Cannot compare values"),
};
if is_less == comparator {
let current_call = data.call_stack.last_mut_unchecked();
current_call.ip += 1;
@ -445,12 +658,46 @@ pub fn less(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn less_equal(instruction: Instruction, data: &mut ThreadData) -> bool {
let LessEqual { value, left, right } = instruction.into();
let left = data.get_argument_unchecked(left);
let right = data.get_argument_unchecked(right);
let is_less_or_equal = left <= right;
let LessEqual {
comparator,
left,
left_type,
right,
right_type,
} = instruction.into();
let is_less_or_equal = match (left_type, right_type) {
(TypeCode::INTEGER, TypeCode::INTEGER) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_integer()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_integer()
.unwrap_unchecked()
};
if is_less_or_equal == value {
left <= right
}
(TypeCode::FLOAT, TypeCode::FLOAT) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_float()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_float()
.unwrap_unchecked()
};
left <= right
}
_ => panic!("VM Error: Cannot compare values"),
};
if is_less_or_equal == comparator {
let current_call = data.call_stack.last_mut_unchecked();
current_call.ip += 1;
@ -465,6 +712,7 @@ pub fn negate(instruction: Instruction, data: &mut ThreadData) -> bool {
let Negate {
destination,
argument,
argument_type,
} = instruction.into();
let argument = data.get_argument_unchecked(argument);
let negated = argument.negate();
@ -525,14 +773,14 @@ pub fn call(instruction: Instruction, data: &mut ThreadData) -> bool {
let current_call = data.call_stack.last_unchecked();
let first_argument_register = return_register - argument_count;
let prototype = if is_recursive {
current_call.chunk
current_call.chunk.clone()
} else {
let function = data
.open_register_unchecked(function_register)
.as_function()
.unwrap();
&current_call.chunk.prototypes[function.prototype_index as usize]
current_call.chunk.prototypes[function.prototype_index as usize].clone()
};
let mut next_call = FunctionCall::new(prototype, return_register);
let mut argument_index = 0;
@ -565,7 +813,7 @@ pub fn call_native(instruction: Instruction, data: &mut ThreadData) -> bool {
let first_argument_index = destination - argument_count;
let argument_range = first_argument_index..destination;
function.call(data, Some(destination), argument_range)
function.call(data, destination, argument_range)
}
pub fn r#return(instruction: Instruction, data: &mut ThreadData) -> bool {

View File

@ -124,7 +124,7 @@ impl<T: Debug> Debug for Stack<T> {
}
}
impl Display for Stack<FunctionCall<'_>> {
impl Display for Stack<FunctionCall> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
writeln!(f, "----- DUST CALL STACK -----")?;

View File

@ -1,17 +1,17 @@
use std::mem::replace;
use std::{mem::replace, sync::Arc, thread::JoinHandle};
use tracing::{info, trace};
use crate::{vm::FunctionCall, Argument, Chunk, DustString, Span, Value};
use crate::{Chunk, DustString, Operand, Span, Value, vm::FunctionCall};
use super::{Pointer, Register, RunAction, Stack};
pub struct Thread {
chunk: Chunk,
chunk: Arc<Chunk>,
}
impl Thread {
pub fn new(chunk: Chunk) -> Self {
pub fn new(chunk: Arc<Chunk>) -> Self {
Thread { chunk }
}
@ -25,7 +25,8 @@ impl Thread {
);
let mut call_stack = Stack::with_capacity(self.chunk.prototypes.len() + 1);
let main_call = FunctionCall::new(&self.chunk, 0);
let mut main_call = FunctionCall::new(self.chunk.clone(), 0);
main_call.ip = 1; // The first action is already known
call_stack.push(main_call);
@ -34,6 +35,7 @@ impl Thread {
call_stack,
next_action: first_action,
return_value_index: None,
spawned_threads: Vec::new(),
};
loop {
@ -45,7 +47,7 @@ impl Thread {
);
if should_end {
let return_value = if let Some(register_index) = thread_data.return_value_index {
let value_option = if let Some(register_index) = thread_data.return_value_index {
let value =
thread_data.empty_register_or_clone_constant_unchecked(register_index);
@ -54,20 +56,28 @@ impl Thread {
None
};
return return_value;
thread_data
.spawned_threads
.into_iter()
.for_each(|join_handle| {
let _ = join_handle.join();
});
return value_option;
}
}
}
}
#[derive(Debug)]
pub struct ThreadData<'a> {
pub call_stack: Stack<FunctionCall<'a>>,
pub struct ThreadData {
pub call_stack: Stack<FunctionCall>,
pub next_action: RunAction,
pub return_value_index: Option<u8>,
pub return_value_index: Option<u16>,
pub spawned_threads: Vec<JoinHandle<()>>,
}
impl ThreadData<'_> {
impl ThreadData {
pub fn current_position(&self) -> Span {
let current_call = self.call_stack.last_unchecked();
@ -75,7 +85,7 @@ impl ThreadData<'_> {
}
pub(crate) fn follow_pointer_unchecked(&self, pointer: Pointer) -> &Value {
trace!("Follow pointer {pointer}");
trace!("Follow {pointer}");
match pointer {
Pointer::Register(register_index) => self.open_register_unchecked(register_index),
@ -96,8 +106,8 @@ impl ThreadData<'_> {
}
}
pub fn get_register_unchecked(&self, register_index: u8) -> &Register {
trace!("Get register R{register_index}");
pub fn get_register_unchecked(&self, register_index: u16) -> &Register {
trace!("Get R{register_index}");
let register_index = register_index as usize;
@ -113,13 +123,13 @@ impl ThreadData<'_> {
}
}
pub fn set_register(&mut self, to_register: u8, register: Register) {
pub fn set_register(&mut self, to_register: u16, register: Register) {
let to_register = to_register as usize;
self.call_stack.last_mut_unchecked().registers[to_register] = register;
}
pub fn open_register_unchecked(&self, register_index: u8) -> &Value {
pub fn open_register_unchecked(&self, register_index: u16) -> &Value {
let register_index = register_index as usize;
let register = if cfg!(debug_assertions) {
@ -133,42 +143,30 @@ impl ThreadData<'_> {
}
};
trace!("Open R{register_index} to {register}");
match register {
Register::Value(value) => {
trace!("Register R{register_index} opened to value {value}");
value
}
Register::Pointer(pointer) => {
trace!("Open register R{register_index} opened to pointer {pointer}");
self.follow_pointer_unchecked(*pointer)
}
Register::Value(value) => value,
Register::Pointer(pointer) => self.follow_pointer_unchecked(*pointer),
Register::Empty => panic!("VM Error: Register {register_index} is empty"),
}
}
pub fn open_register_allow_empty_unchecked(&self, register_index: u8) -> Option<&Value> {
trace!("Open register R{register_index}");
pub fn open_register_allow_empty_unchecked(&self, register_index: u16) -> Option<&Value> {
trace!("Open R{register_index}");
let register = self.get_register_unchecked(register_index);
trace!("Open R{register_index} to {register}");
match register {
Register::Value(value) => {
trace!("Register R{register_index} openned to value {value}");
Some(value)
}
Register::Pointer(pointer) => {
trace!("Open register R{register_index} openned to pointer {pointer}");
Some(self.follow_pointer_unchecked(*pointer))
}
Register::Value(value) => Some(value),
Register::Pointer(pointer) => Some(self.follow_pointer_unchecked(*pointer)),
Register::Empty => None,
}
}
pub fn empty_register_or_clone_constant_unchecked(&mut self, register_index: u8) -> Value {
pub fn empty_register_or_clone_constant_unchecked(&mut self, register_index: u16) -> Value {
let register_index = register_index as usize;
let old_register = replace(
&mut self.call_stack.last_mut_unchecked().registers[register_index],
@ -205,7 +203,7 @@ impl ThreadData<'_> {
}
}
pub fn clone_register_value_or_constant_unchecked(&self, register_index: u8) -> Value {
pub fn clone_register_value_or_constant_unchecked(&self, register_index: u16) -> Value {
let register = self.get_register_unchecked(register_index);
match register {
@ -235,14 +233,14 @@ impl ThreadData<'_> {
}
/// DRY helper to get a value from an Argument
pub fn get_argument_unchecked(&self, argument: Argument) -> &Value {
pub fn get_argument_unchecked(&self, argument: Operand) -> &Value {
match argument {
Argument::Constant(constant_index) => self.get_constant_unchecked(constant_index),
Argument::Register(register_index) => self.open_register_unchecked(register_index),
Operand::Constant(constant_index) => self.get_constant_unchecked(constant_index),
Operand::Register(register_index) => self.open_register_unchecked(register_index),
}
}
pub fn get_constant_unchecked(&self, constant_index: u8) -> &Value {
pub fn get_constant_unchecked(&self, constant_index: u16) -> &Value {
let constant_index = constant_index as usize;
if cfg!(debug_assertions) {
@ -258,9 +256,9 @@ impl ThreadData<'_> {
}
}
pub fn get_local_register(&self, local_index: u8) -> u8 {
pub fn get_local_register(&self, local_index: u16) -> u16 {
let local_index = local_index as usize;
let chunk = self.call_stack.last_unchecked().chunk;
let chunk = &self.call_stack.last_unchecked().chunk;
assert!(
local_index < chunk.locals.len(),

View File

@ -61,11 +61,11 @@ fn parentheses_precedence() {
},
vec![
(
Instruction::add(0, Argument::Constant(0), Argument::Constant(1)),
Instruction::add(0, Operand::Constant(0), Operand::Constant(1)),
Span(3, 4)
),
(
Instruction::multiply(1, Argument::Register(0), Argument::Constant(2)),
Instruction::multiply(1, Operand::Register(0), Operand::Constant(2)),
Span(8, 9)
),
(Instruction::r#return(true), Span(11, 11)),
@ -97,19 +97,19 @@ fn math_operator_precedence() {
},
vec![
(
Instruction::add(0, Argument::Constant(0), Argument::Constant(1)),
Instruction::add(0, Operand::Constant(0), Operand::Constant(1)),
Span(2, 3)
),
(
Instruction::multiply(1, Argument::Constant(2), Argument::Constant(3)),
Instruction::multiply(1, Operand::Constant(2), Operand::Constant(3)),
Span(10, 11)
),
(
Instruction::divide(2, Argument::Register(1), Argument::Constant(4)),
Instruction::divide(2, Operand::Register(1), Operand::Constant(4)),
Span(14, 15)
),
(
Instruction::subtract(3, Argument::Register(0), Argument::Register(2)),
Instruction::subtract(3, Operand::Register(0), Operand::Register(2)),
Span(6, 7)
),
(Instruction::r#return(true), Span(17, 17)),

View File

@ -15,7 +15,7 @@ fn equal() {
},
vec![
(
Instruction::equal(0, true, Argument::Constant(0), Argument::Constant(1)),
Instruction::equal(0, true, Operand::Constant(0), Operand::Constant(1)),
Span(2, 4)
),
(Instruction::r#return(true), Span(6, 6)),
@ -43,7 +43,7 @@ fn greater() {
},
vec![
(
Instruction::less_equal(0, false, Argument::Constant(0), Argument::Constant(1)),
Instruction::less_equal(0, false, Operand::Constant(0), Operand::Constant(1)),
Span(2, 3)
),
(Instruction::r#return(true), Span(5, 5)),
@ -71,7 +71,7 @@ fn greater_than_or_equal() {
},
vec![
(
Instruction::less(0, false, Argument::Constant(0), Argument::Constant(1)),
Instruction::less(0, false, Operand::Constant(0), Operand::Constant(1)),
Span(2, 4)
),
(Instruction::r#return(true), Span(6, 6)),
@ -99,7 +99,7 @@ fn less_than() {
},
vec![
(
Instruction::less(0, true, Argument::Constant(0), Argument::Constant(1)),
Instruction::less(0, true, Operand::Constant(0), Operand::Constant(1)),
Span(2, 3)
),
(Instruction::r#return(true), Span(5, 5)),
@ -127,7 +127,7 @@ fn less_than_or_equal() {
},
vec![
(
Instruction::less_equal(0, true, Argument::Constant(0), Argument::Constant(1)),
Instruction::less_equal(0, true, Operand::Constant(0), Operand::Constant(1)),
Span(2, 4)
),
(Instruction::r#return(true), Span(6, 6)),
@ -155,7 +155,7 @@ fn not_equal() {
},
vec![
(
Instruction::equal(0, false, Argument::Constant(0), Argument::Constant(1)),
Instruction::equal(0, false, Operand::Constant(0), Operand::Constant(1)),
Span(2, 4)
),
(Instruction::r#return(true), Span(6, 6)),

View File

@ -20,7 +20,7 @@ fn function() {
},
vec![
(
Instruction::add(2, Argument::Register(0), Argument::Register(1)),
Instruction::add(2, Operand::Register(0), Operand::Register(1)),
Span(30, 31)
),
(Instruction::r#return(true), Span(34, 35)),
@ -51,7 +51,7 @@ fn function_call() {
(Instruction::load_constant(0, 0, false), Span(0, 35)),
(Instruction::load_constant(1, 1, false), Span(36, 37)),
(Instruction::load_constant(2, 2, false), Span(39, 40)),
(Instruction::call(3, Argument::Constant(0), 2), Span(35, 41)),
(Instruction::call(3, Operand::Constant(0), 2), Span(35, 41)),
(Instruction::r#return(true), Span(41, 41)),
],
vec![
@ -64,7 +64,7 @@ fn function_call() {
},
vec![
(
Instruction::add(2, Argument::Register(0), Argument::Register(1)),
Instruction::add(2, Operand::Register(0), Operand::Register(1)),
Span(30, 31)
),
(Instruction::r#return(true), Span(34, 35)),
@ -112,7 +112,7 @@ fn function_declaration() {
},
vec![
(
Instruction::add(2, Argument::Register(0), Argument::Register(1)),
Instruction::add(2, Operand::Register(0), Operand::Register(1)),
Span(35, 36)
),
(Instruction::r#return(true), Span(39, 40)),

View File

@ -80,15 +80,15 @@ fn list_with_complex_expression() {
vec![
(Instruction::load_constant(0, 0, false), Span(1, 2)),
(
Instruction::add(1, Argument::Constant(1), Argument::Constant(2)),
Instruction::add(1, Operand::Constant(1), Operand::Constant(2)),
Span(6, 7)
),
(
Instruction::multiply(2, Argument::Constant(3), Argument::Constant(4)),
Instruction::multiply(2, Operand::Constant(3), Operand::Constant(4)),
Span(14, 15)
),
(
Instruction::subtract(3, Argument::Register(1), Argument::Register(2)),
Instruction::subtract(3, Operand::Register(1), Operand::Register(2)),
Span(10, 11)
),
(Instruction::close(1, 3), Span(17, 18)),
@ -131,7 +131,7 @@ fn list_with_simple_expression() {
vec![
(Instruction::load_constant(0, 0, false), Span(1, 2)),
(
Instruction::add(1, Argument::Constant(1), Argument::Constant(2)),
Instruction::add(1, Operand::Constant(1), Operand::Constant(2)),
Span(6, 7)
),
(Instruction::load_constant(2, 3, false), Span(11, 12)),

View File

@ -16,12 +16,12 @@ fn r#while() {
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(
Instruction::less(0, true, Argument::Register(0), Argument::Constant(2)),
Instruction::less(0, true, Operand::Register(0), Operand::Constant(2)),
Span(23, 24)
),
(Instruction::jump(2, true), Span(41, 42)),
(
Instruction::add(0, Argument::Register(0), Argument::Constant(3)),
Instruction::add(0, Operand::Register(0), Operand::Constant(3)),
Span(35, 36)
),
(Instruction::jump(3, false), Span(41, 42)),

View File

@ -15,7 +15,7 @@ fn divide_bytes() {
},
vec![
(
Instruction::divide(0, Argument::Constant(0), Argument::Constant(1)),
Instruction::divide(0, Operand::Constant(0), Operand::Constant(1)),
Span(5, 6)
),
(Instruction::r#return(true), Span(11, 11))
@ -43,7 +43,7 @@ fn divide_floats() {
},
vec![
(
Instruction::divide(0, Argument::Constant(0), Argument::Constant(0)),
Instruction::divide(0, Operand::Constant(0), Operand::Constant(0)),
Span(4, 5)
),
(Instruction::r#return(true), Span(9, 9))
@ -71,7 +71,7 @@ fn divide_integers() {
},
vec![
(
Instruction::divide(0, Argument::Constant(0), Argument::Constant(0)),
Instruction::divide(0, Operand::Constant(0), Operand::Constant(0)),
Span(2, 3)
),
(Instruction::r#return(true), Span(5, 5))

View File

@ -16,7 +16,7 @@ fn divide_assign() {
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(
Instruction::divide(0, Argument::Register(0), Argument::Constant(0)),
Instruction::divide(0, Operand::Register(0), Operand::Constant(0)),
Span(17, 19)
),
(Instruction::get_local(1, 0), Span(23, 24)),

View File

@ -15,7 +15,7 @@ fn modulo_floats() {
},
vec![
(
Instruction::modulo(0, Argument::Constant(0), Argument::Constant(0)),
Instruction::modulo(0, Operand::Constant(0), Operand::Constant(0)),
Span(4, 5)
),
(Instruction::r#return(true), Span(9, 9))
@ -43,7 +43,7 @@ fn modulo_integers() {
},
vec![
(
Instruction::modulo(0, Argument::Constant(0), Argument::Constant(0)),
Instruction::modulo(0, Operand::Constant(0), Operand::Constant(0)),
Span(2, 3)
),
(Instruction::r#return(true), Span(5, 5))

View File

@ -15,7 +15,7 @@ fn multiply_floats() {
},
vec![
(
Instruction::multiply(0, Argument::Constant(0), Argument::Constant(0)),
Instruction::multiply(0, Operand::Constant(0), Operand::Constant(0)),
Span(4, 5)
),
(Instruction::r#return(true), Span(9, 9)),
@ -43,7 +43,7 @@ fn multiply_integers() {
},
vec![
(
Instruction::multiply(0, Argument::Constant(0), Argument::Constant(1)),
Instruction::multiply(0, Operand::Constant(0), Operand::Constant(1)),
Span(2, 3)
),
(Instruction::r#return(true), Span(5, 5)),

View File

@ -16,7 +16,7 @@ fn multiply_assign() {
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(
Instruction::multiply(0, Argument::Register(0), Argument::Constant(2)),
Instruction::multiply(0, Operand::Register(0), Operand::Constant(2)),
Span(17, 19)
),
(Instruction::get_local(1, 0), Span(22, 23)),

View File

@ -15,7 +15,7 @@ fn subtract_floats() {
},
vec![
(
Instruction::subtract(0, Argument::Constant(0), Argument::Constant(0)),
Instruction::subtract(0, Operand::Constant(0), Operand::Constant(0)),
Span(4, 5)
),
(Instruction::r#return(true), Span(9, 9)),
@ -43,7 +43,7 @@ fn subtract_floats_saturate() {
},
vec![
(
Instruction::subtract(0, Argument::Constant(0), Argument::Constant(1)),
Instruction::subtract(0, Operand::Constant(0), Operand::Constant(1)),
Span(25, 26)
),
(Instruction::r#return(true), Span(36, 36)),
@ -74,7 +74,7 @@ fn subtract_integers() {
},
vec![
(
Instruction::subtract(0, Argument::Constant(0), Argument::Constant(1)),
Instruction::subtract(0, Operand::Constant(0), Operand::Constant(1)),
Span(2, 3)
),
(Instruction::r#return(true), Span(5, 5)),
@ -102,7 +102,7 @@ fn subtract_integers_saturate() {
},
vec![
(
Instruction::subtract(0, Argument::Constant(0), Argument::Constant(1)),
Instruction::subtract(0, Operand::Constant(0), Operand::Constant(1)),
Span(21, 22)
),
(Instruction::r#return(true), Span(24, 24)),

View File

@ -16,7 +16,7 @@ fn subtract_assign() {
vec![
(Instruction::load_constant(0, 0, false), Span(12, 14)),
(
Instruction::subtract(0, Argument::Register(0), Argument::Constant(2)),
Instruction::subtract(0, Operand::Register(0), Operand::Constant(2)),
Span(18, 20)
),
(Instruction::get_local(1, 0), Span(24, 25)),

View File

@ -14,7 +14,7 @@ fn negate() {
return_type: Type::Integer,
},
vec![
(Instruction::negate(0, Argument::Constant(0)), Span(0, 1)),
(Instruction::negate(0, Operand::Constant(0)), Span(0, 1)),
(Instruction::r#return(true), Span(5, 5)),
],
vec![ConcreteValue::Integer(42)],
@ -40,7 +40,7 @@ fn not() {
},
vec![
(Instruction::load_boolean(0, true, false), Span(1, 5)),
(Instruction::not(1, Argument::Register(0)), Span(0, 1)),
(Instruction::not(1, Operand::Register(0)), Span(0, 1)),
(Instruction::r#return(true), Span(5, 5)),
],
vec![],