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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" 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]] [[package]]
name = "crossbeam-deque" name = "crossbeam-deque"
version = "0.8.5" version = "0.8.5"
@ -335,6 +344,7 @@ dependencies = [
"annotate-snippets", "annotate-snippets",
"colored", "colored",
"criterion", "criterion",
"crossbeam-channel",
"getrandom", "getrandom",
"rand", "rand",
"serde", "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. **Fast**, **safe** and **easy-to-use** general-purpose programming language.
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.**
```rust ```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...") write_line("Enter your name...")
let name = read_line() let name = read_line()
@ -38,7 +23,23 @@ fn fib (n: int) -> int {
write_line(fib(25)) 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 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 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 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 typed language, users should feel confident in the type-consistency of their code and not want
to go back to a dynamically typed language. 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 - **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. 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 Dust's design is to use a separate thread for garbage collection, allowing other threads to
continue executing code while the garbage collector looks for unused memory. continue executing instructions while the garbage collector looks for unused memory.
- **Easy** - **Easy**
- **Simple Syntax** Dust should be easier to learn than most programming languages. Its syntax - **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 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 - **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. 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 I'm Jeff 🦀 and I started this project as simple expession evaluator. Initially, the project used an
most programmers such as creating variables, using binary operators and printing to the console. external parser and a tree-walking interpreter. After several books, a few papers, countless
Eventually there should be a complete reference for the syntax. 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
### 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
languages. languages.
> I call it my billion-dollar mistake. It was the invention of the null reference in 1965. ## Usage
> - Tony Hoare
Dust *does* have a `none` type, which should not be confused for being `null`-like. Like the `()` or **Dust is under active development and is not yet ready for general use.**
"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`.
## Previous Implementations ## Installation
Dust has gone through several iterations, each with its own design choices. It was originally Eventually, Dust should be available via package managers and as an embeddable library. For now,
implemented with a syntax tree generated by an external parser, then a parser generator, and finally the only way to use Dust is to clone the repository and build it from source.
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.
## Inspiration ## Inspiration
[Crafting Interpreters] by Bob Nystrom was a great resource for writing the compiler, especially the *Crafting Interpreters*[^0] by Bob Nystrom was a great resource for writing the compiler, especially
Pratt parser. The book is a great introduction to writing interpreters. Had it been discovered 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 sooner, some early implementations of Dust would have been both simpler in design and more ambitious
in scope. in scope.
[The Implementation of Lua 5.0] by Roberto Ierusalimschy, Luiz Henrique de Figueiredo, and Waldemar *The Implementation of Lua 5.0*[^1] by Roberto Ierusalimschy, Luiz Henrique de Figueiredo, and
Celes was a great resource for understanding register-based virtual machines and their instructions. Waldemar Celes was a great resource for understanding register-based virtual machines and their
This paper was recommended by Bob Nystrom in [Crafting Interpreters]. 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 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 essential in the design of Dust's instructions. Dust uses compile-time optimizations that are based
on Lua optimizations covered in this paper. 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 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. 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 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 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 ## License
Dust is licensed under the GNU General Public License v3.0. See the `LICENSE` file for details. Dust is licensed under the GNU General Public License v3.0. See the `LICENSE` file for details.
## References [^0]: [Crafting Interpreters](https://craftinginterpreters.com/)
[^1]: [The Implementation of Lua 5.0](https://www.lua.org/doc/jucs05.pdf)
[^1]: [Crafting Interpreters](https://craftinginterpreters.com/) [^2]: [A No-Frills Introduction to Lua 5.1 VM Instructions](https://www.mcours.net/cours/pdf/hasclic3/hasssclic818.pdf)
[^2]: [The Implementation of Lua 5.0](https://www.lua.org/doc/jucs05.pdf) [^3]: [A Performance Survey on Stack-based and Register-based Virtual Machines](https://arxiv.org/abs/1611.00467)
[^3]: [A No-Frills Introduction to Lua 5.1 VM Instructions](https://www.mcours.net/cours/pdf/hasclic3/hasssclic818.pdf) [^4]: [List of C-family programming languages](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages)
[^4]: [A Performance Survey on Stack-based and Register-based Virtual Machines](https://arxiv.org/abs/1611.00467) [^5]: [ripgrep is faster than {grep, ag, git grep, ucg, pt, sift}](https://blog.burntsushi.net/ripgrep/#mechanics)
[^5]: [List of C-family programming languages](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages) [^6]: [Writing a Compiler in Go](https://compilerbook.com/)
[^6]: [ripgrep is faster than {grep, ag, git grep, ucg, pt, sift}](https://blog.burntsushi.net/ripgrep/#mechanics) [^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" annotate-snippets = "0.11.4"
colored = "2.1.0" colored = "2.1.0"
rand = "0.8.5" rand = "0.8.5"
serde = { version = "1.0.203", features = ["derive"] } serde = { version = "1.0.203", features = ["derive", "rc"] }
serde_json = "1.0.117" serde_json = "1.0.117"
getrandom = { version = "0.2", features = [ getrandom = { version = "0.2", features = [
"js", "js",
@ -21,6 +21,7 @@ smartstring = { version = "1.0.1", features = [
"serde", "serde",
], default-features = false } ], default-features = false }
tracing = "0.1.41" tracing = "0.1.41"
crossbeam-channel = "0.5.14"
[dev-dependencies] [dev-dependencies]
criterion = { version = "0.3.4", features = ["html_reports"] } criterion = { version = "0.3.4", features = ["html_reports"] }
@ -33,6 +34,10 @@ harness = false
name = "fibonacci" name = "fibonacci"
harness = false harness = false
[[bench]]
name = "threads"
harness = false
[[test]] [[test]]
name = "logic_and" name = "logic_and"
path = "tests/logic/and.rs" 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}; use crate::{Chunk, Local};
const INSTRUCTION_COLUMNS: [(&str, usize); 4] = 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 INSTRUCTION_BORDERS: [&str; 3] = [
"╭─────┬────────────┬─────────────────┬────────────────────────", "╭─────┬────────────┬─────────────────┬─────────────────────────────────────────",
"├─────┼────────────┼─────────────────┼────────────────────────", "├─────┼────────────┼─────────────────┼─────────────────────────────────────────",
"╰─────┴────────────┴─────────────────┴────────────────────────", "╰─────┴────────────┴─────────────────┴─────────────────────────────────────────",
]; ];
const LOCAL_COLUMNS: [(&str, usize); 5] = [ const LOCAL_COLUMNS: [(&str, usize); 5] = [
@ -286,7 +286,7 @@ impl<'a, W: Write> Disassembler<'a, W> {
.unwrap_or("stripped".to_string()); .unwrap_or("stripped".to_string());
let operation = instruction.operation().to_string(); let operation = instruction.operation().to_string();
let info = instruction.disassembly_info(); 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)?; self.write_center_border(&row)?;
} }

View File

@ -1,15 +1,17 @@
//! Scoped variable.
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::Scope; use crate::Scope;
/// A scoped variable. /// Scoped variable.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Local { pub struct Local {
/// The index of the identifier in the constants table. /// Index of the identifier in the constants list.
pub identifier_index: u8, pub identifier_index: u16,
/// Stack index where the local's value is stored. /// Index of the register where the variable's value is stored.
pub register_index: u8, pub register_index: u16,
/// Whether the local is mutable. /// Whether the local is mutable.
pub is_mutable: bool, pub is_mutable: bool,
@ -20,7 +22,7 @@ pub struct Local {
impl Local { impl Local {
/// Creates a new Local instance. /// 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 { Self {
identifier_index, identifier_index,
register_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::fmt::{self, Debug, Display, Formatter, Write as FmtWrite};
use std::io::Write; use std::io::Write;
use std::sync::Arc;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -40,10 +41,10 @@ pub struct Chunk {
pub(crate) positions: Vec<Span>, pub(crate) positions: Vec<Span>,
pub(crate) constants: Vec<Value>, pub(crate) constants: Vec<Value>,
pub(crate) locals: Vec<Local>, pub(crate) locals: Vec<Local>,
pub(crate) prototypes: Vec<Chunk>, pub(crate) prototypes: Vec<Arc<Chunk>>,
pub(crate) register_count: usize, pub(crate) register_count: usize,
pub(crate) prototype_index: u8, pub(crate) prototype_index: u16,
} }
impl Chunk { impl Chunk {
@ -55,7 +56,7 @@ impl Chunk {
positions: impl Into<Vec<Span>>, positions: impl Into<Vec<Span>>,
constants: impl Into<Vec<Value>>, constants: impl Into<Vec<Value>>,
locals: impl Into<Vec<Local>>, locals: impl Into<Vec<Local>>,
prototypes: Vec<Chunk>, prototypes: impl IntoIterator<Item = Chunk>,
) -> Self { ) -> Self {
Self { Self {
name, name,
@ -64,7 +65,7 @@ impl Chunk {
positions: positions.into(), positions: positions.into(),
constants: constants.into(), constants: constants.into(),
locals: locals.into(), locals: locals.into(),
prototypes, prototypes: prototypes.into_iter().map(Arc::new).collect(),
register_count: 0, register_count: 0,
prototype_index: 0, prototype_index: 0,
} }

View File

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

View File

@ -28,14 +28,14 @@ use parse_rule::{ParseRule, Precedence};
use tracing::{Level, debug, info, span}; use tracing::{Level, debug, info, span};
use type_checks::{check_math_type, check_math_types}; 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 optimize::control_flow_register_consolidation;
use crate::{ use crate::{
Argument, Chunk, ConcreteValue, DustError, DustString, FunctionType, Instruction, Lexer, Local, Chunk, ConcreteValue, DustError, DustString, FunctionType, Instruction, Lexer, Local,
NativeFunction, Operation, Scope, Span, Token, TokenKind, Type, Value, NativeFunction, Operand, Operation, Scope, Span, Token, TokenKind, Type, Value,
instruction::{CallNative, Close, GetLocal, Jump, LoadList, Negate, Not, Return, SetLocal}, instruction::{CallNative, Close, GetLocal, Jump, LoadList, Return, SetLocal, TypeCode},
}; };
/// Compiles the input and returns a chunk. /// 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 /// Prototypes that have been compiled. These are assigned to the chunk when
/// [`Compiler::finish`] is called. /// [`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 /// Maximum stack size required by the chunk. This is assigned to the chunk when
/// [`Compiler::finish`] is called. /// [`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 /// 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. /// 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 /// Index of the current block. This is used to determine the scope of locals and is incremented
/// when a new block is entered. /// 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 /// 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. /// 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 /// Whether the chunk is the program's main chunk. This is used to prevent recursive calls to
/// the main chunk. /// the main chunk.
@ -227,7 +227,7 @@ impl<'src> Compiler<'src> {
matches!(self.current_token, Token::Eof) matches!(self.current_token, Token::Eof)
} }
fn next_register(&self) -> u8 { fn next_register(&self) -> u16 {
self.instructions self.instructions
.iter() .iter()
.rev() .rev()
@ -260,7 +260,7 @@ impl<'src> Compiler<'src> {
Ok(()) Ok(())
} }
fn get_local(&self, index: u8) -> Result<&(Local, Type), CompileError> { fn get_local(&self, index: u16) -> Result<&(Local, Type), CompileError> {
self.locals self.locals
.get(index as usize) .get(index as usize)
.ok_or(CompileError::UndeclaredVariable { .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 self.locals
.iter() .iter()
.enumerate() .enumerate()
@ -284,7 +284,7 @@ impl<'src> Compiler<'src> {
}; };
if identifier == identifier_text { if identifier == identifier_text {
Some(index as u8) Some(index as u16)
} else { } else {
None None
} }
@ -298,16 +298,16 @@ impl<'src> Compiler<'src> {
fn declare_local( fn declare_local(
&mut self, &mut self,
identifier: &str, identifier: &str,
register_index: u8, register_index: u16,
r#type: Type, r#type: Type,
is_mutable: bool, is_mutable: bool,
scope: Scope, scope: Scope,
) -> (u8, u8) { ) -> (u16, u16) {
info!("Declaring local {identifier}"); info!("Declaring local {identifier}");
let identifier = Value::Concrete(ConcreteValue::string(identifier)); let identifier = Value::Concrete(ConcreteValue::string(identifier));
let identifier_index = self.push_or_get_constant(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(( self.locals.push((
Local::new(identifier_index, register_index, is_mutable, scope), Local::new(identifier_index, register_index, is_mutable, scope),
@ -317,7 +317,7 @@ impl<'src> Compiler<'src> {
(local_index, identifier_index) (local_index, identifier_index)
} }
fn get_identifier(&self, local_index: u8) -> Option<String> { fn get_identifier(&self, local_index: u16) -> Option<String> {
self.locals self.locals
.get(local_index as usize) .get(local_index as usize)
.and_then(|(local, _)| { .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 if let Some(index) = self
.constants .constants
.iter() .iter()
.position(|constant| constant == &value) .position(|constant| constant == &value)
{ {
index as u8 index as u16
} else { } else {
let index = self.constants.len() as u8; let index = self.constants.len() as u16;
self.constants.push(value); self.constants.push(value);
@ -393,7 +393,7 @@ impl<'src> Compiler<'src> {
.unwrap_or(Type::None) .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 if let Some((_, r#type)) = self
.locals .locals
.iter() .iter()
@ -651,15 +651,26 @@ impl<'src> Compiler<'src> {
} }
let destination = self.next_register(); let destination = self.next_register();
let instruction = match operator.kind() { let type_code = match previous_type {
TokenKind::Bang => Instruction::from(Not { Type::Boolean => TypeCode::BOOLEAN,
destination, Type::Byte => TypeCode::BYTE,
argument, Type::Character => TypeCode::CHARACTER,
}), Type::Float => TypeCode::FLOAT,
TokenKind::Minus => Instruction::from(Negate { Type::Integer => TypeCode::INTEGER,
destination, Type::String => TypeCode::STRING,
argument, _ => 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 { return Err(CompileError::ExpectedTokenMultiple {
expected: &[TokenKind::Bang, TokenKind::Minus], expected: &[TokenKind::Bang, TokenKind::Minus],
@ -667,6 +678,12 @@ impl<'src> Compiler<'src> {
position: operator_position, 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); self.emit_instruction(instruction, previous_type, operator_position);
@ -677,14 +694,14 @@ impl<'src> Compiler<'src> {
fn handle_binary_argument( fn handle_binary_argument(
&mut self, &mut self,
instruction: &Instruction, instruction: &Instruction,
) -> Result<(Argument, bool), CompileError> { ) -> Result<(Operand, bool), CompileError> {
let (argument, push_back) = match instruction.operation() { 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 => { Operation::GET_LOCAL => {
let local_index = instruction.b_field(); let local_index = instruction.b_field();
let (local, _) = self.get_local(local_index)?; let (local, _) = self.get_local(local_index)?;
(Argument::Register(local.register_index), false) (Operand::Register(local.register_index), false)
} }
Operation::LOAD_BOOLEAN Operation::LOAD_BOOLEAN
| Operation::LOAD_LIST | Operation::LOAD_LIST
@ -699,12 +716,12 @@ impl<'src> Compiler<'src> {
| Operation::LESS_EQUAL | Operation::LESS_EQUAL
| Operation::NEGATE | Operation::NEGATE
| Operation::NOT | Operation::NOT
| Operation::CALL => (Argument::Register(instruction.a_field()), true), | Operation::CALL => (Operand::Register(instruction.a_field()), true),
Operation::CALL_NATIVE => { Operation::CALL_NATIVE => {
let function = NativeFunction::from(instruction.b_field()); let function = NativeFunction::from(instruction.b_field());
if function.returns_value() { if function.returns_value() {
(Argument::Register(instruction.a_field()), true) (Operand::Register(instruction.a_field()), true)
} else { } else {
return Err(CompileError::ExpectedExpression { return Err(CompileError::ExpectedExpression {
found: self.previous_token.to_owned(), found: self.previous_token.to_owned(),
@ -762,6 +779,16 @@ impl<'src> Compiler<'src> {
check_math_type(&left_type, operator, &left_position)?; 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 { if is_assignment && !left_is_mutable_local {
return Err(CompileError::ExpectedMutableVariable { return Err(CompileError::ExpectedMutableVariable {
found: self.previous_token.to_owned(), found: self.previous_token.to_owned(),
@ -784,6 +811,16 @@ impl<'src> Compiler<'src> {
&right_position, &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 { if push_back_right {
self.instructions self.instructions
.push((right_instruction, right_type, right_position)); .push((right_instruction, right_type, right_position));
@ -798,18 +835,28 @@ impl<'src> Compiler<'src> {
}; };
let destination = if is_assignment { let destination = if is_assignment {
match left { match left {
Argument::Register(register) => register, Operand::Register(register) => register,
Argument::Constant(_) => self.next_register(), Operand::Constant(_) => self.next_register(),
} }
} else { } else {
self.next_register() self.next_register()
}; };
let instruction = match operator { let instruction = match operator {
Token::Plus | Token::PlusEqual => Instruction::add(destination, left, right), Token::Plus | Token::PlusEqual => {
Token::Minus | Token::MinusEqual => Instruction::subtract(destination, left, right), Instruction::add(destination, left, left_type_code, right, right_type_code)
Token::Star | Token::StarEqual => Instruction::multiply(destination, left, right), }
Token::Slash | Token::SlashEqual => Instruction::divide(destination, left, right), Token::Minus | Token::MinusEqual => {
Token::Percent | Token::PercentEqual => Instruction::modulo(destination, left, right), 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 { return Err(CompileError::ExpectedTokenMultiple {
expected: &[ expected: &[
@ -861,6 +908,18 @@ impl<'src> Compiler<'src> {
let operator_position = self.current_position; let operator_position = self.current_position;
let rule = ParseRule::from(&operator); 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 { if push_back_left {
self.instructions self.instructions
.push((left_instruction, left_type, left_position)); .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)?; 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 { if push_back_right {
self.instructions self.instructions
.push((right_instruction, right_type, right_position)); .push((right_instruction, right_type, right_position));
@ -885,12 +957,22 @@ impl<'src> Compiler<'src> {
let destination = self.next_register(); let destination = self.next_register();
let comparison = match operator { let comparison = match operator {
Token::DoubleEqual => Instruction::equal(true, left, right), Token::DoubleEqual => {
Token::BangEqual => Instruction::equal(false, left, right), Instruction::equal(true, left, left_type_code, right, right_type_code)
Token::Less => Instruction::less(true, left, right), }
Token::LessEqual => Instruction::less_equal(true, left, right), Token::BangEqual => {
Token::Greater => Instruction::less_equal(false, left, right), Instruction::equal(false, left, left_type_code, right, right_type_code)
Token::GreaterEqual => Instruction::less(false, left, right), }
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 { return Err(CompileError::ExpectedTokenMultiple {
expected: &[ expected: &[
@ -982,7 +1064,7 @@ impl<'src> Compiler<'src> {
let old_jump = &mut instructions[1].0; let old_jump = &mut instructions[1].0;
let jump_index = instructions_length - group_index * 3 - 1; 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); *old_jump = Instruction::jump(short_circuit_distance, true);
} }
@ -1009,7 +1091,7 @@ impl<'src> Compiler<'src> {
return self.parse_call_native(native_function); return self.parse_call_native(native_function);
} else if self.function_name.as_deref() == Some(identifier) && !self.is_main { } else if self.function_name.as_deref() == Some(identifier) && !self.is_main {
let destination = self.next_register(); 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); self.emit_instruction(load_self, Type::SelfFunction, start_position);
@ -1147,10 +1229,7 @@ impl<'src> Compiler<'src> {
let destination = self.next_register(); let destination = self.next_register();
let end = self.previous_position.1; let end = self.previous_position.1;
let load_list = Instruction::from(LoadList { let load_list = Instruction::load_list(destination, start_register, false);
destination,
start_register,
});
self.emit_instruction(load_list, Type::List(Box::new(item_type)), Span(start, end)); 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 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(); let if_block_type = self.get_last_instruction_type();
if let Token::Else = self.current_token { 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_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(); let else_block_type = self.get_last_instruction_type();
if let Err(conflict) = if_block_type.check(&else_block_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(); let (loader, _, _) = self.instructions.last_mut().unwrap();
loader.set_c_field(true as u8); loader.set_c_field(true as u16);
} else { } else {
if_block_distance += 1; if_block_distance += 1;
let jump = Instruction::from(Jump { let jump = Instruction::from(Jump {
@ -1270,7 +1349,7 @@ impl<'src> Compiler<'src> {
fn parse_while(&mut self) -> Result<(), CompileError> { fn parse_while(&mut self) -> Result<(), CompileError> {
self.advance()?; self.advance()?;
let expression_start = self.instructions.len() as u8; let expression_start = self.instructions.len();
self.parse_expression()?; self.parse_expression()?;
@ -1297,8 +1376,8 @@ impl<'src> Compiler<'src> {
self.parse_block()?; self.parse_block()?;
let block_end = self.instructions.len() as u8; let block_end = self.instructions.len();
let jump_distance = block_end - block_start as u8 + 1; let jump_distance = (block_end - block_start + 1) as u16;
let jump = Instruction::from(Jump { let jump = Instruction::from(Jump {
offset: jump_distance, offset: jump_distance,
is_positive: true, is_positive: true,
@ -1307,7 +1386,7 @@ impl<'src> Compiler<'src> {
self.instructions self.instructions
.insert(block_start, (jump, Type::None, self.current_position)); .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 { let jump_back = Instruction::from(Jump {
offset: jump_back_distance, offset: jump_back_distance,
is_positive: false, is_positive: false,
@ -1433,7 +1512,7 @@ impl<'src> Compiler<'src> {
let offset = offset as usize; let offset = offset as usize;
if is_positive && offset + index == instruction_length - 1 { 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; let offset = offset as usize;
if is_positive && offset + index == instruction_length - 1 { 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)? { while !function_compiler.allow(Token::RightParenthesis)? {
let is_mutable = function_compiler.allow(Token::Mut)?; let is_mutable = function_compiler.allow(Token::Mut)?;
@ -1645,7 +1724,7 @@ impl<'src> Compiler<'src> {
let chunk = function_compiler.finish(); let chunk = function_compiler.finish();
let destination = self.next_register(); let destination = self.next_register();
self.prototypes.push(chunk); self.prototypes.push(Arc::new(chunk));
if let Some(identifier) = identifier { if let Some(identifier) = identifier {
self.declare_local( 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( self.emit_instruction(
load_function, 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. /// a `POINT` instruction to prevent the VM from encountering an empty register.
/// ///
/// The instructions must be in the following order: /// 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` /// - `JUMP`
/// - `LOAD_BOOLEAN` or `LOAD_CONSTANT` /// - `LOAD_BOOLEAN` or `LOAD_CONSTANT`
/// - `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!( if !matches!(
compiler.get_last_operations(), compiler.get_last_operations(),
Some([ Some([
Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL | Operation::TEST, Operation::TEST | Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL,
Operation::JUMP, Operation::JUMP,
Operation::LOAD_BOOLEAN | Operation::LOAD_CONSTANT, Operation::LOAD_BOOLEAN | Operation::LOAD_CONSTANT,
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 struct Add {
pub destination: u8, pub destination: u16,
pub left: Argument, pub left: Operand,
pub right: Argument, pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
} }
impl From<Instruction> for Add { impl From<Instruction> for Add {
fn from(instruction: Instruction) -> Self { fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field(); 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 { Add {
destination, destination,
left, left,
left_type,
right, right,
right_type,
} }
} }
} }
@ -22,10 +30,40 @@ impl From<Instruction> for Add {
impl From<Add> for Instruction { impl From<Add> for Instruction {
fn from(add: Add) -> Self { fn from(add: Add) -> Self {
let operation = Operation::ADD; let operation = Operation::ADD;
let a = add.destination; let a_field = add.destination;
let (b, b_is_constant) = add.left.as_index_and_constant_flag(); let (b_field, b_is_constant) = add.left.as_index_and_constant_flag();
let (c, c_is_constant) = add.right.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 crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct Call { pub struct Call {
pub destination: u8, pub destination: u16,
pub function_register: u8, pub function_register: u16,
pub argument_count: u8, pub argument_count: u16,
pub is_recursive: bool, pub is_recursive: bool,
} }
@ -25,11 +29,45 @@ impl From<Instruction> for Call {
impl From<Call> for Instruction { impl From<Call> for Instruction {
fn from(call: Call) -> Self { fn from(call: Call) -> Self {
let a = call.destination; let a_field = call.destination;
let b = call.function_register; let b_field = call.function_register;
let c = call.argument_count; let c_field = call.argument_count;
let d = call.is_recursive; 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 crate::{Instruction, NativeFunction, Operation};
use super::InstructionBuilder;
pub struct CallNative { pub struct CallNative {
pub destination: u8, pub destination: u16,
pub function: NativeFunction, pub function: NativeFunction,
pub argument_count: u8, pub argument_count: u16,
} }
impl From<Instruction> for CallNative { impl From<Instruction> for CallNative {
@ -22,10 +26,41 @@ impl From<Instruction> for CallNative {
impl From<CallNative> for Instruction { impl From<CallNative> for Instruction {
fn from(call_native: CallNative) -> Self { fn from(call_native: CallNative) -> Self {
let operation = Operation::CALL_NATIVE; let operation = Operation::CALL_NATIVE;
let a = call_native.destination; let a_field = call_native.destination;
let b = call_native.function as u8; let b_field = call_native.function as u16;
let c = call_native.argument_count; 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 crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct Close { pub struct Close {
pub from: u8, pub from: u16,
pub to: u8, pub to: u16,
} }
impl From<Instruction> for Close { impl From<Instruction> for Close {
@ -17,8 +21,23 @@ impl From<Instruction> for Close {
impl From<Close> for Instruction { impl From<Close> for Instruction {
fn from(close: Close) -> Self { fn from(close: Close) -> Self {
let operation = Operation::CLOSE; 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 struct Divide {
pub destination: u8, pub destination: u16,
pub left: Argument, pub left: Operand,
pub right: Argument, pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
} }
impl From<Instruction> for Divide { impl From<Instruction> for Divide {
fn from(instruction: Instruction) -> Self { fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field(); 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 { Divide {
destination, destination,
left, left,
left_type,
right, right,
right_type,
} }
} }
} }
@ -22,10 +30,40 @@ impl From<Instruction> for Divide {
impl From<Divide> for Instruction { impl From<Divide> for Instruction {
fn from(divide: Divide) -> Self { fn from(divide: Divide) -> Self {
let operation = Operation::DIVIDE; let operation = Operation::DIVIDE;
let a = divide.destination; let a_field = divide.destination;
let (b, b_is_constant) = divide.left.as_index_and_constant_flag(); let (b_field, b_is_constant) = divide.left.as_index_and_constant_flag();
let (c, c_is_constant) = divide.right.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 struct Equal {
pub value: bool, pub comparator: bool,
pub left: Argument, pub left: Operand,
pub right: Argument, pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
} }
impl From<Instruction> for Equal { impl From<Instruction> for Equal {
fn from(instruction: Instruction) -> Self { fn from(instruction: Instruction) -> Self {
let value = instruction.d_field(); let comparator = instruction.d_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();
Equal { value, left, right } Equal {
comparator,
left,
left_type,
right,
right_type,
}
} }
} }
impl From<Equal> for Instruction { impl From<Equal> for Instruction {
fn from(equal: Equal) -> Self { fn from(equal_bool: Equal) -> Self {
let operation = Operation::EQUAL; let operation = Operation::EQUAL;
let (b, b_is_constant) = equal.left.as_index_and_constant_flag(); let (b_field, b_is_constant) = equal_bool.left.as_index_and_constant_flag();
let (c, c_is_constant) = equal.right.as_index_and_constant_flag(); let (c_field, c_is_constant) = equal_bool.right.as_index_and_constant_flag();
let d = equal.value; 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 crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct GetLocal { pub struct GetLocal {
pub destination: u8, pub destination: u16,
pub local_index: u8, pub local_index: u16,
} }
impl From<Instruction> for GetLocal { impl From<Instruction> for GetLocal {
@ -20,9 +24,26 @@ impl From<Instruction> for GetLocal {
impl From<GetLocal> for Instruction { impl From<GetLocal> for Instruction {
fn from(get_local: GetLocal) -> Self { fn from(get_local: GetLocal) -> Self {
let operation = Operation::GET_LOCAL; let operation = Operation::GET_LOCAL;
let a = get_local.destination; let a_field = get_local.destination;
let b = get_local.local_index; 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 crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct Jump { pub struct Jump {
pub offset: u8, pub offset: u16,
pub is_positive: bool, pub is_positive: bool,
} }
@ -17,9 +21,27 @@ impl From<Instruction> for Jump {
impl From<Jump> for Instruction { impl From<Jump> for Instruction {
fn from(jump: Jump) -> Self { fn from(jump: Jump) -> Self {
let operation = Operation::JUMP; let operation = Operation::JUMP;
let b = jump.offset; let b_field = jump.offset;
let c = jump.is_positive as u8; 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 struct Less {
pub value: bool, pub comparator: bool,
pub left: Argument, pub left: Operand,
pub right: Argument, pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
} }
impl From<Instruction> for Less { impl From<Instruction> for Less {
fn from(instruction: Instruction) -> Self { fn from(instruction: Instruction) -> Self {
let value = instruction.d_field(); let comparator = instruction.d_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();
Less { value, left, right } Less {
comparator,
left,
left_type,
right,
right_type,
}
} }
} }
impl From<Less> for Instruction { impl From<Less> for Instruction {
fn from(less: Less) -> Self { fn from(less: Less) -> Self {
let operation = Operation::LESS; let operation = Operation::LESS;
let (b, b_is_constant) = less.left.as_index_and_constant_flag(); let (b_field, b_is_constant) = less.left.as_index_and_constant_flag();
let (c, c_is_constant) = less.right.as_index_and_constant_flag(); let (c_field, c_is_constant) = less.right.as_index_and_constant_flag();
let d = less.value; 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 struct LessEqual {
pub value: bool, pub comparator: bool,
pub left: Argument, pub left: Operand,
pub right: Argument, pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
} }
impl From<Instruction> for LessEqual { impl From<Instruction> for LessEqual {
fn from(instruction: Instruction) -> Self { fn from(instruction: Instruction) -> Self {
let value = instruction.d_field(); let comparator = instruction.d_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();
LessEqual { value, left, right } LessEqual {
comparator,
left,
left_type,
right,
right_type,
}
} }
} }
impl From<LessEqual> for Instruction { impl From<LessEqual> for Instruction {
fn from(less_equal: LessEqual) -> Self { fn from(less_equal_byte: LessEqual) -> Self {
let operation = Operation::LESS_EQUAL; let operation = Operation::LESS_EQUAL;
let (b, b_options) = less_equal.left.as_index_and_constant_flag(); let (b_field, b_is_constant) = less_equal_byte.left.as_index_and_constant_flag();
let (c, c_options) = less_equal.right.as_index_and_constant_flag(); let (c_field, c_is_constant) = less_equal_byte.right.as_index_and_constant_flag();
let d = less_equal.value; 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 crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct LoadBoolean { pub struct LoadBoolean {
pub destination: u8, pub destination: u16,
pub value: bool, pub value: bool,
pub jump_next: bool, pub jump_next: bool,
} }
@ -19,10 +23,35 @@ impl From<Instruction> for LoadBoolean {
impl From<LoadBoolean> for Instruction { impl From<LoadBoolean> for Instruction {
fn from(load_boolean: LoadBoolean) -> Self { fn from(load_boolean: LoadBoolean) -> Self {
let operation = Operation::LOAD_BOOLEAN; let operation = Operation::LOAD_BOOLEAN;
let a = load_boolean.destination; let a_field = load_boolean.destination;
let b = load_boolean.value as u8; let b_field = load_boolean.value as u16;
let c = load_boolean.jump_next as u8; 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 crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct LoadConstant { pub struct LoadConstant {
pub destination: u8, pub destination: u16,
pub constant_index: u8, pub constant_index: u16,
pub jump_next: bool, pub jump_next: bool,
} }
@ -24,12 +26,14 @@ impl From<Instruction> for LoadConstant {
impl From<LoadConstant> for Instruction { impl From<LoadConstant> for Instruction {
fn from(load_constant: LoadConstant) -> Self { fn from(load_constant: LoadConstant) -> Self {
let operation = Operation::LOAD_CONSTANT; InstructionBuilder {
let a = load_constant.destination; operation: Operation::LOAD_CONSTANT,
let b = load_constant.constant_index; a_field: load_constant.destination,
let c = load_constant.jump_next as u8; b_field: load_constant.constant_index,
c_field: load_constant.jump_next as u16,
Instruction::new(operation, a, b, c, false, false, false) ..Default::default()
}
.build()
} }
} }
@ -41,12 +45,12 @@ impl Display for LoadConstant {
jump_next, jump_next,
} = self; } = self;
write!(f, "R{destination} = Constant {constant_index}")?; write!(f, "R{destination} = C{constant_index}")?;
if *jump_next { if *jump_next {
write!(f, " JUMP +1") write!(f, " JUMP +1")?;
} else { }
Ok(()) Ok(())
} }
} }
}

View File

@ -1,40 +1,54 @@
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use super::{Instruction, Operation}; use super::{Instruction, InstructionBuilder, Operation};
pub struct LoadFunction { pub struct LoadFunction {
pub destination: u8, pub destination: u16,
pub prototype_index: u8, pub prototype_index: u16,
pub jump_next: bool,
} }
impl From<Instruction> for LoadFunction { impl From<Instruction> for LoadFunction {
fn from(instruction: Instruction) -> Self { fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field(); 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 { LoadFunction {
destination, destination,
prototype_index: record_index, prototype_index,
jump_next,
} }
} }
} }
impl From<LoadFunction> for Instruction { impl From<LoadFunction> for Instruction {
fn from(load_function: LoadFunction) -> Self { fn from(load_function: LoadFunction) -> Self {
Instruction::new( InstructionBuilder {
Operation::LOAD_FUNCTION, operation: Operation::LOAD_FUNCTION,
load_function.destination, a_field: load_function.destination,
load_function.prototype_index, b_field: load_function.prototype_index,
0, c_field: load_function.jump_next as u16,
false, ..Default::default()
false, }
false, .build()
)
} }
} }
impl Display for LoadFunction { impl Display for LoadFunction {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { 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 crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct LoadList { pub struct LoadList {
pub destination: u8, pub destination: u16,
pub start_register: u8, pub start_register: u16,
pub jump_next: bool,
} }
impl From<Instruction> for LoadList { impl From<Instruction> for LoadList {
fn from(instruction: Instruction) -> Self { fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field(); let destination = instruction.a_field();
let start_register = instruction.b_field(); let start_register = instruction.b_field();
let jump_next = instruction.c_field() != 0;
LoadList { LoadList {
destination, destination,
start_register, start_register,
jump_next,
} }
} }
} }
impl From<LoadList> for Instruction { impl From<LoadList> for Instruction {
fn from(load_list: LoadList) -> Self { fn from(load_list: LoadList) -> Self {
let operation = Operation::LOAD_LIST; InstructionBuilder {
let a = load_list.destination; operation: Operation::LOAD_LIST,
let b = load_list.start_register; a_field: load_list.destination,
b_field: load_list.start_register,
c_field: load_list.jump_next as u16,
..Default::default()
}
.build()
}
}
Instruction::new(operation, a, b, 0, false, false, false) 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 crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct LoadSelf { pub struct LoadSelf {
pub destination: u8, pub destination: u16,
pub jump_next: bool,
} }
impl From<Instruction> for LoadSelf { impl From<Instruction> for LoadSelf {
fn from(instruction: Instruction) -> Self { fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field(); let destination = instruction.a_field();
let jump_next = instruction.c_field() != 0;
LoadSelf { destination } LoadSelf {
destination,
jump_next,
}
} }
} }
impl From<LoadSelf> for Instruction { impl From<LoadSelf> for Instruction {
fn from(load_self: LoadSelf) -> Self { fn from(load_self: LoadSelf) -> Self {
let operation = Operation::LOAD_SELF; InstructionBuilder {
let a = load_self.destination; operation: Operation::LOAD_SELF,
a_field: load_self.destination,
c_field: load_self.jump_next as u16,
..Default::default()
}
.build()
}
}
Instruction::new(operation, a, 0, 0, false, false, false) 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 struct Modulo {
pub destination: u8, pub destination: u16,
pub left: Argument, pub left: Operand,
pub right: Argument, pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
} }
impl From<Instruction> for Modulo { impl From<Instruction> for Modulo {
fn from(instruction: Instruction) -> Self { fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field(); 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 { Modulo {
destination, destination,
left, left,
left_type,
right, right,
right_type,
} }
} }
} }
@ -22,10 +30,40 @@ impl From<Instruction> for Modulo {
impl From<Modulo> for Instruction { impl From<Modulo> for Instruction {
fn from(modulo: Modulo) -> Self { fn from(modulo: Modulo) -> Self {
let operation = Operation::MODULO; let operation = Operation::MODULO;
let a = modulo.destination; let a_field = modulo.destination;
let (b, b_is_constant) = modulo.left.as_index_and_constant_flag(); let (b_field, b_is_constant) = modulo.left.as_index_and_constant_flag();
let (c, c_is_constant) = modulo.right.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 struct Multiply {
pub destination: u8, pub destination: u16,
pub left: Argument, pub left: Operand,
pub right: Argument, pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
} }
impl From<Instruction> for Multiply { impl From<Instruction> for Multiply {
fn from(instruction: Instruction) -> Self { fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field(); 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 { Multiply {
destination, destination,
left, left,
left_type,
right, right,
right_type,
} }
} }
} }
@ -22,10 +30,40 @@ impl From<Instruction> for Multiply {
impl From<Multiply> for Instruction { impl From<Multiply> for Instruction {
fn from(multiply: Multiply) -> Self { fn from(multiply: Multiply) -> Self {
let operation = Operation::MULTIPLY; let operation = Operation::MULTIPLY;
let a = multiply.destination; let a_field = multiply.destination;
let (b, b_options) = multiply.left.as_index_and_constant_flag(); let (b_field, b_is_constant) = multiply.left.as_index_and_constant_flag();
let (c, c_options) = multiply.right.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 struct Negate {
pub destination: u8, pub destination: u16,
pub argument: Argument, pub argument: Operand,
pub argument_type: TypeCode,
} }
impl From<Instruction> for Negate { impl From<Instruction> for Negate {
fn from(instruction: Instruction) -> Self { fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field(); let destination = instruction.a_field();
let argument = instruction.b_as_argument(); let argument = instruction.b_as_argument();
let argument_type = instruction.b_type();
Negate { Negate {
destination, destination,
argument, argument,
argument_type,
} }
} }
} }
@ -20,10 +25,30 @@ impl From<Instruction> for Negate {
impl From<Negate> for Instruction { impl From<Negate> for Instruction {
fn from(negate: Negate) -> Self { fn from(negate: Negate) -> Self {
let operation = Operation::NEGATE; let operation = Operation::NEGATE;
let a = negate.destination; let a_field = negate.destination;
let (b, b_is_constant) = negate.argument.as_index_and_constant_flag(); let (b_field, b_is_constant) = negate.argument.as_index_and_constant_flag();
let c = 0; 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 struct Not {
pub destination: u8, pub destination: u16,
pub argument: Argument, pub argument: Operand,
} }
impl From<Instruction> for Not { impl From<Instruction> for Not {
@ -20,9 +24,27 @@ impl From<Instruction> for Not {
impl From<Not> for Instruction { impl From<Not> for Instruction {
fn from(not: Not) -> Self { fn from(not: Not) -> Self {
let operation = Operation::NOT; let operation = Operation::NOT;
let a = not.destination; let a_field = not.destination;
let (b, b_is_constant) = not.argument.as_index_and_constant_flag(); 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); pub struct Operation(pub u8);
impl Operation { impl Operation {
// Stack manipulation
pub const POINT: Operation = Operation(0); pub const POINT: Operation = Operation(0);
pub const CLOSE: Operation = Operation(1); pub const CLOSE: Operation = Operation(1);
// Loaders
pub const LOAD_BOOLEAN: Operation = Operation(2); pub const LOAD_BOOLEAN: Operation = Operation(2);
pub const LOAD_CONSTANT: Operation = Operation(3); pub const LOAD_CONSTANT: Operation = Operation(3);
pub const LOAD_FUNCTION: Operation = Operation(4); pub const LOAD_FUNCTION: Operation = Operation(4);
pub const LOAD_LIST: Operation = Operation(5); pub const LOAD_LIST: Operation = Operation(5);
pub const LOAD_SELF: Operation = Operation(6); pub const LOAD_SELF: Operation = Operation(6);
// Locals
pub const GET_LOCAL: Operation = Operation(7); pub const GET_LOCAL: Operation = Operation(7);
pub const SET_LOCAL: Operation = Operation(8); pub const SET_LOCAL: Operation = Operation(8);
// Arithmetic
pub const ADD: Operation = Operation(9); pub const ADD: Operation = Operation(9);
pub const SUBTRACT: Operation = Operation(10); pub const SUBTRACT: Operation = Operation(10);
pub const MULTIPLY: Operation = Operation(11); pub const MULTIPLY: Operation = Operation(11);
pub const DIVIDE: Operation = Operation(12); pub const DIVIDE: Operation = Operation(12);
pub const MODULO: Operation = Operation(13); pub const MODULO: Operation = Operation(13);
pub const TEST: Operation = Operation(14);
pub const TEST_SET: Operation = Operation(15); // Comparison
pub const EQUAL: Operation = Operation(16); pub const EQUAL: Operation = Operation(14);
pub const LESS: Operation = Operation(17); pub const LESS: Operation = Operation(15);
pub const LESS_EQUAL: Operation = Operation(18); pub const LESS_EQUAL: Operation = Operation(16);
pub const NEGATE: Operation = Operation(19);
pub const NOT: Operation = Operation(20); // 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: Operation = Operation(21);
pub const CALL_NATIVE: Operation = Operation(22); pub const CALL_NATIVE: Operation = Operation(22);
// Control flow
pub const JUMP: Operation = Operation(23); pub const JUMP: Operation = Operation(23);
pub const RETURN: Operation = Operation(24); pub const RETURN: Operation = Operation(24);
} }
@ -53,23 +70,41 @@ impl Operation {
Self::MULTIPLY => "MULTIPLY", Self::MULTIPLY => "MULTIPLY",
Self::DIVIDE => "DIVIDE", Self::DIVIDE => "DIVIDE",
Self::MODULO => "MODULO", Self::MODULO => "MODULO",
Self::TEST => "TEST",
Self::TEST_SET => "TEST_SET",
Self::EQUAL => "EQUAL", Self::EQUAL => "EQUAL",
Self::LESS => "LESS", Self::LESS => "LESS",
Self::LESS_EQUAL => "LESS_EQUAL", Self::LESS_EQUAL => "LESS_EQUAL",
Self::NEGATE => "NEGATE", Self::NEGATE => "NEGATE",
Self::NOT => "NOT", Self::NOT => "NOT",
Self::TEST => "TEST",
Self::TEST_SET => "TEST_SET",
Self::CALL => "CALL", Self::CALL => "CALL",
Self::CALL_NATIVE => "CALL_NATIVE", Self::CALL_NATIVE => "CALL_NATIVE",
Self::JUMP => "JUMP", Self::JUMP => "JUMP",
Self::RETURN => "RETURN", Self::RETURN => "RETURN",
_ => Self::panic_from_unknown_code(self.0), _ => self.panic_from_unknown_code(),
} }
} }
pub fn panic_from_unknown_code(code: u8) -> ! { pub fn is_math(self) -> bool {
panic!("Unknown operation code: {code}"); 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 crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct Point { pub struct Point {
pub from: u8, pub from: u16,
pub to: u8, pub to: u16,
} }
impl From<Instruction> for Point { 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 { impl Display for Point {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let Point { from, to } = self; let Point { from, to } = self;
@ -23,13 +41,3 @@ impl Display for Point {
write!(f, "{from} -> {to}") 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 crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct Return { pub struct Return {
pub should_return_value: bool, pub should_return_value: bool,
pub return_register: u8, pub return_register: u16,
} }
impl From<Instruction> for Return { impl From<Instruction> for Return {
@ -20,9 +24,30 @@ impl From<Instruction> for Return {
impl From<Return> for Instruction { impl From<Return> for Instruction {
fn from(r#return: Return) -> Self { fn from(r#return: Return) -> Self {
let operation = Operation::RETURN; let operation = Operation::RETURN;
let b = r#return.should_return_value as u8; let b_field = r#return.should_return_value as u16;
let c = r#return.return_register; 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 crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct SetLocal { pub struct SetLocal {
pub register_index: u8, pub register_index: u16,
pub local_index: u8, pub local_index: u16,
} }
impl From<Instruction> for SetLocal { impl From<Instruction> for SetLocal {
@ -20,9 +24,26 @@ impl From<Instruction> for SetLocal {
impl From<SetLocal> for Instruction { impl From<SetLocal> for Instruction {
fn from(set_local: SetLocal) -> Self { fn from(set_local: SetLocal) -> Self {
let operation = Operation::SET_LOCAL; let operation = Operation::SET_LOCAL;
let b = set_local.register_index; let b_field = set_local.register_index;
let c = set_local.local_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 struct Subtract {
pub destination: u8, pub destination: u16,
pub left: Argument, pub left: Operand,
pub right: Argument, pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
} }
impl From<Instruction> for Subtract { impl From<Instruction> for Subtract {
fn from(instruction: Instruction) -> Self { fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field(); 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 { Subtract {
destination, destination,
left, left,
left_type,
right, right,
right_type,
} }
} }
} }
@ -22,10 +30,40 @@ impl From<Instruction> for Subtract {
impl From<Subtract> for Instruction { impl From<Subtract> for Instruction {
fn from(subtract: Subtract) -> Self { fn from(subtract: Subtract) -> Self {
let operation = Operation::SUBTRACT; let operation = Operation::SUBTRACT;
let a = subtract.destination; let a_field = subtract.destination;
let (b, b_is_constant) = subtract.left.as_index_and_constant_flag(); let (b_field, b_is_constant) = subtract.left.as_index_and_constant_flag();
let (c, c_is_constant) = subtract.right.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 crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct Test { pub struct Test {
pub operand_register: u8, pub operand_register: u16,
pub test_value: bool, pub test_value: bool,
} }
@ -19,14 +23,27 @@ impl From<Instruction> for Test {
impl From<Test> for Instruction { impl From<Test> for Instruction {
fn from(test: Test) -> Self { fn from(test: Test) -> Self {
Instruction::new( let b_field = test.operand_register;
Operation::TEST, let c_field = test.test_value as u16;
0,
test.operand_register, InstructionBuilder {
test.test_value as u8, operation: Operation::TEST,
false, b_field,
false, c_field,
false, ..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 struct TestSet {
pub destination: u8, pub destination: u16,
pub argument: Argument, pub argument: Operand,
pub test_value: bool, pub test_value: bool,
} }
@ -23,10 +27,34 @@ impl From<Instruction> for TestSet {
impl From<TestSet> for Instruction { impl From<TestSet> for Instruction {
fn from(test_set: TestSet) -> Self { fn from(test_set: TestSet) -> Self {
let operation = Operation::TEST; let operation = Operation::TEST;
let a = test_set.destination; let a_field = test_set.destination;
let (b, b_is_constant) = test_set.argument.as_index_and_constant_flag(); let (b_field, b_is_constant) = test_set.argument.as_index_and_constant_flag();
let c = test_set.test_value as u8; 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 //! - [`Lexer`], which lexes the input a token at a time
use serde::{Deserialize, Serialize}; 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. /// Lexes the input and returns a vector of tokens and their positions.
/// ///
@ -83,7 +83,7 @@ impl<'src> Lexer<'src> {
self.skip_whitespace(); self.skip_whitespace();
let (token, span) = if let Some(character) = self.peek_char() { 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)? lexer(self)?
} else { } 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> { pub struct LexRule<'src> {
lexer: LexerFn<'src>, lex_action: LexAction<'src>,
} }
impl From<&char> for LexRule<'_> { impl From<&char> for LexRule<'_> {
fn from(char: &char) -> Self { fn from(char: &char) -> Self {
match char { match char {
'0'..='9' => LexRule { '0'..='9' => LexRule {
lexer: Lexer::lex_numeric, lex_action: Lexer::lex_numeric,
}, },
char if char.is_alphabetic() => LexRule { char if char.is_alphabetic() => LexRule {
lexer: Lexer::lex_keyword_or_identifier, lex_action: Lexer::lex_keyword_or_identifier,
}, },
'"' => LexRule { '"' => LexRule {
lexer: Lexer::lex_string, lex_action: Lexer::lex_string,
}, },
'\'' => LexRule { '\'' => LexRule {
lexer: Lexer::lex_char, lex_action: Lexer::lex_char,
}, },
'+' => LexRule { '+' => LexRule {
lexer: Lexer::lex_plus, lex_action: Lexer::lex_plus,
}, },
'-' => LexRule { '-' => LexRule {
lexer: Lexer::lex_minus, lex_action: Lexer::lex_minus,
}, },
'*' => LexRule { '*' => LexRule {
lexer: Lexer::lex_star, lex_action: Lexer::lex_star,
}, },
'/' => LexRule { '/' => LexRule {
lexer: Lexer::lex_slash, lex_action: Lexer::lex_slash,
}, },
'%' => LexRule { '%' => LexRule {
lexer: Lexer::lex_percent, lex_action: Lexer::lex_percent,
}, },
'!' => LexRule { '!' => LexRule {
lexer: Lexer::lex_exclamation_mark, lex_action: Lexer::lex_exclamation_mark,
}, },
'=' => LexRule { '=' => LexRule {
lexer: Lexer::lex_equal, lex_action: Lexer::lex_equal,
}, },
'<' => LexRule { '<' => LexRule {
lexer: Lexer::lex_less_than, lex_action: Lexer::lex_less_than,
}, },
'>' => LexRule { '>' => LexRule {
lexer: Lexer::lex_greater_than, lex_action: Lexer::lex_greater_than,
}, },
'&' => LexRule { '&' => LexRule {
lexer: Lexer::lex_ampersand, lex_action: Lexer::lex_ampersand,
}, },
'|' => LexRule { '|' => LexRule {
lexer: Lexer::lex_pipe, lex_action: Lexer::lex_pipe,
}, },
'(' => LexRule { '(' => LexRule {
lexer: Lexer::lex_left_parenthesis, lex_action: Lexer::lex_left_parenthesis,
}, },
')' => LexRule { ')' => LexRule {
lexer: Lexer::lex_right_parenthesis, lex_action: Lexer::lex_right_parenthesis,
}, },
'[' => LexRule { '[' => LexRule {
lexer: Lexer::lex_left_bracket, lex_action: Lexer::lex_left_bracket,
}, },
']' => LexRule { ']' => LexRule {
lexer: Lexer::lex_right_bracket, lex_action: Lexer::lex_right_bracket,
}, },
'{' => LexRule { '{' => LexRule {
lexer: Lexer::lex_left_brace, lex_action: Lexer::lex_left_brace,
}, },
'}' => LexRule { '}' => LexRule {
lexer: Lexer::lex_right_brace, lex_action: Lexer::lex_right_brace,
}, },
';' => LexRule { ';' => LexRule {
lexer: Lexer::lex_semicolon, lex_action: Lexer::lex_semicolon,
}, },
':' => LexRule { ':' => LexRule {
lexer: Lexer::lex_colon, lex_action: Lexer::lex_colon,
}, },
',' => LexRule { ',' => LexRule {
lexer: Lexer::lex_comma, lex_action: Lexer::lex_comma,
}, },
'.' => LexRule { '.' => LexRule {
lexer: Lexer::lex_dot, lex_action: Lexer::lex_dot,
}, },
_ => LexRule { _ => LexRule {
lexer: Lexer::lex_unexpected, lex_action: Lexer::lex_unexpected,
}, },
} }
} }

View File

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

View File

@ -2,7 +2,7 @@ use std::{ops::Range, panic};
use crate::vm::ThreadData; 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 position = data.current_position();
let mut message = format!("Dust panic at {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 std::ops::Range;
use crate::{ use crate::{
vm::{get_next_action, Register, ThreadData},
ConcreteValue, Value, ConcreteValue, Value,
vm::{Register, ThreadData, get_next_action},
}; };
pub fn read_line( pub fn read_line(data: &mut ThreadData, destination: u16, _argument_range: Range<u16>) -> bool {
data: &mut ThreadData,
destination: Option<u8>,
_argument_range: Range<u8>,
) -> bool {
let destination = destination.unwrap();
let mut buffer = String::new(); let mut buffer = String::new();
if stdin().read_line(&mut buffer).is_ok() { if stdin().read_line(&mut buffer).is_ok() {
@ -29,7 +24,7 @@ pub fn read_line(
false 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(); let mut stdout = stdout();
for register_index in argument_range { for register_index in argument_range {
@ -45,11 +40,7 @@ pub fn write(data: &mut ThreadData, _destination: Option<u8>, argument_range: Ra
false false
} }
pub fn write_line( pub fn write_line(data: &mut ThreadData, _: u16, argument_range: Range<u16>) -> bool {
data: &mut ThreadData,
_destination: Option<u8>,
argument_range: Range<u8>,
) -> bool {
let mut stdout = stdout().lock(); let mut stdout = stdout().lock();
for register_index in argument_range { for register_index in argument_range {

View File

@ -4,7 +4,9 @@
//! itself or that are more efficient to implement in Rust. //! itself or that are more efficient to implement in Rust.
mod assert; mod assert;
mod io; mod io;
mod random;
mod string; mod string;
mod thread;
use std::{ use std::{
fmt::{self, Display, Formatter}, fmt::{self, Display, Formatter},
@ -15,7 +17,7 @@ use std::{
use serde::{Deserialize, Serialize}; 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 { macro_rules! define_native_function {
($(($name:ident, $bytes:literal, $str:expr, $type:expr, $function:expr)),*) => { ($(($name:ident, $bytes:literal, $str:expr, $type:expr, $function:expr)),*) => {
@ -33,8 +35,8 @@ macro_rules! define_native_function {
pub fn call( pub fn call(
&self, &self,
data: &mut ThreadData, data: &mut ThreadData,
destination: Option<u8>, destination: u16,
argument_range: Range<u8>, argument_range: Range<u16>,
) -> bool { ) -> bool {
match self { match self {
$( $(
@ -78,8 +80,8 @@ macro_rules! define_native_function {
} }
} }
impl From<u8> for NativeFunction { impl From<u16> for NativeFunction {
fn from(bytes: u8) -> Self { fn from(bytes: u16) -> Self {
match bytes { match bytes {
$( $(
$bytes => NativeFunction::$name, $bytes => NativeFunction::$name,
@ -244,11 +246,42 @@ define_native_function! {
return_type: Type::None return_type: Type::None
}, },
io::write_line io::write_line
) ),
// // Random // // 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)] #[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 std::ops::Range;
use crate::{ use crate::{
vm::{get_next_action, Register, ThreadData},
ConcreteValue, Value, ConcreteValue, Value,
vm::{Register, ThreadData, get_next_action},
}; };
pub fn to_string( pub fn to_string(data: &mut ThreadData, destination: u16, argument_range: Range<u16>) -> bool {
data: &mut ThreadData,
destination: Option<u8>,
argument_range: Range<u8>,
) -> bool {
let argument_value = data.open_register_unchecked(argument_range.start); let argument_value = data.open_register_unchecked(argument_range.start);
let argument_string = argument_value.display(data); let argument_string = argument_value.display(data);
let destination = destination.unwrap();
let register = Register::Value(Value::Concrete(ConcreteValue::string(argument_string))); let register = Register::Value(Value::Concrete(ConcreteValue::string(argument_string)));
data.set_register(destination, register); 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)] #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct FunctionType { pub struct FunctionType {
pub type_parameters: Vec<u8>, pub type_parameters: Vec<u16>,
pub value_parameters: Vec<(u8, Type)>, pub value_parameters: Vec<(u16, Type)>,
pub return_type: Type, pub return_type: Type,
} }
impl FunctionType { 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, type_parameters: T,
value_parameters: U, value_parameters: U,
return_type: Type, return_type: Type,

View File

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

View File

@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize};
use std::fmt::{self, Debug, Display, Formatter}; use std::fmt::{self, Debug, Display, Formatter};
use crate::{vm::ThreadData, Type}; use crate::{Type, vm::ThreadData};
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
pub enum Value { pub enum Value {
@ -50,9 +50,33 @@ impl Value {
Value::Concrete(ConcreteValue::String(string.into())) Value::Concrete(ConcreteValue::String(string.into()))
} }
pub fn as_boolean(&self) -> Option<&bool> { pub fn as_boolean(&self) -> Option<bool> {
if let Value::Concrete(ConcreteValue::Boolean(value)) = self { if let Value::Concrete(ConcreteValue::Boolean(boolean)) = self {
Some(value) 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 { } else {
None 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> { pub fn as_string(&self) -> Option<&DustString> {
if let Value::Concrete(ConcreteValue::String(value)) = self { if let Value::Concrete(ConcreteValue::String(value)) = self {
Some(value) 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 { pub fn r#type(&self) -> Type {
match self { match self {
Value::Concrete(concrete_value) => concrete_value.r#type(), 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 crate::{Chunk, DustString};
use super::Register; use super::Register;
#[derive(Debug)] #[derive(Debug)]
pub struct FunctionCall<'a> { pub struct FunctionCall {
pub chunk: &'a Chunk, pub chunk: Arc<Chunk>,
pub ip: usize, pub ip: usize,
pub return_register: u8, pub return_register: u16,
pub registers: Vec<Register>, pub registers: Vec<Register>,
} }
impl<'a> FunctionCall<'a> { impl FunctionCall {
pub fn new(chunk: &'a Chunk, return_register: u8) -> Self { pub fn new(chunk: Arc<Chunk>, return_register: u16) -> Self {
let register_count = chunk.register_count;
Self { Self {
chunk, chunk,
ip: 0, ip: 0,
return_register, 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 { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!( write!(
f, f,

View File

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

View File

@ -1,16 +1,16 @@
use tracing::trace; use tracing::trace;
use crate::{ use crate::{
AbstractList, ConcreteValue, Instruction, Operand, Type, Value,
instruction::{ instruction::{
Add, Call, CallNative, Close, Divide, Equal, GetLocal, Jump, Less, LessEqual, LoadBoolean, Add, Call, CallNative, Close, Divide, Equal, GetLocal, Jump, Less, LessEqual, LoadBoolean,
LoadConstant, LoadFunction, LoadList, LoadSelf, Modulo, Multiply, Negate, Not, Point, LoadConstant, LoadFunction, LoadList, LoadSelf, Modulo, Multiply, Negate, Not, Point,
Return, SetLocal, Subtract, Test, TestSet, Return, SetLocal, Subtract, Test, TestSet, TypeCode,
}, },
vm::FunctionCall, 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)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct RunAction { pub struct RunAction {
@ -44,13 +44,13 @@ pub const RUNNER_LOGIC_TABLE: [RunnerLogic; 25] = [
multiply, multiply,
divide, divide,
modulo, modulo,
test,
test_set,
equal, equal,
less, less,
less_equal, less_equal,
negate, negate,
not, not,
test,
test_set,
call, call,
call_native, call_native,
jump, jump,
@ -145,6 +145,7 @@ pub fn load_list(instruction: Instruction, data: &mut ThreadData) -> bool {
let LoadList { let LoadList {
destination, destination,
start_register, start_register,
jump_next,
} = instruction.into(); } = instruction.into();
let mut item_pointers = Vec::with_capacity((destination - start_register) as usize); let mut item_pointers = Vec::with_capacity((destination - start_register) as usize);
let mut item_type = Type::Any; let mut item_type = Type::Any;
@ -186,6 +187,7 @@ pub fn load_function(instruction: Instruction, data: &mut ThreadData) -> bool {
let LoadFunction { let LoadFunction {
destination, destination,
prototype_index, prototype_index,
jump_next,
} = instruction.into(); } = instruction.into();
let prototype_index = prototype_index as usize; let prototype_index = prototype_index as usize;
let current_call = data.call_stack.last_mut_unchecked(); 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 { 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 current_call = data.call_stack.last_mut_unchecked();
let prototype = &current_call.chunk; let prototype = &current_call.chunk;
let function = prototype.as_function(); let function = prototype.as_function();
@ -248,12 +253,42 @@ pub fn add(instruction: Instruction, data: &mut ThreadData) -> bool {
let Add { let Add {
destination, destination,
left, left,
left_type,
right, right,
right_type,
} = instruction.into(); } = instruction.into();
let left = data.get_argument_unchecked(left); let sum = match (left_type, right_type) {
let right = data.get_argument_unchecked(right); (TypeCode::INTEGER, TypeCode::INTEGER) => {
let sum = left.add(right); let left = unsafe {
let register = Register::Value(sum); 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); data.set_register(destination, register);
@ -266,12 +301,42 @@ pub fn subtract(instruction: Instruction, data: &mut ThreadData) -> bool {
let Subtract { let Subtract {
destination, destination,
left, left,
left_type,
right, right,
right_type,
} = instruction.into(); } = instruction.into();
let left = data.get_argument_unchecked(left); let difference = match (left_type, right_type) {
let right = data.get_argument_unchecked(right); (TypeCode::INTEGER, TypeCode::INTEGER) => {
let difference = left.subtract(right); let left = unsafe {
let register = Register::Value(difference); 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); data.set_register(destination, register);
@ -284,20 +349,42 @@ pub fn multiply(instruction: Instruction, data: &mut ThreadData) -> bool {
let Multiply { let Multiply {
destination, destination,
left, left,
left_type,
right, right,
right_type,
} = instruction.into(); } = instruction.into();
let left = data.get_argument_unchecked(left); let product = match (left_type, right_type) {
let right = data.get_argument_unchecked(right); (TypeCode::INTEGER, TypeCode::INTEGER) => {
let product = match (left, right) { let left = unsafe {
(Value::Concrete(left), Value::Concrete(right)) => match (left, right) { data.get_argument_unchecked(left)
(ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => { .as_integer()
ConcreteValue::Integer(left * right).to_value() .unwrap_unchecked()
}
_ => panic!("Value Error: Cannot multiply values"),
},
_ => panic!("Value Error: Cannot multiply values"),
}; };
let register = Register::Value(product); 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(Value::Concrete(product));
data.set_register(destination, register); data.set_register(destination, register);
@ -310,20 +397,42 @@ pub fn divide(instruction: Instruction, data: &mut ThreadData) -> bool {
let Divide { let Divide {
destination, destination,
left, left,
left_type,
right, right,
right_type,
} = instruction.into(); } = instruction.into();
let left = data.get_argument_unchecked(left); let quotient = match (left_type, right_type) {
let right = data.get_argument_unchecked(right); (TypeCode::INTEGER, TypeCode::INTEGER) => {
let quotient = match (left, right) { let left = unsafe {
(Value::Concrete(left), Value::Concrete(right)) => match (left, right) { data.get_argument_unchecked(left)
(ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => { .as_integer()
ConcreteValue::Integer(left / right).to_value() .unwrap_unchecked()
}
_ => panic!("Value Error: Cannot divide values"),
},
_ => panic!("Value Error: Cannot divide values"),
}; };
let register = Register::Value(quotient); 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(Value::Concrete(quotient));
data.set_register(destination, register); data.set_register(destination, register);
@ -336,20 +445,28 @@ pub fn modulo(instruction: Instruction, data: &mut ThreadData) -> bool {
let Modulo { let Modulo {
destination, destination,
left, left,
left_type,
right, right,
right_type,
} = instruction.into(); } = instruction.into();
let left = data.get_argument_unchecked(left); let remainder = match (left_type, right_type) {
let right = data.get_argument_unchecked(right); (TypeCode::INTEGER, TypeCode::INTEGER) => {
let remainder = match (left, right) { let left = unsafe {
(Value::Concrete(left), Value::Concrete(right)) => match (left, right) { data.get_argument_unchecked(left)
(ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => { .as_integer()
ConcreteValue::Integer(left % right).to_value() .unwrap_unchecked()
}
_ => panic!("Value Error: Cannot modulo values"),
},
_ => panic!("Value Error: Cannot modulo values"),
}; };
let register = Register::Value(remainder); 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(Value::Concrete(remainder));
data.set_register(destination, register); data.set_register(destination, register);
@ -397,8 +514,8 @@ pub fn test_set(instruction: Instruction, data: &mut ThreadData) -> bool {
if boolean == test_value { if boolean == test_value {
} else { } else {
let pointer = match argument { let pointer = match argument {
Argument::Constant(constant_index) => Pointer::Constant(constant_index), Operand::Constant(constant_index) => Pointer::Constant(constant_index),
Argument::Register(register_index) => Pointer::Register(register_index), Operand::Register(register_index) => Pointer::Register(register_index),
}; };
let register = Register::Pointer(pointer); 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 { pub fn equal(instruction: Instruction, data: &mut ThreadData) -> bool {
let Equal { value, left, right } = instruction.into(); let Equal {
let left = data.get_argument_unchecked(left); comparator,
let right = data.get_argument_unchecked(right); left,
let is_equal = left.equals(right); 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(); let current_call = data.call_stack.last_mut_unchecked();
current_call.ip += 1; 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 { pub fn less(instruction: Instruction, data: &mut ThreadData) -> bool {
let Less { value, left, right } = instruction.into(); let Less {
let left = data.get_argument_unchecked(left); comparator,
let right = data.get_argument_unchecked(right); left,
let is_less = left < right; 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(); let current_call = data.call_stack.last_mut_unchecked();
current_call.ip += 1; 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 { pub fn less_equal(instruction: Instruction, data: &mut ThreadData) -> bool {
let LessEqual { value, left, right } = instruction.into(); let LessEqual {
let left = data.get_argument_unchecked(left); comparator,
let right = data.get_argument_unchecked(right); left,
let is_less_or_equal = left <= right; 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(); let current_call = data.call_stack.last_mut_unchecked();
current_call.ip += 1; current_call.ip += 1;
@ -465,6 +712,7 @@ pub fn negate(instruction: Instruction, data: &mut ThreadData) -> bool {
let Negate { let Negate {
destination, destination,
argument, argument,
argument_type,
} = instruction.into(); } = instruction.into();
let argument = data.get_argument_unchecked(argument); let argument = data.get_argument_unchecked(argument);
let negated = argument.negate(); 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 current_call = data.call_stack.last_unchecked();
let first_argument_register = return_register - argument_count; let first_argument_register = return_register - argument_count;
let prototype = if is_recursive { let prototype = if is_recursive {
current_call.chunk current_call.chunk.clone()
} else { } else {
let function = data let function = data
.open_register_unchecked(function_register) .open_register_unchecked(function_register)
.as_function() .as_function()
.unwrap(); .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 next_call = FunctionCall::new(prototype, return_register);
let mut argument_index = 0; 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 first_argument_index = destination - argument_count;
let argument_range = first_argument_index..destination; 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 { 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 { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
writeln!(f, "----- DUST CALL STACK -----")?; 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 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}; use super::{Pointer, Register, RunAction, Stack};
pub struct Thread { pub struct Thread {
chunk: Chunk, chunk: Arc<Chunk>,
} }
impl Thread { impl Thread {
pub fn new(chunk: Chunk) -> Self { pub fn new(chunk: Arc<Chunk>) -> Self {
Thread { chunk } Thread { chunk }
} }
@ -25,7 +25,8 @@ impl Thread {
); );
let mut call_stack = Stack::with_capacity(self.chunk.prototypes.len() + 1); 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); call_stack.push(main_call);
@ -34,6 +35,7 @@ impl Thread {
call_stack, call_stack,
next_action: first_action, next_action: first_action,
return_value_index: None, return_value_index: None,
spawned_threads: Vec::new(),
}; };
loop { loop {
@ -45,7 +47,7 @@ impl Thread {
); );
if should_end { 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 = let value =
thread_data.empty_register_or_clone_constant_unchecked(register_index); thread_data.empty_register_or_clone_constant_unchecked(register_index);
@ -54,20 +56,28 @@ impl Thread {
None None
}; };
return return_value; thread_data
.spawned_threads
.into_iter()
.for_each(|join_handle| {
let _ = join_handle.join();
});
return value_option;
} }
} }
} }
} }
#[derive(Debug)] #[derive(Debug)]
pub struct ThreadData<'a> { pub struct ThreadData {
pub call_stack: Stack<FunctionCall<'a>>, pub call_stack: Stack<FunctionCall>,
pub next_action: RunAction, 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 { pub fn current_position(&self) -> Span {
let current_call = self.call_stack.last_unchecked(); let current_call = self.call_stack.last_unchecked();
@ -75,7 +85,7 @@ impl ThreadData<'_> {
} }
pub(crate) fn follow_pointer_unchecked(&self, pointer: Pointer) -> &Value { pub(crate) fn follow_pointer_unchecked(&self, pointer: Pointer) -> &Value {
trace!("Follow pointer {pointer}"); trace!("Follow {pointer}");
match pointer { match pointer {
Pointer::Register(register_index) => self.open_register_unchecked(register_index), 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 { pub fn get_register_unchecked(&self, register_index: u16) -> &Register {
trace!("Get register R{register_index}"); trace!("Get R{register_index}");
let register_index = register_index as usize; 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; let to_register = to_register as usize;
self.call_stack.last_mut_unchecked().registers[to_register] = register; 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_index = register_index as usize;
let register = if cfg!(debug_assertions) { let register = if cfg!(debug_assertions) {
@ -133,42 +143,30 @@ impl ThreadData<'_> {
} }
}; };
trace!("Open R{register_index} to {register}");
match register { match register {
Register::Value(value) => { Register::Value(value) => value,
trace!("Register R{register_index} opened to value {value}"); Register::Pointer(pointer) => self.follow_pointer_unchecked(*pointer),
value
}
Register::Pointer(pointer) => {
trace!("Open register R{register_index} opened to pointer {pointer}");
self.follow_pointer_unchecked(*pointer)
}
Register::Empty => panic!("VM Error: Register {register_index} is empty"), Register::Empty => panic!("VM Error: Register {register_index} is empty"),
} }
} }
pub fn open_register_allow_empty_unchecked(&self, register_index: u8) -> Option<&Value> { pub fn open_register_allow_empty_unchecked(&self, register_index: u16) -> Option<&Value> {
trace!("Open register R{register_index}"); trace!("Open R{register_index}");
let register = self.get_register_unchecked(register_index); let register = self.get_register_unchecked(register_index);
trace!("Open R{register_index} to {register}");
match register { match register {
Register::Value(value) => { Register::Value(value) => Some(value),
trace!("Register R{register_index} openned to value {value}"); Register::Pointer(pointer) => Some(self.follow_pointer_unchecked(*pointer)),
Some(value)
}
Register::Pointer(pointer) => {
trace!("Open register R{register_index} openned to pointer {pointer}");
Some(self.follow_pointer_unchecked(*pointer))
}
Register::Empty => None, 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 register_index = register_index as usize;
let old_register = replace( let old_register = replace(
&mut self.call_stack.last_mut_unchecked().registers[register_index], &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); let register = self.get_register_unchecked(register_index);
match register { match register {
@ -235,14 +233,14 @@ impl ThreadData<'_> {
} }
/// DRY helper to get a value from an Argument /// 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 { match argument {
Argument::Constant(constant_index) => self.get_constant_unchecked(constant_index), Operand::Constant(constant_index) => self.get_constant_unchecked(constant_index),
Argument::Register(register_index) => self.open_register_unchecked(register_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; let constant_index = constant_index as usize;
if cfg!(debug_assertions) { 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 local_index = local_index as usize;
let chunk = self.call_stack.last_unchecked().chunk; let chunk = &self.call_stack.last_unchecked().chunk;
assert!( assert!(
local_index < chunk.locals.len(), local_index < chunk.locals.len(),

View File

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

View File

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

View File

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

View File

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

View File

@ -16,12 +16,12 @@ fn r#while() {
vec![ vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)), (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) Span(23, 24)
), ),
(Instruction::jump(2, true), Span(41, 42)), (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) Span(35, 36)
), ),
(Instruction::jump(3, false), Span(41, 42)), (Instruction::jump(3, false), Span(41, 42)),

View File

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

View File

@ -16,7 +16,7 @@ fn divide_assign() {
vec![ vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)), (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) Span(17, 19)
), ),
(Instruction::get_local(1, 0), Span(23, 24)), (Instruction::get_local(1, 0), Span(23, 24)),

View File

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

View File

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

View File

@ -16,7 +16,7 @@ fn multiply_assign() {
vec![ vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)), (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) Span(17, 19)
), ),
(Instruction::get_local(1, 0), Span(22, 23)), (Instruction::get_local(1, 0), Span(22, 23)),

View File

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

View File

@ -16,7 +16,7 @@ fn subtract_assign() {
vec![ vec![
(Instruction::load_constant(0, 0, false), Span(12, 14)), (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) Span(18, 20)
), ),
(Instruction::get_local(1, 0), Span(24, 25)), (Instruction::get_local(1, 0), Span(24, 25)),

View File

@ -14,7 +14,7 @@ fn negate() {
return_type: Type::Integer, return_type: Type::Integer,
}, },
vec![ 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)), (Instruction::r#return(true), Span(5, 5)),
], ],
vec![ConcreteValue::Integer(42)], vec![ConcreteValue::Integer(42)],
@ -40,7 +40,7 @@ fn not() {
}, },
vec![ vec![
(Instruction::load_boolean(0, true, false), Span(1, 5)), (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)), (Instruction::r#return(true), Span(5, 5)),
], ],
vec![], vec![],