1
0

Compare commits

..

82 Commits
dev ... main

Author SHA1 Message Date
a08a1e560a Update to latest Rust nightly 2025-02-17 15:54:09 -05:00
1f62391b58 Fix compiler bug; Rewrite CI workflow 2025-02-17 15:36:59 -05:00
0aee607961 Update README.md 2025-02-17 15:22:40 -05:00
a53e0018cf Add comparison tests for lists; Edit README.md; Add benches to Cargo.toml 2025-02-17 15:11:55 -05:00
4c019410e8 Pass tests except for the logic tests 2025-02-17 11:23:22 -05:00
4a169bc515 Fix tests; Rename point to move; Implement lists in the VM 2025-02-17 10:04:31 -05:00
5de44b58ee Replace complex actions with a simple loop 2025-02-17 08:01:10 -05:00
0292a01162 Fill out more VM actions 2025-02-17 06:18:34 -05:00
b0f7ca7992 Expand VM functionality; Slow the VM down quite a bit 2025-02-17 02:37:21 -05:00
7153cc16f2 Fix add tests 2025-02-17 00:02:33 -05:00
7f939b693e Fix comparison tests; Implement less_equal and equal actions 2025-02-16 23:33:35 -05:00
07f8b36c99 Decide on an optimization strategy for the VM 2025-02-16 22:55:55 -05:00
5030171bb6 Add VM actions for add and less 2025-02-15 16:14:45 -05:00
afcc1874fe Clean up 2025-02-15 15:52:27 -05:00
720f006d8c Add new benchmarks; Experiment with VM optimizations 2025-02-13 10:37:35 -05:00
4145499e0c Fix typo in README.md 2025-02-12 21:44:25 -05:00
794e556c7a Test new badges 2025-02-12 19:18:44 -05:00
d21bb2cbdc Add workflow badge to README.md 2025-02-12 18:50:44 -05:00
fb5eac1307 Change Rust edition to 2021 2025-02-12 17:41:06 -05:00
728eb071e8 Resolve all clippy lints 2025-02-12 17:04:22 -05:00
8940f37654 New VM optimizations; Pass all tests 2025-02-12 17:00:27 -05:00
69ef1b3b06 Experiment with more VM optimizations 2025-02-11 16:55:54 -05:00
6a61947476 Edit README.md 2025-02-11 12:56:47 -05:00
77148cbf3f Extend VM optimizations 2025-02-11 11:09:50 -05:00
d53521c0f9 Fix typo 2025-02-11 09:32:17 -05:00
5aefb29a95 Experiment with VM optimization 2025-02-11 09:28:02 -05:00
3599643fad Tweak compiler error messages 2025-02-11 07:04:12 -05:00
00f35bd3ae Bring back the assignment error tests 2025-02-11 06:44:26 -05:00
e1e259141c Prevent unnecessary CLOSE instructions 2025-02-11 05:52:26 -05:00
59f64c9afd Fix register closing 2025-02-11 05:29:00 -05:00
833ee30930 Clean up 2025-02-11 05:08:00 -05:00
fe59024bf4 Add tests for chained logic operators 2025-02-11 04:52:09 -05:00
d2ee33c7df Add tests for double "and" and "or" operators 2025-02-11 04:11:49 -05:00
cc93d8e345 Add tests for the "and" and "or" operators 2025-02-11 03:54:56 -05:00
a53cfb9cf6 Add tests for comparison operators; Implement comparison operators in the VM 2025-02-11 03:30:20 -05:00
f2ee01d66f Add comparison tests for less than operator; Implement less than operator in VM 2025-02-11 02:22:14 -05:00
dfa5f6858c Add tests for modulo operator; Implement modulo operator in VM 2025-02-11 01:55:51 -05:00
c7a18b1d39 Update README.md; Clean up 2025-02-08 18:25:23 -05:00
63312cf08a Fix control flow and reintroduce the semicolon parser 2025-02-08 18:13:01 -05:00
a5d2e7d869 Clean up 2025-02-08 17:55:26 -05:00
3af1b64820 Reimplement more instruction and compiler logic 2025-02-08 17:36:30 -05:00
07001a03e7 Add tests for divide instruction 2025-02-08 11:58:30 -05:00
6e9d5a49d2 Add tests for multiply instruction 2025-02-08 11:45:03 -05:00
1bb87de70f Add tests for subtract instruction 2025-02-08 11:35:58 -05:00
dcea611a0b Add new tests 2025-02-08 11:21:29 -05:00
dc2acbfe46 Fix while loop compiler bug 2025-02-08 10:28:06 -05:00
95cfaecd17 Fix register allocation bug in compiler 2025-02-08 09:06:32 -05:00
e19ddbe2f3 Fix bugs and pass tests 2025-02-08 07:50:00 -05:00
e53ecd93fe Simplify CLI code 2025-02-08 07:16:10 -05:00
cd03e06ade Add support for RON as an input/output format 2025-02-08 06:57:32 -05:00
62fcdd7e8c Add support for YAML as an input/output format 2025-02-08 06:54:29 -05:00
71a92c078b Add formatting disassembly output into JSON or TOML 2025-02-08 05:56:49 -05:00
e387579a81 Extend and pass tests 2025-02-08 00:17:15 -05:00
d3addbe183 Fix and pass all tests 2025-02-07 22:39:07 -05:00
ac11ad5674 Refactor instruction layout to allow for more type codes 2025-02-07 20:52:08 -05:00
3d48558b94 Add and pass new tests 2025-02-07 19:37:39 -05:00
1d0824165d Implement lists; Add tests 2025-02-07 17:40:08 -05:00
8cc5661944 Begin rewriting tests 2025-02-07 15:37:48 -05:00
4b38a93409 Tweak disassembly output 2025-02-07 13:35:25 -05:00
72421bf510 Fix compiling of comparison expressions; Implement LoadEncoded in the VM 2025-02-07 13:29:14 -05:00
1155b5fff8 Clean up 2025-02-07 11:14:44 -05:00
820ead0c02 Store types in locals; Fix local disassembly 2025-02-07 10:39:33 -05:00
25b4230aa4 Use Operand type to store instruction argument types 2025-02-07 10:19:38 -05:00
b90adcb9f0 Clean up README.md 2025-02-06 19:59:07 -05:00
788e3d4a2b Clean up 2025-02-06 17:25:46 -05:00
75d6948e82 Consolidate point instructions into return instructions 2025-02-06 14:34:31 -05:00
bd273035aa Convert LoadBoolean to LoadEncoded; Fix register handling 2025-02-06 13:10:11 -05:00
6f0955c29a Improve control flow register consolidation 2025-02-06 12:42:55 -05:00
6d17ba9a2c Clean up 2025-02-05 19:29:15 -05:00
5662ba3d08 Add string concatenation 2025-02-05 19:27:51 -05:00
4775d425a0 Implement typed registers with untyped constants 2025-02-05 19:12:26 -05:00
03103e4fa1 Find a solution for the removal of the SetLocal instruction 2025-02-03 18:11:45 -05:00
12092c30f4 Roll back slightly 2025-02-03 18:08:03 -05:00
93f8f31b6d Fix compiler bug 2025-02-03 17:56:11 -05:00
371a061b1c Consolidate local operations to point operations 2025-02-03 17:49:38 -05:00
c1fe54ccd5 Attempt alternative to total register overhaul 2025-02-03 15:05:32 -05:00
3fcbde59e5 Edit README.md 2025-01-30 07:14:48 -05:00
7cfd60d281 Merge branch 'dev' 2025-01-13 10:44:42 -05:00
6eac92f4ce Fix small type in addictive_addition benchmarks 2025-01-09 14:41:51 -05:00
0d39d91cc7 Merge branch 'dev' 2025-01-09 10:41:17 -05:00
dc002023cd Clean up token.rs 2024-12-02 03:50:51 -05:00
8e09d487c3 Fix token list display 2024-12-02 03:46:05 -05:00
119 changed files with 10037 additions and 14907 deletions

19
.github/workflows/rust.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Cargo Build & Test
on:
push:
pull_request:
env:
CARGO_TERM_COLOR: always
jobs:
build_and_test:
name: Dust Build & Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: rustup update nightly && rustup default nightly
- run: cargo build --verbose
- run: cargo test --verbose --no-fail-fast

545
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -5,11 +5,11 @@ resolver = "2"
[workspace.package]
authors = ["Jeff Anderson"]
edition = "2024"
license = "GPL-3.0"
readme = "README.md"
repository = "https://git.jeffa.io/jeff/dust.git"
version = "0.5.0"
edition = "2021"
[profile.dev]
opt-level = 1

191
README.md
View File

@ -1,4 +1,10 @@
# ✭ Dust Programming Language
# Dust   [![test_shield]][actions] [![License: GPL v3][gpl3_shield]][gpl3] [![rust_shield]](#)
[test_shield]: https://github.com/solaeus/dust/actions/workflows/rust.yml/badge.svg
[actions]: https://github.com/solaeus/dust/actions
[gpl3_shield]: https://img.shields.io/badge/License-GPLv3-blue.svg
[gpl3]: https://www.gnu.org/licenses/gpl-3.0
[rust_shield]: https://img.shields.io/badge/Rust-%23000000.svg?e&logo=rust&logoColor=white
**Fast**, **safe** and **easy-to-use** general-purpose programming language.
@ -23,12 +29,17 @@ fn fib (n: int) -> int {
write_line(fib(25))
```
> [!IMPORTANT]
> 🧪 💡 ⚗️
> Dust is still experimental. Currently, development is more focused on exploring ideas for
> optimization and performance than on stability or feature completeness. This will change as the
> project matures.
## 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
- Multi-threaded register-based virtual machine
- Beautiful, helpful error messages from the compiler
- Safe execution, runtime errors are treated as bugs
@ -45,40 +56,40 @@ This project's goal is to deliver a language with features that stand out due to
design choices and a high-quality implementation. As mentioned in the first sentence, Dust's general
aspirations are to be **fast**, **safe** and **easy**.
- **Fast**
- **Fast** 🚀
- **Fast Compilation** Despite its compile-time abstractions, Dust should compile and start
executing quickly. The compilation time should feel negligible to the user.
- **Fast Execution** Dust should be competitive with highly optimized, modern, register-based VM
languages like Lua. Dust should be bench tested during development to inform decisions about
performance.
- **Low Resource Usage** Memory and CPU power should be used conservatively and predictably.
- **Safe**
- **Safe** 🛡️
- **Static Types** Typing should prevent runtime errors and improve code quality, offering a
superior development experience despite some additional constraints. Like any good statically
typed language, users should feel confident in the type-consistency of their code and not want
to go back to a dynamically typed language.
superior development experience despite some additional constraints.
- **Null-Free** Dust has no "null" or "undefined" values. All values are initialized and have a
type. This eliminates a whole class of bugs that are common in other languages.
- **Memory Safety** Dust should be free of memory bugs. Being implemented in Rust makes this easy
but, to accommodate long-running programs, Dust still requires a memory management strategy.
Dust's design is to use a separate thread for garbage collection, allowing other threads to
continue executing instructions while the garbage collector looks for unused memory.
- **Easy**
- **Memory Safety** Dust should be free of memory bugs, using both safe Rust and sound, correct
"unsafe" Rust to maximize performance. Dust should employ a concurrent mark-and-sweep garbage
collecter, allowing other threads to continue executing instructions while the garbage collector
looks for freeable memory.
- **Easy** 🎂
- **Simple Syntax** Dust should be easier to learn than most programming languages. Its syntax
should be familiar to users of other C-like languages to the point that even a new user can read
Dust code and understand what it does. Rather than being held back by a lack of features, Dust
should be powerful and elegant in its simplicity, seeking a maximum of capability with a minimum
of complexity.
- **Practical Tooling** Shipped as a single binary, Dust should provide logging and tools for
disassembly and tokenization that make the lexer, compiler and runtime as transparent as
possible. Dust should also include an official formatter through the same binary. Additional
tools such as a language server and linter should be adopted when possible.
- **Excellent Errors** Dust should provide helpful error messages that guide the user to the
source of the problem and suggest a solution. Errors should be a helpful learning resource for
users rather than a source of frustration.
- **Relevant Documentation** Users should have the resources they need to learn Dust and write
code in it. They should know where to look for answers and how to reach out for help.
### Author
I'm Jeff 🦀 and I started this project as simple expession evaluator. Initially, the project used an
external parser and a tree-walking interpreter. After several books, a few papers, countless
I'm Jeff 🦀 and I started this project as a simple expession evaluator. Initially, the project used
an external parser and a tree-walking interpreter. After several books, a few papers, countless
articles and a lot of experimentation, Dust has evolved to an ambitious project that aims to
implement lucrative features with a high-quality implementation that competes with established
languages.
@ -87,6 +98,150 @@ languages.
**Dust is under active development and is not yet ready for general use.**
### CLI
The Dust CLI has commands to run, disassemble or tokenize Dust code. It can also provide logging at
different levels and measure the time taken for compilation and execution.
<details>
<summary>Show CLI help</summary>
```text
Command line interface for the Dust programming language
Usage: dust [OPTIONS] [FILE]
dust {run|-r} [OPTIONS] [FILE]
dust {compile|-c} [OPTIONS] [FILE]
dust {tokenize|-t} [OPTIONS] [FILE]
dust help [COMMAND]...
Arguments:
[FILE] Path to a source code file
Options:
-l, --log-level <LOG_LEVEL> Overrides the DUST_LOG environment variable
--time Print the time taken for compilation and execution
--no-output Do not print the program's return value
--name <NAME> Custom program name, overrides the file name
-e, --eval <INPUT> Source code to run instead of a file
--stdin Read source code from stdin
-i, --input <INPUT> Input format [default: dust] [possible values: dust, json, ron, yaml]
-h, --help Print help
-V, --version Print version
dust {run|-r}:
Compile and run the program (default)
--time Print the time taken for compilation and execution
--no-output Do not print the program's return value
--name <NAME> Custom program name, overrides the file name
-e, --eval <INPUT> Source code to run instead of a file
--stdin Read source code from stdin
-i, --input <INPUT> Input format [default: dust] [possible values: dust, json, ron, yaml]
-h, --help Print help
[FILE] Path to a source code file
dust {compile|-c}:
Compile and print the input
-s, --style Style disassembly output
-n, --name <NAME> Custom program name, overrides the file name
-e, --eval <INPUT> Source code to run instead of a file
--stdin Read source code from stdin
-o, --output <FORMAT> [default: cli] [possible values: cli, json, ron, yaml]
-h, --help Print help
[FILE] Path to a source code file
dust {tokenize|-t}:
Lex the source code and print the tokens
-s, --style Style token output
-e, --eval <INPUT> Source code to run instead of a file
--stdin Read source code from stdin
-h, --help Print help
[FILE] Path to a source code file
dust help:
Print this message or the help of the given subcommand(s)
[COMMAND]... Print help for the subcommand(s)
```
</details>
#### Run a program
If not specified, the CLI will use `run` command. This mode compiles and executes the Dust program,
printing the return value to the console. You can also run Dust code directly from the command line
using the `--eval` or `-e` flag.
```sh
dust foobar.ds
dust -e 'let x = 42; x'
```
#### Compile and disassemble
Dust's disassembly output is a detailed, human-readable representation of the internal
representation of the Dust program. It shows every piece of information that the compiler sends to
the virtual machine and explains what each instruction does and what data it uses.
```sh
dust compile example.ds
```
<details>
<summary>Show disassembly</summary>
```text
╭──────────────────────────────────────────────────────────────────────────────────╮
│ example.ds │
│ │
│ let mut i = 0; while i < 10 { i += 1 }
│ │
│ 6 instructions, 4 constants, 1 locals, returns none │
│ │
│ Instructions │
│ ╭─────┬────────────┬─────────────────┬─────────────────────────────────────────╮ │
│ │ i │ POSITION │ OPERATION │ INFO │ │
│ ├─────┼────────────┼─────────────────┼─────────────────────────────────────────┤ │
│ │ 0 │ (12, 13) │ LOAD_CONSTANT │ R_INT_0 = C0 │ │
│ │ 1 │ (23, 24) │ LESS │ if R_INT_0 < C_2 { JUMP +1 }
│ │ 2 │ (38, 38) │ JUMP │ JUMP +2 │ │
│ │ 3 │ (30, 36) │ ADD │ R_INT_0 = R_INT_0 + C_3 │ │
│ │ 4 │ (38, 38) │ JUMP │ JUMP -3 │ │
│ │ 5 │ (38, 38) │ RETURN │ RETURN │ │
│ ╰─────┴────────────┴─────────────────┴─────────────────────────────────────────╯ │
│ Locals │
│ ╭─────┬────────────────┬──────────────────────────┬────────────┬───────┬───────╮ │
│ │ i │ IDENTIFIER │ TYPE │ REGISTER │ SCOPE │MUTABLE│ │
│ ├─────┼────────────────┼──────────────────────────┼────────────┼───────┼───────┤ │
│ │ 0 │ i │ int │ R_INT_0 │ 0.0 │ true │ │
│ ╰─────┴────────────────┴──────────────────────────┴────────────┴───────┴───────╯ │
│ Constants │
│ ╭─────┬──────────────────────────┬──────────────────────────╮ │
│ │ i │ TYPE │ VALUE │ │
│ ├─────┼──────────────────────────┼──────────────────────────┤ │
│ │ 0 │ int │ 0 │ │
│ │ 1 │ str │ i │ │
│ │ 2 │ int │ 10 │ │
│ │ 3 │ int │ 1 │ │
│ ╰─────┴──────────────────────────┴──────────────────────────╯ │
╰──────────────────────────────────────────────────────────────────────────────────╯
```
</details>
The instruction notation reflects the Dust VM's register-based architecture. Values are referred to
by their address in the register or constant table. For example, `R_INT_42` refers to the
forty-second integer register, and `C_0` refers to the first constant.
```text
R_INT_0 = R_INT_0 + C_3
```
The info section for the ADD instruction shows what the instruction does: it adds the value at
`R_INT_0` to the value at `C_3` and stores the result in `R_INT_0`. As the "Constants" section
shows, `C_3` is the integer constant `1`. This means that this add instruction increments the value
in `R_INT_0` by `1`. In the "Locals" section, we can see that `R_INT_0` is the register used by the
`i` variable.
## Installation
Eventually, Dust should be available via package managers and as an embeddable library. For now,
@ -101,7 +256,7 @@ in scope.
*The Implementation of Lua 5.0*[^1] by Roberto Ierusalimschy, Luiz Henrique de Figueiredo, and
Waldemar Celes was a great resource for understanding register-based virtual machines and their
instructions. This paper was recommended by Bob Nystrom in [Crafting Interpreters].
instructions. This paper was recommended by Bob Nystrom in *Crafting Interpreters*.
*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

View File

@ -1,4 +1,4 @@
local i = 1
local i = 0
while i < 5000000 do
i = i + 1

View File

@ -1,4 +1,4 @@
i = 1
i = 0
while i < 5_000_000:
i += 1

View File

@ -1,4 +1,4 @@
#!/bin/sh
hyperfine \
--shell none \
--prepare 'sync' \
@ -9,5 +9,6 @@ hyperfine \
'bun addictive_addition.js' \
'python addictive_addition.py' \
'lua addictive_addition.lua' \
'luajit addictive_addition.lua' \
'ruby addictive_addition.rb' \
'java addictive_addition.java'

View File

@ -13,16 +13,17 @@ name = "dust"
path = "src/main.rs"
[dependencies]
clap = { version = "4.5.14", features = [
clap = { version = "4.5.29", features = [
"cargo",
"color",
"derive",
"help",
"wrap_help",
] }
color-print = "0.3.7"
dust-lang = { path = "../dust-lang" }
postcard = "1.0.10"
serde_json = "1.0.133"
postcard = "1.1.1"
ron = "0.8.1"
serde_json = "1.0.138"
serde_yaml = "0.9.34"
tracing = "0.1.41"
tracing-subscriber = "0.3.19"
tracing-subscriber = { version = "0.3.19", features = ["time"] }

View File

@ -9,66 +9,18 @@ use clap::{
builder::{styling::AnsiColor, Styles},
crate_authors, crate_description, crate_version,
error::ErrorKind,
Args, ColorChoice, Error, Parser, Subcommand, ValueHint,
Args, ColorChoice, Error, Parser, Subcommand, ValueEnum, ValueHint,
};
use color_print::{cformat, cstr};
use dust_lang::{CompileError, Compiler, DustError, DustString, Lexer, Span, Token, Vm};
use tracing::{subscriber::set_global_default, Level};
use tracing_subscriber::FmtSubscriber;
const ABOUT: &str = cstr!(
r#"
<bright-magenta,bold>Dust CLI
</>
{about}
<bold> Version:</> {version}
<bold>🦀 Author:</> {author}
<bold> License:</> GPL-3.0
<bold>🔬 Repository:</> https://git.jeffa.io/jeff/dust
"#
);
const PLAIN_ABOUT: &str = r#"
{about}
"#;
const USAGE: &str = cstr!(
r#"
<bright-magenta,bold>Usage:</> {usage}
"#
);
const SUBCOMMANDS: &str = cstr!(
r#"
<bright-magenta,bold>Modes:</>
{subcommands}
"#
);
const OPTIONS: &str = cstr!(
r#"
<bright-magenta,bold>Options:</>
{options}
"#
);
const CREATE_MAIN_HELP_TEMPLATE: fn() -> String =
|| cformat!("{ABOUT}{USAGE}{SUBCOMMANDS}{OPTIONS}");
const CREATE_MODE_HELP_TEMPLATE: fn(&str) -> String = |title| {
cformat!(
"\
<bright-magenta,bold>{title}\n</>\
{PLAIN_ABOUT}{USAGE}{OPTIONS}
"
)
};
use tracing_subscriber::{fmt::time::Uptime, FmtSubscriber};
const STYLES: Styles = Styles::styled()
.literal(AnsiColor::Cyan.on_default())
.placeholder(AnsiColor::Cyan.on_default())
.valid(AnsiColor::BrightCyan.on_default())
.header(AnsiColor::BrightMagenta.on_default().bold().underline())
.usage(AnsiColor::BrightMagenta.on_default().bold().underline())
.literal(AnsiColor::BrightCyan.on_default().bold())
.placeholder(AnsiColor::BrightCyan.on_default().bold())
.valid(AnsiColor::BrightGreen.on_default())
.invalid(AnsiColor::BrightYellow.on_default())
.error(AnsiColor::BrightRed.on_default());
@ -78,7 +30,6 @@ const STYLES: Styles = Styles::styled()
author = crate_authors!(),
about = crate_description!(),
color = ColorChoice::Auto,
help_template = CREATE_MAIN_HELP_TEMPLATE(),
styles = STYLES,
)]
struct Cli {
@ -98,17 +49,17 @@ struct Cli {
log_level: Option<Level>,
#[command(subcommand)]
mode: Option<Mode>,
mode: Option<Command>,
#[command(flatten)]
run: Run,
}
#[derive(Args)]
struct Input {
struct Source {
/// Source code to run instead of a file
#[arg(short, long, value_hint = ValueHint::Other, value_name = "INPUT")]
command: Option<String>,
eval: Option<String>,
/// Read source code from stdin
#[arg(long)]
@ -121,10 +72,7 @@ struct Input {
/// Compile and run the program (default)
#[derive(Args)]
#[command(
short_flag = 'r',
help_template = CREATE_MODE_HELP_TEMPLATE("Run Mode")
)]
#[command(short_flag = 'r')]
struct Run {
/// Print the time taken for compilation and execution
#[arg(long)]
@ -139,49 +87,66 @@ struct Run {
name: Option<DustString>,
#[command(flatten)]
input: Input,
source: Source,
/// Input format
#[arg(short, long, default_value = "dust")]
input: InputFormat,
}
#[derive(Subcommand)]
#[clap(subcommand_value_name = "MODE", flatten_help = true)]
enum Mode {
#[clap(subcommand_value_name = "COMMAND", flatten_help = true)]
enum Command {
Run(Run),
/// Compile and print the bytecode disassembly
#[command(
short_flag = 'd',
help_template = CREATE_MODE_HELP_TEMPLATE("Disassemble Mode")
)]
Disassemble {
/// Compile and print the input
#[command(short_flag = 'c')]
Compile {
/// Style disassembly output
#[arg(short, long, default_value = "true")]
style: bool,
/// Custom program name, overrides the file name
#[arg(long)]
#[arg(short, long)]
name: Option<DustString>,
#[command(flatten)]
input: Input,
source: Source,
#[arg(short, long, default_value = "cli", value_name = "FORMAT")]
output: OutputFormat,
},
/// Lex the source code and print the tokens
#[command(
short_flag = 't',
help_template = CREATE_MODE_HELP_TEMPLATE("Tokenize Mode")
)]
#[command(short_flag = 't')]
Tokenize {
/// Style token output
#[arg(short, long, default_value = "true")]
style: bool,
#[command(flatten)]
input: Input,
source: Source,
},
}
fn get_source_and_file_name(input: Input) -> (String, Option<DustString>) {
if let Some(path) = input.file {
#[derive(ValueEnum, Clone, Copy)]
enum OutputFormat {
Cli,
Json,
Ron,
Yaml,
}
#[derive(ValueEnum, Clone, Copy)]
enum InputFormat {
Dust,
Json,
Ron,
Yaml,
}
fn get_source_and_file_name(source: Source) -> (String, Option<DustString>) {
if let Some(path) = source.file {
let source = read_to_string(&path).expect("Failed to read source file");
let file_name = path
.file_name()
@ -191,7 +156,7 @@ fn get_source_and_file_name(input: Input) -> (String, Option<DustString>) {
return (source, file_name);
}
if input.stdin {
if source.stdin {
let mut source = String::new();
io::stdin()
.read_to_string(&mut source)
@ -200,7 +165,7 @@ fn get_source_and_file_name(input: Input) -> (String, Option<DustString>) {
return (source, None);
}
let source = input.command.expect("No source code provided");
let source = source.eval.expect("No source code provided");
(source, None)
}
@ -212,17 +177,93 @@ fn main() {
mode,
run,
} = Cli::parse();
let mode = mode.unwrap_or(Mode::Run(run));
let mode = mode.unwrap_or(Command::Run(run));
let subscriber = FmtSubscriber::builder()
.with_max_level(log_level)
.with_thread_names(true)
.with_ansi(true)
.with_file(false)
.with_line_number(false)
.with_thread_names(true)
.with_timer(Uptime::from(start_time))
.finish();
set_global_default(subscriber).expect("Failed to set tracing subscriber");
if let Mode::Disassemble { style, name, input } = mode {
let (source, file_name) = get_source_and_file_name(input);
if let Command::Run(Run {
time,
no_output,
name,
source,
input,
}) = mode
{
let (source, file_name) = get_source_and_file_name(source);
let lexer = Lexer::new(&source);
let program_name = name.or(file_name);
let chunk = match input {
InputFormat::Dust => {
let mut compiler = match Compiler::new(lexer, program_name, true) {
Ok(compiler) => compiler,
Err(error) => {
handle_compile_error(error, &source);
return;
}
};
match compiler.compile() {
Ok(()) => {}
Err(error) => {
handle_compile_error(error, &source);
return;
}
}
compiler.finish()
}
InputFormat::Json => {
serde_json::from_str(&source).expect("Failed to deserialize JSON into chunk")
}
InputFormat::Ron => {
ron::de::from_str(&source).expect("Failed to deserialize RON into chunk")
}
InputFormat::Yaml => {
serde_yaml::from_str(&source).expect("Failed to deserialize YAML into chunk")
}
};
let compile_end = start_time.elapsed();
let vm = Vm::new(chunk);
let return_value = vm.run();
let run_end = start_time.elapsed();
if !no_output {
if let Some(value) = return_value {
println!("{}", value)
}
}
if time {
let run_time = run_end - compile_end;
let total_time = compile_end + run_time;
print_time("Compile Time", compile_end);
print_time("Run Time", run_time);
print_time("Total Time", total_time);
}
return;
}
if let Command::Compile {
style,
name,
source,
output,
} = mode
{
let (source, file_name) = get_source_and_file_name(source);
let lexer = Lexer::new(&source);
let program_name = name.or(file_name);
let mut compiler = match Compiler::new(lexer, program_name, true) {
@ -246,6 +287,8 @@ fn main() {
let chunk = compiler.finish();
let mut stdout = stdout().lock();
match output {
OutputFormat::Cli => {
chunk
.disassembler(&mut stdout)
.width(65)
@ -253,12 +296,32 @@ fn main() {
.source(&source)
.disassemble()
.expect("Failed to write disassembly to stdout");
}
OutputFormat::Json => {
let json = serde_json::to_string_pretty(&chunk)
.expect("Failed to serialize chunk to JSON");
println!("{json}");
}
OutputFormat::Ron => {
let ron = ron::ser::to_string_pretty(&chunk, Default::default())
.expect("Failed to serialize chunk to RON");
println!("{ron}");
}
OutputFormat::Yaml => {
let yaml =
serde_yaml::to_string(&chunk).expect("Failed to serialize chunk to YAML");
println!("{yaml}");
}
}
return;
}
if let Mode::Tokenize { input, .. } = mode {
let (source, _) = get_source_and_file_name(input);
if let Command::Tokenize { source, .. } = mode {
let (source, _) = get_source_and_file_name(source);
let mut lexer = Lexer::new(&source);
let mut next_token = || -> Option<(Token, Span, bool)> {
match lexer.next_token() {
@ -299,59 +362,6 @@ fn main() {
println!("{token_kind:^21}|{token:^22}|{position:^22}");
}
return;
}
if let Mode::Run(Run {
time,
no_output,
name,
input,
}) = mode
{
let (source, file_name) = get_source_and_file_name(input);
let lexer = Lexer::new(&source);
let program_name = name.or(file_name);
let mut compiler = match Compiler::new(lexer, program_name, true) {
Ok(compiler) => compiler,
Err(error) => {
handle_compile_error(error, &source);
return;
}
};
match compiler.compile() {
Ok(()) => {}
Err(error) => {
handle_compile_error(error, &source);
return;
}
}
let chunk = compiler.finish();
let compile_end = start_time.elapsed();
let vm = Vm::new(chunk);
let return_value = vm.run();
let run_end = start_time.elapsed();
if let Some(value) = return_value {
if !no_output {
println!("{}", value)
}
}
if time {
let run_time = run_end - compile_end;
let total_time = compile_end + run_time;
print_time("Compile Time", compile_end);
print_time("Run Time", run_time);
print_time("Total Time", total_time);
}
}
}

View File

@ -9,11 +9,11 @@ repository.workspace = true
version.workspace = true
[dependencies]
annotate-snippets = "0.11.4"
colored = "2.1.0"
annotate-snippets = "0.11.5"
colored = "2.2.0"
rand = "0.8.5"
serde = { version = "1.0.203", features = ["derive", "rc"] }
serde_json = "1.0.117"
serde = { version = "1.0.217", features = ["derive", "rc"] }
serde_json = "1.0.138"
getrandom = { version = "0.2", features = [
"js",
] } # Indirect dependency, for WASM builds
@ -22,14 +22,27 @@ smartstring = { version = "1.0.1", features = [
], default-features = false }
tracing = "0.1.41"
crossbeam-channel = "0.5.14"
smallvec = { version = "1.14.0", features = ["serde", "const_generics"] }
[dev-dependencies]
criterion = { version = "0.3.4", features = ["html_reports"] }
criterion = { version = "0.5.1", features = ["html_reports"] }
[[bench]]
name = "addictive_addition"
harness = false
[[bench]]
name = "addictive_subtraction"
harness = false
[[bench]]
name = "addictive_multiplication"
harness = false
[[bench]]
name = "addictive_division"
harness = false
[[bench]]
name = "fibonacci"
harness = false
@ -39,77 +52,73 @@ name = "threads"
harness = false
[[test]]
name = "logic_and"
path = "tests/logic/and.rs"
[[test]]
name = "logic_and_and"
path = "tests/logic/and_and.rs"
[[test]]
name = "logic_or"
path = "tests/logic/or.rs"
[[test]]
name = "logic_variables"
path = "tests/logic/variables.rs"
[[test]]
name = "math_add"
name = "add"
path = "tests/math/add.rs"
[[test]]
name = "math_add_assign"
path = "tests/math/add_assign.rs"
[[test]]
name = "math_add_errors"
path = "tests/math/add_errors.rs"
[[test]]
name = "math_divide"
path = "tests/math/divide.rs"
[[test]]
name = "math_divide_assign"
path = "tests/math/divide_assign.rs"
[[test]]
name = "math_divide_erros"
path = "tests/math/divide_errors.rs"
[[test]]
name = "math_modulo"
path = "tests/math/modulo.rs"
[[test]]
name = "math_modulo_assign"
path = "tests/math/modulo_assign.rs"
[[test]]
name = "math_modulo_errors"
path = "tests/math/modulo_errors.rs"
[[test]]
name = "math_multiply"
path = "tests/math/multiply.rs"
[[test]]
name = "math_multiply_assign"
path = "tests/math/multiply_assign.rs"
[[test]]
name = "math_multiply_errors"
path = "tests/math/multiply_errors.rs"
[[test]]
name = "math_subtract"
name = "subtract"
path = "tests/math/subtract.rs"
[[test]]
name = "math_subtract_assign"
path = "tests/math/subtract_assign.rs"
name = "multiply"
path = "tests/math/multiply.rs"
[[test]]
name = "math_subtract_errors"
path = "tests/math/subtract_errors.rs"
name = "divide"
path = "tests/math/divide.rs"
[[test]]
name = "modulo"
path = "tests/math/modulo.rs"
[[test]]
name = "less"
path = "tests/comparison/less.rs"
[[test]]
name = "equal"
path = "tests/comparison/equal.rs"
[[test]]
name = "less_equal"
path = "tests/comparison/less_equal.rs"
[[test]]
name = "greater"
path = "tests/comparison/greater.rs"
[[test]]
name = "not_equal"
path = "tests/comparison/not_equal.rs"
[[test]]
name = "greater_equal"
path = "tests/comparison/greater_equal.rs"
[[test]]
name = "and"
path = "tests/logic/and.rs"
[[test]]
name = "or"
path = "tests/logic/or.rs"
[[test]]
name = "and_and"
path = "tests/logic/and_and.rs"
[[test]]
name = "or_or"
path = "tests/logic/or_or.rs"
[[test]]
name = "and_or"
path = "tests/logic/and_or.rs"
[[test]]
name = "or_and"
path = "tests/logic/or_and.rs"
[[test]]
name = "assignment_errors"
path = "tests/compiler_errors/assignment_errors.rs"

View File

@ -0,0 +1,29 @@
use std::time::Duration;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use dust_lang::run;
const SOURCE: &str = r"
let mut i = 1.7976931348623157e308
while i > 1.0 {
i /= 1.00014196662;
}
";
fn addictive_division(source: &str) {
run(source).unwrap();
}
fn criterion_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("addictive_division");
group.measurement_time(Duration::from_secs(15));
group.bench_function("addictive_division", |b| {
b.iter(|| addictive_division(black_box(SOURCE)))
});
group.finish();
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View File

@ -0,0 +1,29 @@
use std::time::Duration;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use dust_lang::run;
const SOURCE: &str = r"
let mut i = 1.0
while i < 1.7976931348623157e308 {
i *= 1.00014196662
}
";
fn addictive_multiplication(source: &str) {
run(source).unwrap();
}
fn criterion_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("addictive_multiplication");
group.measurement_time(Duration::from_secs(15));
group.bench_function("addictive_multiplication", |b| {
b.iter(|| addictive_multiplication(black_box(SOURCE)))
});
group.finish();
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View File

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

View File

@ -1,6 +1,6 @@
use std::time::Duration;
use criterion::{Criterion, black_box, criterion_group, criterion_main};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use dust_lang::run;
const SOURCE: &str = r#"
@ -9,9 +9,13 @@ const SOURCE: &str = r#"
while i < 1_000 {
i += 1
spawn(
fn () { random_int(0, 10); }
)
spawn(fn () {
let mut j = 0
while j < 5_000_000 {
j += 1
}
})
}
"#;

View File

@ -16,34 +16,32 @@
//! that implements the [Write][] trait.
//!
//! ```text
//! ╭─────────────────────────────────────────────────────────────────╮
//! │ dust │
//! │ │
//! │ write_line("Hello world!") │
//! ╭──────────────────────────────────────────────────────────────────────────────────╮
//! │ write_line("hello world") │
//! │ │
//! │ 3 instructions, 1 constants, 0 locals, returns none │
//! │ │
//! │ Instructions │
//! │ ╭─────┬────────────┬─────────────────┬────────────────────────╮ │
//! │ ╭─────┬────────────┬─────────────────┬─────────────────────────────────────────╮ │
//! │ │ i │ POSITION │ OPERATION │ INFO │ │
//! │ ├─────┼────────────┼─────────────────┼────────────────────────┤ │
//! │ │ 0 │ (11, 25) │ LOAD_CONSTANT │ R0 = C0 │ │
//! │ │ 1 │ (0, 26) │ CALL_NATIVE │ write_line(R0) │ │
//! │ │ 2 │ (26, 26) │ RETURN │ RETURN │ │
//! │ ╰─────┴────────────┴─────────────────┴────────────────────────╯ │
//! │ ├─────┼────────────┼─────────────────┼─────────────────────────────────────────┤ │
//! │ │ 0 │ (11, 24) │ LOAD_CONSTANT │ R_STR_0 = C0 │ │
//! │ │ 1 │ (0, 25) │ CALL_NATIVE │ write_line(R0) │ │
//! │ │ 2 │ (25, 25) │ RETURN │ RETURN │ │
//! │ ╰─────┴────────────┴─────────────────┴─────────────────────────────────────────╯ │
//! │ Constants │
//! │ ╭─────┬──────────────────────────┬──────────────────────────╮ │
//! │ │ i │ TYPE │ VALUE │ │
//! │ ├─────┼──────────────────────────┼──────────────────────────┤ │
//! │ │ 0 │ str │ Hello world! │
//! │ │ 0 │ str │ hello world │
//! │ ╰─────┴──────────────────────────┴──────────────────────────╯ │
//! ╰─────────────────────────────────────────────────────────────────
//! ╰──────────────────────────────────────────────────────────────────────────────────
//! ```
use std::io::{self, Write};
use colored::{ColoredString, Colorize};
use crate::{Chunk, Local};
use crate::{Chunk, Local, Type};
const INSTRUCTION_COLUMNS: [(&str, usize); 4] =
[("i", 5), ("POSITION", 12), ("OPERATION", 17), ("INFO", 41)];
@ -53,17 +51,18 @@ const INSTRUCTION_BORDERS: [&str; 3] = [
"╰─────┴────────────┴─────────────────┴─────────────────────────────────────────╯",
];
const LOCAL_COLUMNS: [(&str, usize); 5] = [
const LOCAL_COLUMNS: [(&str, usize); 6] = [
("i", 5),
("IDENTIFIER", 16),
("REGISTER", 10),
("TYPE", 26),
("REGISTER", 12),
("SCOPE", 7),
("MUTABLE", 7),
];
const LOCAL_BORDERS: [&str; 3] = [
"╭─────┬────────────────┬──────────┬───────┬───────╮",
"├─────┼────────────────┼──────────┼───────┼───────┤",
"╰─────┴────────────────┴──────────┴───────┴───────╯",
"╭─────┬────────────────┬──────────────────────────┬────────────┬───────┬───────╮",
"├─────┼────────────────┼──────────────────────────┼────────────┼───────┼───────┤",
"╰─────┴────────────────┴──────────────────────────┴────────────┴───────┴───────╯",
];
const CONSTANT_COLUMNS: [(&str, usize); 3] = [("i", 5), ("TYPE", 26), ("VALUE", 26)];
@ -314,6 +313,7 @@ impl<'a, W: Write> Disassembler<'a, W> {
Local {
identifier_index,
register_index,
r#type,
scope,
is_mutable,
},
@ -321,14 +321,16 @@ impl<'a, W: Write> Disassembler<'a, W> {
{
let identifier_display = self
.chunk
.constants
.string_constants
.get(*identifier_index as usize)
.map(|value| value.to_string())
.unwrap_or_else(|| "unknown".to_string());
let register_display = format!("R{register_index}");
let type_display = r#type.to_string();
let type_caps = type_display.to_uppercase();
let register_display = format!("R_{type_caps}_{register_index}");
let scope = scope.to_string();
let row = format!(
"│{index:^5}│{identifier_display:^16}│{register_display:^10}│{scope:^7}│{is_mutable:^7}│"
"│{index:^5}│{identifier_display:^16}│{type_display:^26}│{register_display:^12}│{scope:^7}│{is_mutable:^7}│"
);
self.write_center_border(&row)?;
@ -352,8 +354,56 @@ impl<'a, W: Write> Disassembler<'a, W> {
self.write_center_border(&column_name_line)?;
self.write_center_border(CONSTANT_BORDERS[1])?;
for (index, value) in self.chunk.constants.iter().enumerate() {
let type_display = value.r#type().to_string();
for (index, value) in self.chunk.character_constants.iter().enumerate() {
let type_display = Type::Character.to_string();
let value_display = {
let mut value_string = value.to_string();
if value_string.len() > 26 {
value_string = format!("{value_string:.23}...");
}
value_string
};
let constant_display = format!("{index:^5}{type_display:^26}{value_display:^26}");
self.write_center_border(&constant_display)?;
}
for (index, value) in self.chunk.float_constants.iter().enumerate() {
let type_display = Type::Float.to_string();
let value_display = {
let mut value_string = value.to_string();
if value_string.len() > 26 {
value_string = format!("{value_string:.23}...");
}
value_string
};
let constant_display = format!("{index:^5}{type_display:^26}{value_display:^26}");
self.write_center_border(&constant_display)?;
}
for (index, value) in self.chunk.integer_constants.iter().enumerate() {
let type_display = Type::Integer.to_string();
let value_display = {
let mut value_string = value.to_string();
if value_string.len() > 26 {
value_string = format!("{value_string:.23}...");
}
value_string
};
let constant_display = format!("{index:^5}{type_display:^26}{value_display:^26}");
self.write_center_border(&constant_display)?;
}
for (index, value) in self.chunk.string_constants.iter().enumerate() {
let type_display = Type::String.to_string();
let value_display = {
let mut value_string = value.to_string();
@ -374,7 +424,7 @@ impl<'a, W: Write> Disassembler<'a, W> {
}
pub fn write_prototype_section(&mut self) -> Result<(), io::Error> {
self.write_center_border_bold("Functions")?;
self.write_center_border_bold("Prototypes")?;
for chunk in &self.chunk.prototypes {
chunk
@ -407,7 +457,10 @@ impl<'a, W: Write> Disassembler<'a, W> {
if let Some(source) = self.source {
let lazily_formatted = source.split_whitespace().collect::<Vec<&str>>().join(" ");
if self.chunk.name.is_some() {
self.write_center_border("")?;
}
self.write_center_border(&lazily_formatted)?;
self.write_center_border("")?;
}
@ -415,7 +468,10 @@ impl<'a, W: Write> Disassembler<'a, W> {
let info_line = format!(
"{} instructions, {} constants, {} locals, returns {}",
self.chunk.instructions.len(),
self.chunk.constants.len(),
self.chunk.character_constants.len()
+ self.chunk.float_constants.len()
+ self.chunk.integer_constants.len()
+ self.chunk.string_constants.len(),
self.chunk.locals.len(),
self.chunk.r#type.return_type
);
@ -431,7 +487,11 @@ impl<'a, W: Write> Disassembler<'a, W> {
self.write_local_section()?;
}
if !self.chunk.constants.is_empty() {
if !self.chunk.character_constants.is_empty()
|| !self.chunk.float_constants.is_empty()
|| !self.chunk.integer_constants.is_empty()
|| !self.chunk.string_constants.is_empty()
{
self.write_constant_section()?;
}

View File

@ -2,7 +2,7 @@
use serde::{Deserialize, Serialize};
use crate::Scope;
use crate::{Scope, Type};
/// Scoped variable.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
@ -16,16 +16,26 @@ pub struct Local {
/// Whether the local is mutable.
pub is_mutable: bool,
/// Type of the variable's value.
pub r#type: Type,
/// Scope where the variable was declared.
pub scope: Scope,
}
impl Local {
/// Creates a new Local instance.
pub fn new(identifier_index: u16, register_index: u16, is_mutable: bool, scope: Scope) -> Self {
pub fn new(
identifier_index: u16,
register_index: u16,
r#type: Type,
is_mutable: bool,
scope: Scope,
) -> Self {
Self {
identifier_index,
register_index,
r#type,
is_mutable,
scope,
}

View File

@ -1,18 +1,15 @@
//! Representation of a Dust program or function.
//!
//! **Except for testing purposes, a chunk should not be created directly. Instead, use the compiler
//! to generate a chunk from Dust source code.**
//!
//! A chunk is output by the compiler to represent all the information needed to execute a Dust
//! program. In addition to the program itself, each function in the source is compiled into its own
//! chunk and stored in the `prototypes` field of its parent. Thus, a chunk can also represent a
//! function prototype.
//!
//! Chunks have a name when they belong to a named function. They also have a type, so the input
//! parameters and the type of the return value are statically known. The [`Chunk::stack_size`]
//! field can provide the necessary stack size that will be needed by the virtual machine. Chunks
//! cannot be instantiated directly and must be created by the compiler. However, when the Rust
//! compiler is in the "test" or "debug_assertions" configuration (used for all types of test),
//! [`Chunk::with_data`] can be used to create a chunk for comparison to the compiler output. Do not
//! try to run these chunks in a virtual machine. Due to their missing stack size and record index,
//! they will cause a panic or undefined behavior.
//! parameters and the type of the return value are statically known.
mod disassembler;
mod local;
mod scope;
@ -27,50 +24,37 @@ use std::sync::Arc;
use serde::{Deserialize, Serialize};
use crate::{DustString, Function, FunctionType, Instruction, Span, Value};
use crate::{DustString, Function, FunctionType, Instruction, Span};
/// Representation of a Dust program or function.
///
/// See the [module-level documentation](index.html) for more information.
#[derive(Clone, PartialOrd, Serialize, Deserialize)]
#[derive(Clone, Default, PartialOrd, Serialize, Deserialize)]
pub struct Chunk {
pub(crate) name: Option<DustString>,
pub(crate) r#type: FunctionType,
pub name: Option<DustString>,
pub r#type: FunctionType,
pub(crate) instructions: Vec<Instruction>,
pub(crate) positions: Vec<Span>,
pub(crate) constants: Vec<Value>,
pub(crate) locals: Vec<Local>,
pub(crate) prototypes: Vec<Arc<Chunk>>,
pub instructions: Vec<Instruction>,
pub positions: Vec<Span>,
pub character_constants: Vec<char>,
pub float_constants: Vec<f64>,
pub integer_constants: Vec<i64>,
pub string_constants: Vec<DustString>,
pub locals: Vec<Local>,
pub prototypes: Vec<Arc<Chunk>>,
pub(crate) register_count: usize,
pub(crate) prototype_index: u16,
pub boolean_register_count: u16,
pub byte_register_count: u16,
pub character_register_count: u16,
pub float_register_count: u16,
pub integer_register_count: u16,
pub string_register_count: u16,
pub list_register_count: u16,
pub function_register_count: u16,
pub prototype_index: u16,
}
impl Chunk {
#[cfg(any(test, debug_assertions))]
pub fn with_data(
name: Option<DustString>,
r#type: FunctionType,
instructions: impl Into<Vec<Instruction>>,
positions: impl Into<Vec<Span>>,
constants: impl Into<Vec<Value>>,
locals: impl Into<Vec<Local>>,
prototypes: impl IntoIterator<Item = Chunk>,
) -> Self {
Self {
name,
r#type,
instructions: instructions.into(),
positions: positions.into(),
constants: constants.into(),
locals: locals.into(),
prototypes: prototypes.into_iter().map(Arc::new).collect(),
register_count: 0,
prototype_index: 0,
}
}
pub fn as_function(&self) -> Function {
Function {
name: self.name.clone(),
@ -125,7 +109,11 @@ impl PartialEq for Chunk {
self.name == other.name
&& self.r#type == other.r#type
&& self.instructions == other.instructions
&& self.constants == other.constants
&& self.positions == other.positions
&& self.character_constants == other.character_constants
&& self.float_constants == other.float_constants
&& self.integer_constants == other.integer_constants
&& self.string_constants == other.string_constants
&& self.locals == other.locals
&& self.prototypes == other.prototypes
}

View File

@ -11,8 +11,7 @@ use serde::{Deserialize, Serialize};
/// The `block index` is a unique identifier for a block within a chunk. It is used to differentiate
/// between blocks that are not nested together but have the same depth, i.e. sibling scopes. If the
/// `block_index` is 0, then the scope is the root scope of the chunk. The `block_index` is always 0
/// when the `depth` is 0. See [Chunk::begin_scope][] and [Chunk::end_scope][] to see how scopes are
/// incremented and decremented.
/// when the `depth` is 0.
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Scope {
/// Level of block nesting.

View File

@ -181,7 +181,7 @@ impl AnnotatedError for CompileError {
fn description(&self) -> &'static str {
match self {
Self::CannotAddArguments { .. } => "Cannot add these types",
Self::CannotAddType { .. } => "Cannot add to this type",
Self::CannotAddType { .. } => "Cannot add this type",
Self::ComparisonChain { .. } => "Cannot chain comparison operations",
Self::CannotDivideArguments { .. } => "Cannot divide these types",
Self::CannotDivideType { .. } => "Cannot divide this type",
@ -222,6 +222,12 @@ impl AnnotatedError for CompileError {
fn detail_snippets(&self) -> Vec<(String, Span)> {
match self {
Self::CannotAddType {
argument_type,
position,
} => {
vec![(format!("Cannot add type `{}`", argument_type), *position)]
}
Self::CannotAddArguments {
left_type,
left_position,
@ -229,12 +235,9 @@ impl AnnotatedError for CompileError {
right_position,
} => {
vec![
(format!("`{left_type}` value was used here"), *left_position),
(
format!("A value of type \"{left_type}\" was used here."),
*left_position,
),
(
format!("A value of type \"{right_type}\" was used here."),
format!("`{right_type}` value was used here"),
*right_position,
),
]
@ -242,7 +245,7 @@ impl AnnotatedError for CompileError {
Self::ReturnTypeConflict { conflict, position } => {
vec![(
format!(
"Expected type {} but found type {}",
"Expected type `{}` but found type `{}`",
conflict.expected, conflict.actual
),
*position,
@ -262,7 +265,7 @@ impl AnnotatedError for CompileError {
} => {
vec![(
format!(
"Type \"{left_type}\" cannot be, added to type \"{right_type}\". Try converting one of the values to the other type."
"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),
)]

File diff suppressed because it is too large Load Diff

View File

@ -1,64 +0,0 @@
//! Functions used by the compiler to optimize a chunk's bytecode during compilation.
use tracing::debug;
use crate::{Compiler, Instruction, Operation};
/// Optimizes a control flow pattern to use fewer registers and avoid using a `POINT` instruction.
/// Use this after parsing an if/else statement.
///
/// This makes the following examples compile to the same bytecode:
///
/// ```dust
/// 4 == 4
/// ```
///
/// ```dust
/// if 4 == 4 { true } else { false }
/// ```
///
/// When they occur in the sequence shown below, instructions can be optimized by taking advantage
/// of the loaders' ability to skip an instruction after loading a value. If these instructions are
/// the result of a binary expression, this will not change anything because they were already
/// emitted optimally. Control flow patterns, however, can be optimized because the load
/// instructions are from seperate expressions that each uses its own register. Since only one of
/// the two branches will be executed, this is wasteful. It would also require the compiler to emit
/// a `POINT` instruction to prevent the VM from encountering an empty register.
///
/// The instructions must be in the following order:
/// - `TEST` or any of the `EQUAL`, `LESS` or `LESS_EQUAL` instructions
/// - `JUMP`
/// - `LOAD_BOOLEAN` or `LOAD_CONSTANT`
/// - `LOAD_BOOLEAN` or `LOAD_CONSTANT`
///
/// This optimization was taken from `A No-Frills Introduction to Lua 5.1 VM Instructions` by
/// Kein-Hong Man.
pub fn control_flow_register_consolidation(compiler: &mut Compiler) {
if !matches!(
compiler.get_last_operations(),
Some([
Operation::TEST | Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL,
Operation::JUMP,
Operation::LOAD_BOOLEAN | Operation::LOAD_CONSTANT,
Operation::LOAD_BOOLEAN | Operation::LOAD_CONSTANT,
])
) {
return;
}
debug!("Consolidating registers for control flow optimization");
let first_loader_index = compiler.instructions.len() - 2;
let (first_loader, _, _) = &mut compiler.instructions.get_mut(first_loader_index).unwrap();
let first_loader_destination = first_loader.a_field();
*first_loader =
Instruction::load_boolean(first_loader.a_field(), first_loader.b_field() != 0, true);
let second_loader_index = compiler.instructions.len() - 1;
let (second_loader, _, _) = &mut compiler.instructions.get_mut(second_loader_index).unwrap();
*second_loader = Instruction::load_boolean(
first_loader_destination,
second_loader.b_field() != 0,
false,
);
}

View File

@ -72,7 +72,7 @@ impl From<&Token<'_>> for ParseRule<'_> {
Token::DoubleAmpersand => ParseRule {
prefix: None,
infix: Some(Compiler::parse_logical_binary),
precedence: Precedence::LogicalAnd,
precedence: Precedence::Logic,
},
Token::DoubleEqual => ParseRule {
prefix: None,
@ -82,7 +82,7 @@ impl From<&Token<'_>> for ParseRule<'_> {
Token::DoublePipe => ParseRule {
prefix: None,
infix: Some(Compiler::parse_logical_binary),
precedence: Precedence::LogicalOr,
precedence: Precedence::Logic,
},
Token::DoubleDot => ParseRule {
prefix: Some(Compiler::expect_expression),
@ -284,14 +284,13 @@ impl From<&Token<'_>> for ParseRule<'_> {
/// Operator precedence levels.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Precedence {
Primary = 9,
Call = 8,
Unary = 7,
Factor = 6,
Term = 5,
Comparison = 4,
LogicalAnd = 3,
LogicalOr = 2,
Primary = 8,
Call = 7,
Unary = 6,
Factor = 5,
Term = 4,
Comparison = 3,
Logic = 2,
Assignment = 1,
None = 0,
}
@ -300,9 +299,8 @@ impl Precedence {
pub fn increment(&self) -> Self {
match self {
Precedence::None => Precedence::Assignment,
Precedence::Assignment => Precedence::LogicalOr,
Precedence::LogicalOr => Precedence::LogicalAnd,
Precedence::LogicalAnd => Precedence::Comparison,
Precedence::Assignment => Precedence::Logic,
Precedence::Logic => Precedence::Comparison,
Precedence::Comparison => Precedence::Term,
Precedence::Term => Precedence::Factor,
Precedence::Factor => Precedence::Unary,

View File

@ -1,28 +1,22 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
use super::{Instruction, InstructionFields, Operand, Operation, TypeCode};
pub struct Add {
pub destination: u16,
pub left: Operand,
pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
}
impl From<Instruction> for Add {
fn from(instruction: Instruction) -> Self {
impl From<&Instruction> for Add {
fn from(instruction: &Instruction) -> Self {
let destination = instruction.a_field();
let (left, right) = instruction.b_and_c_as_operands();
let left_type = instruction.b_type();
let right_type = instruction.c_type();
Add {
destination,
left,
left_type,
right,
right_type,
}
}
}
@ -33,10 +27,10 @@ impl From<Add> for Instruction {
let a_field = add.destination;
let (b_field, b_is_constant) = add.left.as_index_and_constant_flag();
let (c_field, c_is_constant) = add.right.as_index_and_constant_flag();
let b_type = add.left_type;
let c_type = add.right_type;
let b_type = add.left.as_type();
let c_type = add.right.as_type();
InstructionBuilder {
InstructionFields {
operation,
a_field,
b_field,
@ -56,14 +50,19 @@ impl Display for Add {
let Add {
destination,
left,
left_type,
right,
right_type,
} = self;
write!(
f,
"R{destination} = {left}({left_type}) + {right}({right_type})",
)
match left.as_type() {
TypeCode::BOOLEAN => write!(f, "R_BOOL_{destination}")?,
TypeCode::BYTE => write!(f, "R_BYTE_{destination}")?,
TypeCode::CHARACTER => write!(f, "R_STR_{destination}")?,
TypeCode::FLOAT => write!(f, "R_FLOAT_{destination}")?,
TypeCode::INTEGER => write!(f, "R_INT_{destination}")?,
TypeCode::STRING => write!(f, "R_STR_{destination}")?,
_ => todo!(),
}
write!(f, " = {left} + {right}",)
}
}

View File

@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
use super::InstructionFields;
pub struct Call {
pub destination: u16,
@ -34,7 +34,7 @@ impl From<Call> for Instruction {
let c_field = call.argument_count;
let d_field = call.is_recursive;
InstructionBuilder {
InstructionFields {
operation: Operation::CALL,
a_field,
b_field,

View File

@ -1,24 +1,25 @@
use std::fmt::Display;
use crate::{Instruction, NativeFunction, Operation};
use crate::{Instruction, NativeFunction, Operation, Type};
use super::InstructionBuilder;
use super::InstructionFields;
pub struct CallNative {
pub destination: u16,
pub function: NativeFunction,
pub argument_count: u16,
pub first_argument_index: u16,
}
impl From<Instruction> for CallNative {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let function = NativeFunction::from(instruction.b_field());
let first_argument_index = instruction.c_field();
CallNative {
destination,
function,
argument_count: instruction.c_field(),
first_argument_index,
}
}
}
@ -28,9 +29,9 @@ impl From<CallNative> for Instruction {
let operation = Operation::CALL_NATIVE;
let a_field = call_native.destination;
let b_field = call_native.function as u16;
let c_field = call_native.argument_count;
let c_field = call_native.first_argument_index;
InstructionBuilder {
InstructionFields {
operation,
a_field,
b_field,
@ -46,21 +47,57 @@ impl Display for CallNative {
let CallNative {
destination,
function,
argument_count,
first_argument_index,
} = self;
let arguments_start = destination.saturating_sub(*argument_count);
let arguments_end = arguments_start + argument_count;
let argument_count = function.r#type().value_parameters.len() as u16;
if function.returns_value() {
write!(f, "R{destination} = ")?;
}
write!(f, "{function}")?;
match argument_count {
0 => {
write!(f, "{function}()")
write!(f, "()")
}
_ => {
let arguments_end = first_argument_index + argument_count - 1;
let arguments_index_range = *first_argument_index..=arguments_end;
let function_type = function.r#type();
let argument_types = function_type.value_parameters.iter();
write!(f, "(")?;
for (index, r#type) in arguments_index_range.zip(argument_types) {
match r#type {
Type::Boolean => {
write!(f, "R_BOOL_{index}")
}
Type::Byte => {
write!(f, "R_BYTE_{index}")
}
Type::Float => {
write!(f, "R_FLOAT_{index}")
}
Type::Integer => {
write!(f, "R_INT_{index}")
}
Type::String => {
write!(f, "R_STR_{index}")
}
unsupported => {
todo!("Support for {unsupported:?} arguments")
}
}?;
if index != arguments_end {
write!(f, ", ")?;
}
}
write!(f, ")")
}
1 => write!(f, "{function}(R{arguments_start})"),
_ => write!(f, "{function}(R{arguments_start}..R{arguments_end})"),
}
}
}

View File

@ -2,18 +2,20 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
use super::{InstructionFields, TypeCode};
pub struct Close {
pub from: u16,
pub to: u16,
pub r#type: TypeCode,
}
impl From<Instruction> for Close {
fn from(instruction: Instruction) -> Self {
impl From<&Instruction> for Close {
fn from(instruction: &Instruction) -> Self {
Close {
from: instruction.b_field(),
to: instruction.c_field(),
r#type: instruction.b_type(),
}
}
}
@ -22,11 +24,13 @@ impl From<Close> for Instruction {
fn from(close: Close) -> Self {
let operation = Operation::CLOSE;
let b_field = close.from;
let b_type = close.r#type;
let c_field = close.to;
InstructionBuilder {
InstructionFields {
operation,
b_field,
b_type,
c_field,
..Default::default()
}
@ -36,8 +40,17 @@ impl From<Close> for Instruction {
impl Display for Close {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let Close { from, to } = self;
let Close { from, to, r#type } = self;
write!(f, "{from}..={to}")
match *r#type {
TypeCode::BOOLEAN => write!(f, "R_BOOL_{from}..=R_BOOL_{to}"),
TypeCode::BYTE => write!(f, "R_BYTE_{from}..=R_BYTE_{to}"),
TypeCode::CHARACTER => write!(f, "R_CHAR_{from}..=R_CHAR_{to}"),
TypeCode::FLOAT => write!(f, "R_FLOAT_{from}..=R_FLOAT_{to}"),
TypeCode::INTEGER => write!(f, "R_INT_{from}..=R_INT_{to}"),
TypeCode::STRING => write!(f, "R_STR_{from}..=R_STR_{to}"),
TypeCode::LIST => write!(f, "R_LIST_{from}..=R_LIST_{to}"),
unsupported => panic!("Unsupported type code: {:?}", unsupported),
}
}
}

View File

@ -1,28 +1,22 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
use super::{Instruction, InstructionFields, Operand, Operation, TypeCode};
pub struct Divide {
pub destination: u16,
pub left: Operand,
pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
}
impl From<Instruction> for Divide {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let (left, right) = instruction.b_and_c_as_operands();
let left_type = instruction.b_type();
let right_type = instruction.c_type();
Divide {
destination,
left,
left_type,
right,
right_type,
}
}
}
@ -33,10 +27,10 @@ impl From<Divide> for Instruction {
let a_field = divide.destination;
let (b_field, b_is_constant) = divide.left.as_index_and_constant_flag();
let (c_field, c_is_constant) = divide.right.as_index_and_constant_flag();
let b_type = divide.left_type;
let c_type = divide.right_type;
let b_type = divide.left.as_type();
let c_type = divide.right.as_type();
InstructionBuilder {
InstructionFields {
operation,
a_field,
b_field,
@ -56,14 +50,19 @@ impl Display for Divide {
let Divide {
destination,
left,
left_type,
right,
right_type,
} = self;
write!(
f,
"R{destination} = {left_type}({left}) ÷ {right_type}({right})",
)
match left.as_type() {
TypeCode::BOOLEAN => write!(f, "R_BOOL_{destination}")?,
TypeCode::BYTE => write!(f, "R_BYTE_{destination}")?,
TypeCode::CHARACTER => write!(f, "R_STR_{destination}")?,
TypeCode::FLOAT => write!(f, "R_FLOAT_{destination}")?,
TypeCode::INTEGER => write!(f, "R_INT_{destination}")?,
TypeCode::STRING => write!(f, "R_STR_{destination}")?,
_ => todo!(),
}
write!(f, " = {left} ÷ {right}",)
}
}

View File

@ -1,28 +1,22 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
use super::{Instruction, InstructionFields, Operand, Operation};
pub struct Equal {
pub comparator: bool,
pub left: Operand,
pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
}
impl From<Instruction> for Equal {
fn from(instruction: Instruction) -> Self {
let comparator = instruction.d_field();
let (left, right) = instruction.b_and_c_as_operands();
let left_type = instruction.b_type();
let right_type = instruction.c_type();
Equal {
comparator,
left,
left_type,
right,
right_type,
}
}
}
@ -33,10 +27,10 @@ impl From<Equal> for Instruction {
let (b_field, b_is_constant) = equal_bool.left.as_index_and_constant_flag();
let (c_field, c_is_constant) = equal_bool.right.as_index_and_constant_flag();
let d_field = equal_bool.comparator;
let b_type = equal_bool.left_type;
let c_type = equal_bool.right_type;
let b_type = equal_bool.left.as_type();
let c_type = equal_bool.right.as_type();
InstructionBuilder {
InstructionFields {
operation,
b_field,
c_field,
@ -56,15 +50,10 @@ impl Display for Equal {
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 }}"
)
write!(f, "if {left} {operator} {right} {{ JUMP +1 }}")
}
}

View File

@ -1,49 +0,0 @@
use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct GetLocal {
pub destination: u16,
pub local_index: u16,
}
impl From<Instruction> for GetLocal {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let local_index = instruction.b_field();
GetLocal {
destination,
local_index,
}
}
}
impl From<GetLocal> for Instruction {
fn from(get_local: GetLocal) -> Self {
let operation = Operation::GET_LOCAL;
let a_field = get_local.destination;
let b_field = get_local.local_index;
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

@ -2,15 +2,15 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
use super::InstructionFields;
pub struct Jump {
pub offset: u16,
pub is_positive: bool,
}
impl From<Instruction> for Jump {
fn from(instruction: Instruction) -> Self {
impl From<&Instruction> for Jump {
fn from(instruction: &Instruction) -> Self {
Jump {
offset: instruction.b_field(),
is_positive: instruction.c_field() != 0,
@ -24,7 +24,7 @@ impl From<Jump> for Instruction {
let b_field = jump.offset;
let c_field = jump.is_positive as u16;
InstructionBuilder {
InstructionFields {
operation,
b_field,
c_field,

View File

@ -1,28 +1,22 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
use super::{Instruction, InstructionFields, Operand, Operation};
pub struct Less {
pub comparator: bool,
pub left: Operand,
pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
}
impl From<Instruction> for Less {
fn from(instruction: Instruction) -> Self {
let comparator = instruction.d_field();
let (left, right) = instruction.b_and_c_as_operands();
let left_type = instruction.b_type();
let right_type = instruction.c_type();
Less {
comparator,
left,
left_type,
right,
right_type,
}
}
}
@ -33,10 +27,10 @@ impl From<Less> for Instruction {
let (b_field, b_is_constant) = less.left.as_index_and_constant_flag();
let (c_field, c_is_constant) = less.right.as_index_and_constant_flag();
let d_field = less.comparator;
let b_type = less.left_type;
let c_type = less.right_type;
let b_type = less.left.as_type();
let c_type = less.right.as_type();
InstructionBuilder {
InstructionFields {
operation,
b_field,
c_field,
@ -56,15 +50,10 @@ impl Display for Less {
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 }}"
)
write!(f, "if {left} {operator} {right} {{ JUMP +1 }}")
}
}

View File

@ -1,28 +1,22 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
use super::{Instruction, InstructionFields, Operand, Operation};
pub struct LessEqual {
pub comparator: bool,
pub left: Operand,
pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
}
impl From<Instruction> for LessEqual {
fn from(instruction: Instruction) -> Self {
let comparator = instruction.d_field();
let (left, right) = instruction.b_and_c_as_operands();
let left_type = instruction.b_type();
let right_type = instruction.c_type();
LessEqual {
comparator,
left,
left_type,
right,
right_type,
}
}
}
@ -33,10 +27,10 @@ impl From<LessEqual> for Instruction {
let (b_field, b_is_constant) = less_equal_byte.left.as_index_and_constant_flag();
let (c_field, c_is_constant) = less_equal_byte.right.as_index_and_constant_flag();
let d_field = less_equal_byte.comparator;
let b_type = less_equal_byte.left_type;
let c_type = less_equal_byte.right_type;
let b_type = less_equal_byte.left.as_type();
let c_type = less_equal_byte.right.as_type();
InstructionBuilder {
InstructionFields {
operation,
b_field,
c_field,
@ -56,15 +50,10 @@ impl Display for LessEqual {
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 }}"
)
write!(f, "if {left} {operator} {right} {{ JUMP +1 }}")
}
}

View File

@ -1,57 +0,0 @@
use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct LoadBoolean {
pub destination: u16,
pub value: bool,
pub jump_next: bool,
}
impl From<Instruction> for LoadBoolean {
fn from(instruction: Instruction) -> Self {
LoadBoolean {
destination: instruction.a_field(),
value: instruction.b_field() != 0,
jump_next: instruction.c_field() != 0,
}
}
}
impl From<LoadBoolean> for Instruction {
fn from(load_boolean: LoadBoolean) -> Self {
let operation = Operation::LOAD_BOOLEAN;
let a_field = load_boolean.destination;
let b_field = load_boolean.value as u16;
let c_field = load_boolean.jump_next as u16;
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,11 +2,12 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
use super::{InstructionFields, TypeCode};
pub struct LoadConstant {
pub destination: u16,
pub constant_index: u16,
pub constant_type: TypeCode,
pub jump_next: bool,
}
@ -14,11 +15,13 @@ impl From<Instruction> for LoadConstant {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let constant_index = instruction.b_field();
let constant_type = instruction.b_type();
let jump_next = instruction.c_field() != 0;
LoadConstant {
destination,
constant_index,
constant_type,
jump_next,
}
}
@ -26,10 +29,11 @@ impl From<Instruction> for LoadConstant {
impl From<LoadConstant> for Instruction {
fn from(load_constant: LoadConstant) -> Self {
InstructionBuilder {
InstructionFields {
operation: Operation::LOAD_CONSTANT,
a_field: load_constant.destination,
b_field: load_constant.constant_index,
b_type: load_constant.constant_type,
c_field: load_constant.jump_next as u16,
..Default::default()
}
@ -42,10 +46,21 @@ impl Display for LoadConstant {
let LoadConstant {
destination,
constant_index,
constant_type,
jump_next,
} = self;
write!(f, "R{destination} = C{constant_index}")?;
match *constant_type {
TypeCode::BOOLEAN => write!(f, "R_BOOL_{destination}")?,
TypeCode::BYTE => write!(f, "R_BYTE_{destination}")?,
TypeCode::CHARACTER => write!(f, "R_CHAR_{destination}")?,
TypeCode::FLOAT => write!(f, "R_FLOAT_{destination}")?,
TypeCode::INTEGER => write!(f, "R_INT_{destination}")?,
TypeCode::STRING => write!(f, "R_STR_{destination}")?,
unsupported => panic!("Unsupported type code: {}", unsupported.0),
}
write!(f, " = C{constant_index}")?;
if *jump_next {
write!(f, " JUMP +1")?;

View File

@ -0,0 +1,70 @@
use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::{InstructionFields, TypeCode};
pub struct LoadEncoded {
pub destination: u16,
pub value: u8,
pub value_type: TypeCode,
pub jump_next: bool,
}
impl From<Instruction> for LoadEncoded {
fn from(instruction: Instruction) -> Self {
LoadEncoded {
destination: instruction.a_field(),
value: instruction.b_field() as u8,
value_type: instruction.b_type(),
jump_next: instruction.c_field() != 0,
}
}
}
impl From<LoadEncoded> for Instruction {
fn from(load_boolean: LoadEncoded) -> Self {
let operation = Operation::LOAD_ENCODED;
let a_field = load_boolean.destination;
let b_field = load_boolean.value as u16;
let b_type = load_boolean.value_type;
let c_field = load_boolean.jump_next as u16;
InstructionFields {
operation,
a_field,
b_field,
b_type,
c_field,
..Default::default()
}
.build()
}
}
impl Display for LoadEncoded {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let LoadEncoded {
destination,
value,
value_type,
jump_next,
} = self;
match *value_type {
TypeCode::BOOLEAN => {
let boolean = *value != 0;
write!(f, "R_BOOL_{destination} = {boolean}")?
}
TypeCode::BYTE => write!(f, "R_BYTE_{destination} = 0x{value:0X}")?,
_ => panic!("Invalid type code {value_type} for LoadEncoded instruction"),
}
if *jump_next {
write!(f, " JUMP +1")?;
}
Ok(())
}
}

View File

@ -1,6 +1,6 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operation};
use super::{Instruction, InstructionFields, Operation};
pub struct LoadFunction {
pub destination: u16,
@ -24,7 +24,7 @@ impl From<Instruction> for LoadFunction {
impl From<LoadFunction> for Instruction {
fn from(load_function: LoadFunction) -> Self {
InstructionBuilder {
InstructionFields {
operation: Operation::LOAD_FUNCTION,
a_field: load_function.destination,
b_field: load_function.prototype_index,

View File

@ -2,11 +2,13 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
use super::{InstructionFields, TypeCode};
pub struct LoadList {
pub destination: u16,
pub item_type: TypeCode,
pub start_register: u16,
pub end_register: u16,
pub jump_next: bool,
}
@ -14,11 +16,15 @@ impl From<Instruction> for LoadList {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let start_register = instruction.b_field();
let jump_next = instruction.c_field() != 0;
let item_type = instruction.b_type();
let end_register = instruction.c_field();
let jump_next = instruction.d_field();
LoadList {
destination,
item_type,
start_register,
end_register,
jump_next,
}
}
@ -26,11 +32,13 @@ impl From<Instruction> for LoadList {
impl From<LoadList> for Instruction {
fn from(load_list: LoadList) -> Self {
InstructionBuilder {
InstructionFields {
operation: Operation::LOAD_LIST,
a_field: load_list.destination,
b_field: load_list.start_register,
c_field: load_list.jump_next as u16,
b_type: load_list.item_type,
c_field: load_list.end_register,
d_field: load_list.jump_next,
..Default::default()
}
.build()
@ -41,11 +49,43 @@ impl Display for LoadList {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let LoadList {
destination,
item_type,
start_register,
end_register,
jump_next,
} = self;
write!(f, "R{destination} = [R{start_register}..R{destination}]")?;
write!(f, "R_LIST_{destination} = [")?;
match *item_type {
TypeCode::BOOLEAN => {
write!(f, "R_BOOL_{start_register}..=R_BOOL_{end_register}")?;
}
TypeCode::BYTE => {
write!(f, "R_BYTE_{start_register}..=R_BYTE_{end_register}")?;
}
TypeCode::CHARACTER => {
write!(f, "R_CHAR_{start_register}..=R_CHAR_{end_register}")?;
}
TypeCode::FLOAT => {
write!(f, "R_FLOAT_{start_register}..=R_FLOAT_{end_register}")?;
}
TypeCode::INTEGER => {
write!(f, "R_INT_{start_register}..=R_INT_{end_register}")?;
}
TypeCode::STRING => {
write!(f, "R_STR_{start_register}..=R_STR_{end_register}")?;
}
TypeCode::LIST => {
write!(f, "R_LIST_{start_register}..=R_LIST_{end_register}")?;
}
TypeCode::FUNCTION => {
write!(f, "R_FN_{start_register}..=R_FN_{end_register}")?;
}
unknown => panic!("Unknown type code: {}", unknown.0),
}
write!(f, "]")?;
if *jump_next {
write!(f, " JUMP +1")?;

View File

@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
use super::InstructionFields;
pub struct LoadSelf {
pub destination: u16,
@ -23,7 +23,7 @@ impl From<Instruction> for LoadSelf {
impl From<LoadSelf> for Instruction {
fn from(load_self: LoadSelf) -> Self {
InstructionBuilder {
InstructionFields {
operation: Operation::LOAD_SELF,
a_field: load_self.destination,
c_field: load_self.jump_next as u16,

View File

@ -1,59 +1,45 @@
//! The Dust instruction set.
//!
//! Each instruction is 64 bits and uses up to seven distinct fields.
//! Each instruction is 64 bits and uses up to nine distinct fields.
//!
//! # Layout
//!
//! Bits | Description
//! ----- | -----------
//! 0-6 | Operation
//! 7 | Flag indicating if the B field is a constant
//! 8 | Flag indicating if the C field is a constant
//! 9 | D field (boolean)
//! 10-12 | B field type
//! 13-15 | C field type
//! 0-4 | Operation
//! 5-8 | B field type
//! 9 | Flag indicating if the B field is a constant
//! 10-13 | C field type
//! 14 | Flag indicating if the C field is a constant
//! 15 | D field (boolean)
//! 16-31 | A field (unsigned 16-bit integer)
//! 32-47 | B field (unsigned 16-bit integer)
//! 48-63 | C field (unsigned 16-bit integer)
//!
//! **Be careful when working with instructions directly**. When modifying an instruction's fields,
//! you may also need to modify its flags. It is usually best to remove instructions and insert new
//! ones in their place instead of mutating them.
//!
//! # Examples
//!
//! ## Creating Instructions
//! # Creating Instructions
//!
//! For each operation, there are two ways to create an instruction:
//!
//! - Use the associated function on `Instruction`
//! - Use the corresponding struct and call `Instruction::from`
//!
//! Both produce the same result, but the first is more concise. The structs are more useful when
//! reading instructions, as shown below.
//! Both produce the same result, but the first is usuall more concise. The structs are more useful
//! when reading instructions, as shown below.
//!
//! Use the [`Operand`][] type when creating instructions. In addition to being easy to read and
//! write, this ensures that the instruction has the correct flags to represent the operands.
//!
//! ```
//! # use dust_lang::instruction::{Instruction, Move};
//! let move_1 = Instruction::r#move(42, 4);
//! let move_2 = Instruction::from(Move { from: 42, to: 4 });
//!
//! assert_eq!(move_1, move_2);
//! ```
//!
//! Use the [`Argument`][] type when creating instructions. In addition to being easy to read and
//! write, this ensures that the instruction has the correct flags to represent the arguments.
//!
//! ```
//! # use dust_lang::instruction::{Instruction, Add, Argument};
//! # use dust_lang::instruction::{Instruction, Add, Operand, TypeCode};
//! let add_1 = Instruction::add(
//! 0,
//! Argument::Register(1),
//! Argument::Constant(2)
//! Operand::Register(1, TypeCode::INTEGER),
//! Operand::Constant(2, TypeCode::INTEGER)
//! );
//! let add_2 = Instruction::from(Add {
//! destination: 0,
//! left: Argument::Register(1),
//! right: Argument::Constant(2),
//! left: Operand::Register(1, TypeCode::INTEGER),
//! right: Operand::Constant(2, TypeCode::INTEGER),
//! });
//!
//! assert_eq!(add_1, add_2);
@ -64,28 +50,27 @@
//! To read an instruction, check its operation with [`Instruction::operation`], then convert the
//! instruction to the struct that corresponds to that operation. Like the example above, this
//! removes the burden of dealing with the options directly and automatically casts the A, B, C and
//! D fields as `u16`, `bool` or `Argument` values.
//! D fields as `u16`, `bool` or `Operand` values.
//!
//! ```
//! # use dust_lang::instruction::{Instruction, Add, Argument, Operation};
//! # use dust_lang::instruction::{Instruction, Add, Operand, Operation, TypeCode};
//! # let mystery_instruction = Instruction::add(
//! # 1,
//! # Argument::Register(1),
//! # Argument::Constant(2)
//! # Operand::Register(1, TypeCode::INTEGER),
//! # Operand::Constant(2, TypeCode::INTEGER)
//! # );
//! // Let's read an instruction and see if it performs addition-assignment,
//! // like in one of the following examples:
//! // - `a += 2`
//! // - `a = a + 2`
//! // - `a = 2 + a`
//!
//! let operation = mystery_instruction.operation();
//! let is_add_assign = match operation {
//! Operation::Add => {
//! Operation::ADD => {
//! let Add { destination, left, right } = Add::from(&mystery_instruction);
//!
//! left == Argument::Register(destination)
//! || right == Argument::Register(destination);
//! left == Operand::Register(destination, TypeCode::INTEGER)
//! || right == Operand::Register(destination, TypeCode::INTEGER)
//!
//! }
//! _ => false,
@ -99,23 +84,21 @@ mod call_native;
mod close;
mod divide;
mod equal;
mod get_local;
mod jump;
mod less;
mod less_equal;
mod load_boolean;
mod load_constant;
mod load_encoded;
mod load_function;
mod load_list;
mod load_self;
mod modulo;
mod r#move;
mod multiply;
mod negate;
mod not;
mod operation;
mod point;
mod r#return;
mod set_local;
mod subtract;
mod test;
mod test_set;
@ -127,12 +110,11 @@ pub use call_native::CallNative;
pub use close::Close;
pub use divide::Divide;
pub use equal::Equal;
pub use get_local::GetLocal;
pub use jump::Jump;
pub use less::Less;
pub use less_equal::LessEqual;
pub use load_boolean::LoadBoolean;
pub use load_constant::LoadConstant;
pub use load_encoded::LoadEncoded;
pub use load_function::LoadFunction;
pub use load_list::LoadList;
pub use load_self::LoadSelf;
@ -141,9 +123,8 @@ pub use multiply::Multiply;
pub use negate::Negate;
pub use not::Not;
pub use operation::Operation;
pub use point::Point;
pub use r#move::Move;
pub use r#return::Return;
pub use set_local::SetLocal;
pub use subtract::Subtract;
pub use test::Test;
pub use test_set::TestSet;
@ -154,7 +135,8 @@ pub use type_code::TypeCode;
use crate::NativeFunction;
pub struct InstructionBuilder {
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct InstructionFields {
pub operation: Operation,
pub a_field: u16,
pub b_field: u16,
@ -166,14 +148,14 @@ pub struct InstructionBuilder {
pub c_type: TypeCode,
}
impl InstructionBuilder {
impl InstructionFields {
pub fn build(self) -> Instruction {
let bits = ((self.operation.0 as u64) << 57)
| ((self.b_is_constant as u64) << 56)
| ((self.c_is_constant as u64) << 55)
| ((self.d_field as u64) << 54)
| ((self.b_type.0 as u64) << 51)
| ((self.c_type.0 as u64) << 48)
let bits = ((self.operation.0 as u64) << 59)
| ((self.b_type.0 as u64) << 54)
| ((self.b_is_constant as u64) << 53)
| ((self.c_type.0 as u64) << 49)
| ((self.c_is_constant as u64) << 48)
| ((self.d_field as u64) << 47)
| ((self.a_field as u64) << 32)
| ((self.b_field as u64) << 16)
| (self.c_field as u64);
@ -182,10 +164,26 @@ impl InstructionBuilder {
}
}
impl Default for InstructionBuilder {
impl From<&Instruction> for InstructionFields {
fn from(instruction: &Instruction) -> Self {
InstructionFields {
operation: instruction.operation(),
a_field: instruction.a_field(),
b_field: instruction.b_field(),
c_field: instruction.c_field(),
d_field: instruction.d_field(),
b_is_constant: instruction.b_is_constant(),
c_is_constant: instruction.c_is_constant(),
b_type: instruction.b_type(),
c_type: instruction.c_type(),
}
}
}
impl Default for InstructionFields {
fn default() -> Self {
InstructionBuilder {
operation: Operation::POINT,
InstructionFields {
operation: Operation::MOVE,
a_field: 0,
b_field: 0,
c_field: 0,
@ -206,79 +204,207 @@ pub struct Instruction(u64);
impl Instruction {
pub fn operation(&self) -> Operation {
let first_7_bits = (self.0 >> 57) as u8;
let first_5_bits = (self.0 >> 59) as u8;
Operation(first_7_bits)
}
pub fn b_is_constant(&self) -> bool {
(self.0 >> 56) & 1 != 0
}
pub fn c_is_constant(&self) -> bool {
(self.0 >> 55) & 1 != 0
}
pub fn d_field(&self) -> bool {
(self.0 >> 54) & 1 != 0
Operation(first_5_bits)
}
pub fn b_type(&self) -> TypeCode {
let byte = ((self.0 >> 51) & 0b111) as u8;
let bits_5_to_8 = (self.0 >> 54) & 0b1111;
TypeCode(byte)
TypeCode(bits_5_to_8 as u8)
}
pub fn b_is_constant(&self) -> bool {
let bit_9 = (self.0 >> 53) & 1;
bit_9 != 0
}
pub fn c_type(&self) -> TypeCode {
let byte = ((self.0 >> 48) & 0b111) as u8;
let bits_10_to_13 = (self.0 >> 49) & 0b1111;
TypeCode(byte)
TypeCode(bits_10_to_13 as u8)
}
pub fn c_is_constant(&self) -> bool {
let bit_14 = (self.0 >> 48) & 1;
bit_14 != 0
}
pub fn d_field(&self) -> bool {
let bit_15 = (self.0 >> 47) & 1;
bit_15 != 0
}
pub fn a_field(&self) -> u16 {
((self.0 >> 32) & 0xFFFF) as u16
let bits_16_to_31 = (self.0 >> 32) & 0xFFFF;
bits_16_to_31 as u16
}
pub fn b_field(&self) -> u16 {
((self.0 >> 16) & 0xFFFF) as u16
let bits_32_to_47 = (self.0 >> 16) & 0xFFFF;
bits_32_to_47 as u16
}
pub fn c_field(&self) -> u16 {
(self.0 & 0xFFFF) as u16
let bits_48_to_63 = self.0 & 0xFFFF;
bits_48_to_63 as u16
}
pub fn set_a_field(&mut self, bits: u16) {
self.0 = (bits as u64) << 31;
let mut fields = InstructionFields::from(&*self);
fields.a_field = bits;
*self = fields.build();
}
pub fn set_b_field(&mut self, bits: u16) {
self.0 = (bits as u64) << 47;
let mut fields = InstructionFields::from(&*self);
fields.b_field = bits;
*self = fields.build();
}
pub fn set_c_field(&mut self, bits: u16) {
self.0 = (bits as u64) << 63;
let mut fields = InstructionFields::from(&*self);
fields.c_field = bits;
*self = fields.build();
}
pub fn point(from: u16, to: u16) -> Instruction {
Instruction::from(Point { from, to })
pub fn as_operand(&self) -> Operand {
match self.operation() {
Operation::MOVE => {
let Move { operand: to, .. } = Move::from(self);
Operand::Register(to.index(), to.as_type())
}
Operation::LOAD_ENCODED => {
let LoadEncoded {
destination,
value_type,
..
} = LoadEncoded::from(*self);
Operand::Register(destination, value_type)
}
Operation::LOAD_CONSTANT => {
let LoadConstant {
constant_type,
constant_index,
..
} = LoadConstant::from(*self);
Operand::Constant(constant_index, constant_type)
}
Operation::LOAD_LIST => {
let LoadList { destination, .. } = LoadList::from(*self);
Operand::Register(destination, TypeCode::LIST)
}
Operation::LOAD_FUNCTION => {
let LoadFunction { destination, .. } = LoadFunction::from(*self);
Operand::Register(destination, TypeCode::FUNCTION)
}
Operation::LOAD_SELF => {
let LoadSelf { destination, .. } = LoadSelf::from(*self);
Operand::Register(destination, TypeCode::FUNCTION)
}
Operation::ADD => {
let Add {
destination, left, ..
} = Add::from(self);
let register_type = match left.as_type() {
TypeCode::BOOLEAN => TypeCode::BOOLEAN,
TypeCode::BYTE => TypeCode::BYTE,
TypeCode::CHARACTER => TypeCode::STRING, // Adding characters concatenates them
TypeCode::INTEGER => TypeCode::INTEGER,
TypeCode::FLOAT => TypeCode::FLOAT,
TypeCode::STRING => TypeCode::STRING,
TypeCode::LIST => TypeCode::LIST,
TypeCode::FUNCTION => TypeCode::FUNCTION,
_ => unreachable!(),
};
Operand::Register(destination, register_type)
}
Operation::SUBTRACT => {
let Subtract {
destination, left, ..
} = Subtract::from(*self);
Operand::Register(destination, left.as_type())
}
Operation::MULTIPLY => {
let Multiply {
destination, left, ..
} = Multiply::from(*self);
Operand::Register(destination, left.as_type())
}
Operation::DIVIDE => {
let Divide {
destination, left, ..
} = Divide::from(*self);
Operand::Register(destination, left.as_type())
}
Operation::MODULO => {
let Modulo {
destination, left, ..
} = Modulo::from(*self);
Operand::Register(destination, left.as_type())
}
unsupported => todo!("Support {unsupported}"),
}
}
pub fn close(from: u16, to: u16) -> Instruction {
Instruction::from(Close { from, to })
pub fn no_op() -> Instruction {
Instruction(Operation::NO_OP.0 as u64)
}
pub fn load_boolean(destination: u16, value: bool, jump_next: bool) -> Instruction {
Instruction::from(LoadBoolean {
pub fn r#move(destination: u16, to: Operand) -> Instruction {
Instruction::from(Move {
destination,
operand: to,
})
}
pub fn close(from: u16, to: u16, r#type: TypeCode) -> Instruction {
Instruction::from(Close { from, to, r#type })
}
pub fn load_encoded(
destination: u16,
value: u8,
value_type: TypeCode,
jump_next: bool,
) -> Instruction {
Instruction::from(LoadEncoded {
destination,
value,
value_type,
jump_next,
})
}
pub fn load_constant(destination: u16, constant_index: u16, jump_next: bool) -> Instruction {
pub fn load_constant(
destination: u16,
constant_index: u16,
constant_type: TypeCode,
jump_next: bool,
) -> Instruction {
Instruction::from(LoadConstant {
destination,
constant_index,
constant_type,
jump_next,
})
}
@ -291,10 +417,18 @@ impl Instruction {
})
}
pub fn load_list(destination: u16, start_register: u16, jump_next: bool) -> Instruction {
pub fn load_list(
destination: u16,
item_type: TypeCode,
start_register: u16,
end_register: u16,
jump_next: bool,
) -> Instruction {
Instruction::from(LoadList {
destination,
item_type,
start_register,
end_register,
jump_next,
})
}
@ -306,145 +440,67 @@ impl Instruction {
})
}
pub fn get_local(destination: u16, local_index: u16) -> Instruction {
Instruction::from(GetLocal {
destination,
local_index,
})
}
pub fn set_local(register: u16, local_index: u16) -> Instruction {
Instruction::from(SetLocal {
local_index,
register_index: register,
})
}
pub fn add(
destination: u16,
left: Operand,
left_type: TypeCode,
right: Operand,
right_type: TypeCode,
) -> Instruction {
pub fn add(destination: u16, left: Operand, right: Operand) -> Instruction {
Instruction::from(Add {
destination,
left,
left_type,
right,
right_type,
})
}
pub fn subtract(
destination: u16,
left: Operand,
left_type: TypeCode,
right: Operand,
right_type: TypeCode,
) -> Instruction {
pub fn subtract(destination: u16, left: Operand, right: Operand) -> Instruction {
Instruction::from(Subtract {
destination,
left,
left_type,
right,
right_type,
})
}
pub fn multiply(
destination: u16,
left: Operand,
left_type: TypeCode,
right: Operand,
right_type: TypeCode,
) -> Instruction {
pub fn multiply(destination: u16, left: Operand, right: Operand) -> Instruction {
Instruction::from(Multiply {
destination,
left,
left_type,
right,
right_type,
})
}
pub fn divide(
destination: u16,
left: Operand,
left_type: TypeCode,
right: Operand,
right_type: TypeCode,
) -> Instruction {
pub fn divide(destination: u16, left: Operand, right: Operand) -> Instruction {
Instruction::from(Divide {
destination,
left,
left_type,
right,
right_type,
})
}
pub fn modulo(
destination: u16,
left: Operand,
left_type: TypeCode,
right: Operand,
right_type: TypeCode,
) -> Instruction {
pub fn modulo(destination: u16, left: Operand, right: Operand) -> Instruction {
Instruction::from(Modulo {
destination,
left,
left_type,
right,
right_type,
})
}
pub fn equal(
comparator: bool,
left: Operand,
left_type: TypeCode,
right: Operand,
right_type: TypeCode,
) -> Instruction {
pub fn equal(comparator: bool, left: Operand, right: Operand) -> Instruction {
Instruction::from(Equal {
comparator,
left,
left_type,
right,
right_type,
})
}
pub fn less(
comparator: bool,
left: Operand,
left_type: TypeCode,
right: Operand,
right_type: TypeCode,
) -> Instruction {
pub fn less(comparator: bool, left: Operand, right: Operand) -> Instruction {
Instruction::from(Less {
comparator,
left,
left_type,
right,
right_type,
})
}
pub fn less_equal(
comparator: bool,
left: Operand,
left_type: TypeCode,
right: Operand,
right_type: TypeCode,
) -> Instruction {
pub fn less_equal(comparator: bool, left: Operand, right: Operand) -> Instruction {
Instruction::from(LessEqual {
comparator,
left,
left_type,
right,
right_type,
})
}
@ -502,19 +558,24 @@ impl Instruction {
pub fn call_native(
destination: u16,
function: NativeFunction,
argument_count: u16,
first_argument_index: u16,
) -> Instruction {
Instruction::from(CallNative {
destination,
function,
argument_count,
first_argument_index,
})
}
pub fn r#return(should_return_value: bool, return_register: u16) -> Instruction {
pub fn r#return(
should_return_value: bool,
return_register: u16,
r#type: TypeCode,
) -> Instruction {
Instruction::from(Return {
should_return_value,
return_register,
r#type,
})
}
@ -526,55 +587,20 @@ impl Instruction {
self.operation().is_comparison()
}
pub fn as_argument(&self) -> Option<Operand> {
match self.operation() {
Operation::LOAD_CONSTANT => Some(Operand::Constant(self.b_field())),
Operation::LOAD_BOOLEAN
| Operation::LOAD_LIST
| Operation::LOAD_SELF
| Operation::GET_LOCAL
| Operation::ADD
| Operation::SUBTRACT
| Operation::MULTIPLY
| Operation::DIVIDE
| Operation::MODULO
| Operation::EQUAL
| Operation::LESS
| Operation::LESS_EQUAL
| Operation::NEGATE
| Operation::NOT
| Operation::CALL => Some(Operand::Register(self.a_field())),
Operation::CALL_NATIVE => {
let function = NativeFunction::from(self.b_field());
if function.returns_value() {
Some(Operand::Register(self.a_field()))
} else {
None
}
}
_ => None,
}
}
pub fn b_as_argument(&self) -> Operand {
pub fn b_as_operand(&self) -> Operand {
if self.b_is_constant() {
Operand::Constant(self.b_field())
Operand::Constant(self.b_field(), self.b_type())
} else {
Operand::Register(self.b_field())
Operand::Register(self.b_field(), self.b_type())
}
}
pub fn b_and_c_as_operands(&self) -> (Operand, Operand) {
let left = if self.b_is_constant() {
Operand::Constant(self.b_field())
} else {
Operand::Register(self.b_field())
};
let left = self.b_as_operand();
let right = if self.c_is_constant() {
Operand::Constant(self.c_field())
Operand::Constant(self.c_field(), self.c_type())
} else {
Operand::Register(self.c_field())
Operand::Register(self.c_field(), self.c_type())
};
(left, right)
@ -582,13 +608,12 @@ impl Instruction {
pub fn yields_value(&self) -> bool {
match self.operation() {
Operation::POINT
| Operation::LOAD_BOOLEAN
Operation::MOVE
| Operation::LOAD_ENCODED
| Operation::LOAD_CONSTANT
| Operation::LOAD_FUNCTION
| Operation::LOAD_LIST
| Operation::LOAD_SELF
| Operation::GET_LOCAL
| Operation::ADD
| Operation::SUBTRACT
| Operation::MULTIPLY
@ -603,7 +628,6 @@ impl Instruction {
function.returns_value()
}
Operation::CLOSE
| Operation::SET_LOCAL
| Operation::EQUAL
| Operation::LESS
| Operation::LESS_EQUAL
@ -611,7 +635,7 @@ impl Instruction {
| Operation::TEST_SET
| Operation::JUMP
| Operation::RETURN => false,
_ => self.operation().panic_from_unknown_code(),
unknown => panic!("Unknown operation: {}", unknown.0),
}
}
@ -619,16 +643,14 @@ impl Instruction {
let operation = self.operation();
match operation {
Operation::POINT => Point::from(*self).to_string(),
Operation::CLOSE => Close::from(*self).to_string(),
Operation::LOAD_BOOLEAN => LoadBoolean::from(*self).to_string(),
Operation::MOVE => Move::from(self).to_string(),
Operation::CLOSE => Close::from(self).to_string(),
Operation::LOAD_ENCODED => LoadEncoded::from(*self).to_string(),
Operation::LOAD_CONSTANT => LoadConstant::from(*self).to_string(),
Operation::LOAD_FUNCTION => LoadFunction::from(*self).to_string(),
Operation::LOAD_LIST => LoadList::from(*self).to_string(),
Operation::LOAD_SELF => LoadSelf::from(*self).to_string(),
Operation::GET_LOCAL => GetLocal::from(*self).to_string(),
Operation::SET_LOCAL => SetLocal::from(*self).to_string(),
Operation::ADD => Add::from(*self).to_string(),
Operation::ADD => Add::from(self).to_string(),
Operation::SUBTRACT => Subtract::from(*self).to_string(),
Operation::MULTIPLY => Multiply::from(*self).to_string(),
Operation::DIVIDE => Divide::from(*self).to_string(),
@ -638,14 +660,13 @@ impl Instruction {
Operation::EQUAL => Equal::from(*self).to_string(),
Operation::LESS => Less::from(*self).to_string(),
Operation::LESS_EQUAL => LessEqual::from(*self).to_string(),
Operation::TEST => Test::from(*self).to_string(),
Operation::TEST => Test::from(self).to_string(),
Operation::TEST_SET => TestSet::from(*self).to_string(),
Operation::CALL => Call::from(*self).to_string(),
Operation::CALL_NATIVE => CallNative::from(*self).to_string(),
Operation::JUMP => Jump::from(*self).to_string(),
Operation::JUMP => Jump::from(self).to_string(),
Operation::RETURN => Return::from(*self).to_string(),
_ => operation.panic_from_unknown_code(),
unknown => panic!("Unknown operation: {}", unknown.0),
}
}
}
@ -664,30 +685,37 @@ impl Display for Instruction {
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum Operand {
Constant(u16),
Register(u16),
Constant(u16, TypeCode),
Register(u16, TypeCode),
}
impl Operand {
pub fn index(&self) -> u16 {
match self {
Operand::Constant(index) => *index,
Operand::Register(index) => *index,
Operand::Constant(index, _) => *index,
Operand::Register(index, _) => *index,
}
}
pub fn is_constant(&self) -> bool {
matches!(self, Operand::Constant(_))
matches!(self, Operand::Constant(_, _))
}
pub fn is_register(&self) -> bool {
matches!(self, Operand::Register(_))
matches!(self, Operand::Register(_, _))
}
pub fn as_index_and_constant_flag(&self) -> (u16, bool) {
match self {
Operand::Constant(index) => (*index, true),
Operand::Register(index) => (*index, false),
Operand::Constant(index, _) => (*index, true),
Operand::Register(index, _) => (*index, false),
}
}
pub fn as_type(&self) -> TypeCode {
match self {
Operand::Constant(_, r#type) => *r#type,
Operand::Register(_, r#type) => *r#type,
}
}
}
@ -695,8 +723,28 @@ impl Operand {
impl Display for Operand {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Operand::Constant(index) => write!(f, "C{index}"),
Operand::Register(index) => write!(f, "R{index}"),
Operand::Constant(index, r#type) => match *r#type {
TypeCode::BOOLEAN => write!(f, "C_{}", index),
TypeCode::BYTE => write!(f, "C_{}", index),
TypeCode::CHARACTER => write!(f, "C_{}", index),
TypeCode::INTEGER => write!(f, "C_{}", index),
TypeCode::FLOAT => write!(f, "C_{}", index),
TypeCode::STRING => write!(f, "C_{}", index),
TypeCode::LIST => write!(f, "C_{}", index),
TypeCode::FUNCTION => write!(f, "C_{}", index),
_ => unreachable!(),
},
Operand::Register(index, r#type) => match *r#type {
TypeCode::BOOLEAN => write!(f, "R_BOOL_{}", index),
TypeCode::BYTE => write!(f, "R_BYTE_{}", index),
TypeCode::CHARACTER => write!(f, "R_CHAR_{}", index),
TypeCode::INTEGER => write!(f, "R_INT_{}", index),
TypeCode::FLOAT => write!(f, "R_FLOAT_{}", index),
TypeCode::STRING => write!(f, "R_STR_{}", index),
TypeCode::LIST => write!(f, "R_LIST_{}", index),
TypeCode::FUNCTION => write!(f, "R_FN_{}", index),
_ => unreachable!(),
},
}
}
}
@ -709,23 +757,19 @@ mod tests {
fn decode_operation() {
let instruction = Instruction::add(
42,
Operand::Constant(4),
TypeCode::STRING,
Operand::Register(2),
TypeCode::CHARACTER,
Operand::Constant(4, TypeCode::STRING),
Operand::Register(2, TypeCode::CHARACTER),
);
assert_eq!(instruction.operation(), Operation::ADD);
assert_eq!(Operation::ADD, instruction.operation());
}
#[test]
fn decode_a_field() {
let instruction = Instruction::add(
42,
Operand::Constant(4),
TypeCode::STRING,
Operand::Register(2),
TypeCode::CHARACTER,
Operand::Constant(4, TypeCode::STRING),
Operand::Register(2, TypeCode::CHARACTER),
);
assert_eq!(42, instruction.a_field());
@ -735,10 +779,8 @@ mod tests {
fn decode_b_field() {
let instruction = Instruction::add(
42,
Operand::Constant(4),
TypeCode::STRING,
Operand::Register(2),
TypeCode::CHARACTER,
Operand::Constant(4, TypeCode::STRING),
Operand::Register(2, TypeCode::CHARACTER),
);
assert_eq!(4, instruction.b_field());
@ -748,10 +790,8 @@ mod tests {
fn decode_c_field() {
let instruction = Instruction::add(
42,
Operand::Constant(4),
TypeCode::STRING,
Operand::Register(2),
TypeCode::CHARACTER,
Operand::Constant(4, TypeCode::STRING),
Operand::Register(2, TypeCode::CHARACTER),
);
assert_eq!(2, instruction.c_field());
@ -761,23 +801,19 @@ mod tests {
fn decode_d_field() {
let instruction = Instruction::add(
42,
Operand::Constant(4),
TypeCode::STRING,
Operand::Register(2),
TypeCode::CHARACTER,
Operand::Constant(4, TypeCode::STRING),
Operand::Register(2, TypeCode::CHARACTER),
);
assert!(instruction.d_field());
assert!(!instruction.d_field());
}
#[test]
fn decode_b_is_constant() {
let instruction = Instruction::add(
42,
Operand::Constant(4),
TypeCode::STRING,
Operand::Register(2),
TypeCode::CHARACTER,
Operand::Constant(4, TypeCode::STRING),
Operand::Register(2, TypeCode::CHARACTER),
);
assert!(instruction.b_is_constant());
@ -787,10 +823,8 @@ mod tests {
fn decode_c_is_constant() {
let instruction = Instruction::add(
42,
Operand::Register(2),
TypeCode::STRING,
Operand::Constant(4),
TypeCode::CHARACTER,
Operand::Register(2, TypeCode::STRING),
Operand::Constant(4, TypeCode::CHARACTER),
);
assert!(instruction.c_is_constant());
@ -800,10 +834,8 @@ mod tests {
fn decode_b_type() {
let instruction = Instruction::add(
42,
Operand::Constant(4),
TypeCode::STRING,
Operand::Register(2),
TypeCode::CHARACTER,
Operand::Constant(4, TypeCode::STRING),
Operand::Register(2, TypeCode::CHARACTER),
);
assert_eq!(TypeCode::STRING, instruction.b_type());
@ -813,10 +845,8 @@ mod tests {
fn decode_c_type() {
let instruction = Instruction::add(
42,
Operand::Constant(4),
TypeCode::STRING,
Operand::Register(2),
TypeCode::CHARACTER,
Operand::Constant(4, TypeCode::STRING),
Operand::Register(2, TypeCode::CHARACTER),
);
assert_eq!(TypeCode::CHARACTER, instruction.c_type());

View File

@ -1,28 +1,22 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
use super::{Instruction, InstructionFields, Operand, Operation, TypeCode};
pub struct Modulo {
pub destination: u16,
pub left: Operand,
pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
}
impl From<Instruction> for Modulo {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let (left, right) = instruction.b_and_c_as_operands();
let left_type = instruction.b_type();
let right_type = instruction.c_type();
Modulo {
destination,
left,
left_type,
right,
right_type,
}
}
}
@ -33,10 +27,10 @@ impl From<Modulo> for Instruction {
let a_field = modulo.destination;
let (b_field, b_is_constant) = modulo.left.as_index_and_constant_flag();
let (c_field, c_is_constant) = modulo.right.as_index_and_constant_flag();
let b_type = modulo.left_type;
let c_type = modulo.right_type;
let b_type = modulo.left.as_type();
let c_type = modulo.right.as_type();
InstructionBuilder {
InstructionFields {
operation,
a_field,
b_field,
@ -56,14 +50,19 @@ impl Display for Modulo {
let Modulo {
destination,
left,
left_type,
right,
right_type,
} = self;
write!(
f,
"R{destination} = {left_type}({left}) % {right_type}({right})",
)
match left.as_type() {
TypeCode::BOOLEAN => write!(f, "R_BOOL_{destination}")?,
TypeCode::BYTE => write!(f, "R_BYTE_{destination}")?,
TypeCode::CHARACTER => write!(f, "R_STR_{destination}")?,
TypeCode::FLOAT => write!(f, "R_FLOAT_{destination}")?,
TypeCode::INTEGER => write!(f, "R_INT_{destination}")?,
TypeCode::STRING => write!(f, "R_STR_{destination}")?,
_ => todo!(),
}
write!(f, " = {left} % {right}",)
}
}

View File

@ -0,0 +1,60 @@
use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::{InstructionFields, Operand, TypeCode};
pub struct Move {
pub destination: u16,
pub operand: Operand,
}
impl From<&Instruction> for Move {
fn from(instruction: &Instruction) -> Self {
Move {
destination: instruction.a_field(),
operand: instruction.b_as_operand(),
}
}
}
impl From<Move> for Instruction {
fn from(r#move: Move) -> Self {
let operation = Operation::MOVE;
let a_field = r#move.destination;
let (b_field, b_is_constant) = r#move.operand.as_index_and_constant_flag();
let b_type = r#move.operand.as_type();
InstructionFields {
operation,
a_field,
b_field,
b_type,
b_is_constant,
..Default::default()
}
.build()
}
}
impl Display for Move {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let Move {
destination,
operand: to,
} = self;
match to.as_type() {
TypeCode::BOOLEAN => write!(f, "R_BOOL_{destination} -> {to}"),
TypeCode::BYTE => write!(f, "R_BYTE_{destination} -> {to}"),
TypeCode::CHARACTER => write!(f, "R_CHAR_{destination} -> {to}"),
TypeCode::FLOAT => write!(f, "R_FLOAT_{destination} -> {to}"),
TypeCode::INTEGER => write!(f, "R_INT_{destination} -> {to}"),
TypeCode::STRING => write!(f, "R_STR_{destination} -> {to}"),
unsupported => write!(
f,
"Unsupported type code: {unsupported} for MOVE instruction"
),
}
}
}

View File

@ -1,28 +1,22 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
use super::{Instruction, InstructionFields, Operand, Operation, TypeCode};
pub struct Multiply {
pub destination: u16,
pub left: Operand,
pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
}
impl From<Instruction> for Multiply {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let (left, right) = instruction.b_and_c_as_operands();
let left_type = instruction.b_type();
let right_type = instruction.c_type();
Multiply {
destination,
left,
left_type,
right,
right_type,
}
}
}
@ -33,10 +27,10 @@ impl From<Multiply> for Instruction {
let a_field = multiply.destination;
let (b_field, b_is_constant) = multiply.left.as_index_and_constant_flag();
let (c_field, c_is_constant) = multiply.right.as_index_and_constant_flag();
let b_type = multiply.left_type;
let c_type = multiply.right_type;
let b_type = multiply.left.as_type();
let c_type = multiply.right.as_type();
InstructionBuilder {
InstructionFields {
operation,
a_field,
b_field,
@ -56,14 +50,19 @@ impl Display for Multiply {
let Multiply {
destination,
left,
left_type,
right,
right_type,
} = self;
write!(
f,
"R{destination} = {left_type}({left}) ✕ {right_type}({right})",
)
match left.as_type() {
TypeCode::BOOLEAN => write!(f, "R_BOOL_{destination}")?,
TypeCode::BYTE => write!(f, "R_BYTE_{destination}")?,
TypeCode::CHARACTER => write!(f, "R_STR_{destination}")?,
TypeCode::FLOAT => write!(f, "R_FLOAT_{destination}")?,
TypeCode::INTEGER => write!(f, "R_INT_{destination}")?,
TypeCode::STRING => write!(f, "R_STR_{destination}")?,
_ => todo!(),
}
write!(f, " = {left} ✕ {right}",)
}
}

View File

@ -1,6 +1,6 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
use super::{Instruction, InstructionFields, Operand, Operation, TypeCode};
pub struct Negate {
pub destination: u16,
@ -11,7 +11,7 @@ pub struct Negate {
impl From<Instruction> for Negate {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let argument = instruction.b_as_argument();
let argument = instruction.b_as_operand();
let argument_type = instruction.b_type();
Negate {
@ -29,7 +29,7 @@ impl From<Negate> for Instruction {
let (b_field, b_is_constant) = negate.argument.as_index_and_constant_flag();
let b_type = negate.argument_type;
InstructionBuilder {
InstructionFields {
operation,
a_field,
b_field,
@ -46,9 +46,9 @@ impl Display for Negate {
let Negate {
destination,
argument,
argument_type,
..
} = self;
write!(f, "R{destination} = -{argument_type}({argument})")
write!(f, "R{destination} = -{argument}")
}
}

View File

@ -2,7 +2,7 @@ use std::fmt::Display;
use crate::{Instruction, Operand, Operation};
use super::InstructionBuilder;
use super::InstructionFields;
pub struct Not {
pub destination: u16,
@ -12,7 +12,7 @@ pub struct Not {
impl From<Instruction> for Not {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let argument = instruction.b_as_argument();
let argument = instruction.b_as_operand();
Not {
destination,
@ -27,7 +27,7 @@ impl From<Not> for Instruction {
let a_field = not.destination;
let (b_field, b_is_constant) = not.argument.as_index_and_constant_flag();
InstructionBuilder {
InstructionFields {
operation,
a_field,
b_field,

View File

@ -9,62 +9,59 @@ use serde::{Deserialize, Serialize};
pub struct Operation(pub u8);
impl Operation {
// Stack manipulation
pub const POINT: Operation = Operation(0);
pub const CLOSE: Operation = Operation(1);
pub const NO_OP: Operation = Operation(0);
// Register manipulation
pub const MOVE: Operation = Operation(1);
pub const CLOSE: Operation = Operation(2);
// Loaders
pub const LOAD_BOOLEAN: Operation = Operation(2);
pub const LOAD_CONSTANT: Operation = Operation(3);
pub const LOAD_FUNCTION: Operation = Operation(4);
pub const LOAD_ENCODED: Operation = Operation(3);
pub const LOAD_CONSTANT: Operation = Operation(4);
pub const LOAD_LIST: Operation = Operation(5);
pub const LOAD_SELF: Operation = Operation(6);
// Locals
pub const GET_LOCAL: Operation = Operation(7);
pub const SET_LOCAL: Operation = Operation(8);
pub const LOAD_FUNCTION: Operation = Operation(6);
pub const LOAD_SELF: Operation = Operation(7);
// Arithmetic
pub const ADD: Operation = Operation(9);
pub const SUBTRACT: Operation = Operation(10);
pub const MULTIPLY: Operation = Operation(11);
pub const DIVIDE: Operation = Operation(12);
pub const MODULO: Operation = Operation(13);
pub const ADD: Operation = Operation(8);
pub const SUBTRACT: Operation = Operation(9);
pub const MULTIPLY: Operation = Operation(10);
pub const DIVIDE: Operation = Operation(11);
pub const MODULO: Operation = Operation(12);
// Comparison
pub const EQUAL: Operation = Operation(14);
pub const LESS: Operation = Operation(15);
pub const LESS_EQUAL: Operation = Operation(16);
pub const EQUAL: Operation = Operation(13);
pub const LESS: Operation = Operation(14);
pub const LESS_EQUAL: Operation = Operation(15);
// Unary operations
pub const NEGATE: Operation = Operation(17);
pub const NOT: Operation = Operation(18);
pub const NEGATE: Operation = Operation(16);
pub const NOT: Operation = Operation(17);
// Logical operations
pub const TEST: Operation = Operation(19);
pub const TEST_SET: Operation = Operation(20);
pub const TEST: Operation = Operation(18);
pub const TEST_SET: Operation = Operation(19);
// Function calls
pub const CALL: Operation = Operation(21);
pub const CALL_NATIVE: Operation = Operation(22);
pub const CALL: Operation = Operation(20);
pub const CALL_NATIVE: Operation = Operation(21);
// Control flow
pub const JUMP: Operation = Operation(23);
pub const RETURN: Operation = Operation(24);
pub const JUMP: Operation = Operation(22);
pub const RETURN: Operation = Operation(23);
}
impl Operation {
pub fn name(&self) -> &'static str {
match *self {
Self::POINT => "POINT",
Self::NO_OP => "NO_OP",
Self::MOVE => "MOVE",
Self::CLOSE => "CLOSE",
Self::LOAD_BOOLEAN => "LOAD_BOOLEAN",
Self::LOAD_ENCODED => "LOAD_ENCODED",
Self::LOAD_CONSTANT => "LOAD_CONSTANT",
Self::LOAD_FUNCTION => "LOAD_FUNCTION",
Self::LOAD_LIST => "LOAD_LIST",
Self::LOAD_SELF => "LOAD_SELF",
Self::GET_LOCAL => "GET_LOCAL",
Self::SET_LOCAL => "SET_LOCAL",
Self::ADD => "ADD",
Self::SUBTRACT => "SUBTRACT",
Self::MULTIPLY => "MULTIPLY",
@ -81,7 +78,7 @@ impl Operation {
Self::CALL_NATIVE => "CALL_NATIVE",
Self::JUMP => "JUMP",
Self::RETURN => "RETURN",
_ => self.panic_from_unknown_code(),
unknown => panic!("Unknown operation: {}", unknown.0),
}
}
@ -102,10 +99,6 @@ impl Operation {
Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL
)
}
pub fn panic_from_unknown_code(self) -> ! {
panic!("Unknown operation code: {}", self.0);
}
}
impl Debug for Operation {

View File

@ -1,43 +0,0 @@
use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct Point {
pub from: u16,
pub to: u16,
}
impl From<Instruction> for Point {
fn from(instruction: Instruction) -> Self {
Point {
from: instruction.b_field(),
to: instruction.c_field(),
}
}
}
impl From<Point> for Instruction {
fn from(r#move: Point) -> Self {
let operation = Operation::POINT;
let b_field = r#move.from;
let c_field = r#move.to;
InstructionBuilder {
operation,
b_field,
c_field,
..Default::default()
}
.build()
}
}
impl Display for Point {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let Point { from, to } = self;
write!(f, "{from} -> {to}")
}
}

View File

@ -2,21 +2,24 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
use super::{InstructionFields, TypeCode};
pub struct Return {
pub should_return_value: bool,
pub return_register: u16,
pub r#type: TypeCode,
}
impl From<Instruction> for Return {
fn from(instruction: Instruction) -> Self {
let should_return_value = instruction.b_field() != 0;
let return_register = instruction.c_field();
let r#type = instruction.b_type();
Return {
should_return_value,
return_register,
r#type,
}
}
}
@ -25,11 +28,13 @@ impl From<Return> for Instruction {
fn from(r#return: Return) -> Self {
let operation = Operation::RETURN;
let b_field = r#return.should_return_value as u16;
let b_type = r#return.r#type;
let c_field = r#return.return_register;
InstructionBuilder {
InstructionFields {
operation,
b_field,
b_type,
c_field,
..Default::default()
}
@ -42,10 +47,23 @@ impl Display for Return {
let Return {
should_return_value,
return_register,
r#type,
} = self;
if *should_return_value {
write!(f, "RETURN R{return_register}")
write!(f, "RETURN ")?;
match *r#type {
TypeCode::BOOLEAN => write!(f, "R_BOOL_{return_register}"),
TypeCode::BYTE => write!(f, "R_BYTE_{return_register}"),
TypeCode::CHARACTER => write!(f, "R_CHAR_{return_register}"),
TypeCode::FLOAT => write!(f, "R_FLOAT_{return_register}"),
TypeCode::INTEGER => write!(f, "R_INT_{return_register}"),
TypeCode::STRING => write!(f, "R_STR_{return_register}"),
TypeCode::LIST => write!(f, "R_LIST_{return_register}"),
TypeCode::FUNCTION => write!(f, "R_FN_{return_register}"),
unsupported => unreachable!("Unsupported return type: {}", unsupported),
}
} else {
write!(f, "RETURN")
}

View File

@ -1,49 +0,0 @@
use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
pub struct SetLocal {
pub register_index: u16,
pub local_index: u16,
}
impl From<Instruction> for SetLocal {
fn from(instruction: Instruction) -> Self {
let register_index = instruction.b_field();
let local_index = instruction.c_field();
SetLocal {
register_index,
local_index,
}
}
}
impl From<SetLocal> for Instruction {
fn from(set_local: SetLocal) -> Self {
let operation = Operation::SET_LOCAL;
let b_field = set_local.register_index;
let c_field = set_local.local_index;
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,28 +1,22 @@
use std::fmt::{self, Display, Formatter};
use super::{Instruction, InstructionBuilder, Operand, Operation, TypeCode};
use super::{Instruction, InstructionFields, Operand, Operation, TypeCode};
pub struct Subtract {
pub destination: u16,
pub left: Operand,
pub left_type: TypeCode,
pub right: Operand,
pub right_type: TypeCode,
}
impl From<Instruction> for Subtract {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let (left, right) = instruction.b_and_c_as_operands();
let left_type = instruction.b_type();
let right_type = instruction.c_type();
Subtract {
destination,
left,
left_type,
right,
right_type,
}
}
}
@ -33,10 +27,10 @@ impl From<Subtract> for Instruction {
let a_field = subtract.destination;
let (b_field, b_is_constant) = subtract.left.as_index_and_constant_flag();
let (c_field, c_is_constant) = subtract.right.as_index_and_constant_flag();
let b_type = subtract.left_type;
let c_type = subtract.right_type;
let b_type = subtract.left.as_type();
let c_type = subtract.right.as_type();
InstructionBuilder {
InstructionFields {
operation,
a_field,
b_field,
@ -56,14 +50,19 @@ impl Display for Subtract {
let Subtract {
destination,
left,
left_type,
right,
right_type,
} = self;
write!(
f,
"R{destination} = {left_type}({left}) - {right_type}({right})",
)
match left.as_type() {
TypeCode::BOOLEAN => write!(f, "R_BOOL_{destination}")?,
TypeCode::BYTE => write!(f, "R_BYTE_{destination}")?,
TypeCode::CHARACTER => write!(f, "R_STR_{destination}")?,
TypeCode::FLOAT => write!(f, "R_FLOAT_{destination}")?,
TypeCode::INTEGER => write!(f, "R_INT_{destination}")?,
TypeCode::STRING => write!(f, "R_STR_{destination}")?,
_ => todo!(),
}
write!(f, " = {left} - {right}",)
}
}

View File

@ -2,15 +2,15 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operation};
use super::InstructionBuilder;
use super::InstructionFields;
pub struct Test {
pub operand_register: u16,
pub test_value: bool,
}
impl From<Instruction> for Test {
fn from(instruction: Instruction) -> Self {
impl From<&Instruction> for Test {
fn from(instruction: &Instruction) -> Self {
let operand_register = instruction.b_field();
let test_value = instruction.c_field() != 0;
@ -26,7 +26,7 @@ impl From<Test> for Instruction {
let b_field = test.operand_register;
let c_field = test.test_value as u16;
InstructionBuilder {
InstructionFields {
operation: Operation::TEST,
b_field,
c_field,
@ -44,6 +44,6 @@ impl Display for Test {
} = self;
let bang = if *test_value { "" } else { "!" };
write!(f, "if {bang}R{operand_register} {{ JUMP +1 }}")
write!(f, "if {bang}R_BOOL_{operand_register} {{ JUMP +1 }}")
}
}

View File

@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter};
use crate::{Instruction, Operand, Operation};
use super::InstructionBuilder;
use super::InstructionFields;
pub struct TestSet {
pub destination: u16,
@ -13,7 +13,7 @@ pub struct TestSet {
impl From<Instruction> for TestSet {
fn from(instruction: Instruction) -> Self {
let destination = instruction.a_field();
let argument = instruction.b_as_argument();
let argument = instruction.b_as_operand();
let test_value = instruction.c_field() != 0;
TestSet {
@ -31,7 +31,7 @@ impl From<TestSet> for Instruction {
let (b_field, b_is_constant) = test_set.argument.as_index_and_constant_flag();
let c_field = test_set.test_value as u16;
InstructionBuilder {
InstructionFields {
operation,
a_field,
b_field,

View File

@ -1,15 +1,20 @@
use std::fmt::Display;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
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 const NONE: TypeCode = TypeCode(0);
pub const BOOLEAN: TypeCode = TypeCode(1);
pub const BYTE: TypeCode = TypeCode(2);
pub const CHARACTER: TypeCode = TypeCode(3);
pub const FLOAT: TypeCode = TypeCode(4);
pub const INTEGER: TypeCode = TypeCode(5);
pub const STRING: TypeCode = TypeCode(6);
pub const LIST: TypeCode = TypeCode(7);
pub const FUNCTION: TypeCode = TypeCode(8);
pub fn panic_from_unknown_code(self) -> ! {
panic!("Unknown type code: {}", self.0);
@ -25,6 +30,8 @@ impl Display for TypeCode {
TypeCode::FLOAT => write!(f, "float"),
TypeCode::INTEGER => write!(f, "int"),
TypeCode::STRING => write!(f, "str"),
TypeCode::LIST => write!(f, "list"),
TypeCode::FUNCTION => write!(f, "fn"),
_ => self.panic_from_unknown_code(),
}
}

View File

@ -14,10 +14,10 @@
//! ## Examples
//!
//! ```rust
//! # use dust_lang::{run, ConcreteValue};
//! # use dust_lang::{run, Value};
//! let result = run("21 * 2").unwrap();
//!
//! assert_eq!(result, Some(ConcreteValue::Integer(42)));
//! assert_eq!(result, Some(Value::integer(42)));
//! ```
//!
//! ```rust
@ -40,17 +40,17 @@ pub mod value;
pub mod vm;
pub use crate::chunk::{Chunk, Disassembler, Local, Scope};
pub use crate::compiler::{CompileError, Compiler, compile};
pub use crate::compiler::{compile, CompileError, Compiler};
pub use crate::dust_error::{AnnotatedError, DustError};
pub use crate::instruction::{Operand, Instruction, Operation};
pub use crate::lexer::{LexError, Lexer, lex};
pub use crate::instruction::{Instruction, Operand, Operation};
pub use crate::lexer::{lex, LexError, Lexer};
pub use crate::native_function::{NativeFunction, NativeFunctionError};
pub use crate::token::{Token, TokenKind, TokenOwned};
pub use crate::r#type::{EnumType, FunctionType, StructType, Type, TypeConflict};
pub use crate::token::{Token, TokenKind, TokenOwned};
pub use crate::value::{
AbstractList, ConcreteValue, DustString, Function, RangeValue, Value, ValueError,
};
pub use crate::vm::{Pointer, Vm, run};
pub use crate::vm::{run, Vm};
use std::fmt::Display;

View File

@ -1,20 +1,20 @@
use std::{ops::Range, panic};
use crate::vm::ThreadData;
use crate::vm::Thread;
pub fn panic(data: &mut ThreadData, _: u16, argument_range: Range<u16>) -> bool {
pub fn panic(data: &mut Thread, _: usize, argument_range: Range<usize>) {
let current_frame = data.current_frame();
let position = data.current_position();
let mut message = format!("Dust panic at {position}!");
for register_index in argument_range {
let value_option = data.open_register_allow_empty_unchecked(register_index);
let value = match value_option {
Some(value) => value,
None => continue,
};
let string = value.display(data);
let string = current_frame
.registers
.strings
.get(register_index)
.as_value();
message.push_str(&string);
message.push_str(string);
message.push('\n');
}

View File

@ -1,12 +1,11 @@
use std::io::{Write, stdin, stdout};
use std::io::{stdin, stdout, Write};
use std::ops::Range;
use crate::{
ConcreteValue, Value,
vm::{Register, ThreadData, get_next_action},
};
use crate::vm::Thread;
use crate::DustString;
pub fn read_line(data: &mut ThreadData, destination: u16, _argument_range: Range<u16>) -> bool {
pub fn read_line(data: &mut Thread, destination: usize, _argument_range: Range<usize>) {
let current_frame = data.current_frame_mut();
let mut buffer = String::new();
if stdin().read_line(&mut buffer).is_ok() {
@ -14,45 +13,44 @@ pub fn read_line(data: &mut ThreadData, destination: u16, _argument_range: Range
buffer.truncate(length.saturating_sub(1));
let register = Register::Value(Value::Concrete(ConcreteValue::string(buffer)));
let string = DustString::from(buffer);
data.set_register(destination, register);
current_frame
.registers
.strings
.set_to_new_register(destination, string);
}
}
data.next_action = get_next_action(data);
false
}
pub fn write(data: &mut ThreadData, _: u16, argument_range: Range<u16>) -> bool {
pub fn write(data: &mut Thread, _: usize, argument_range: Range<usize>) {
let current_frame = data.current_frame_mut();
let mut stdout = stdout();
for register_index in argument_range {
if let Some(value) = data.open_register_allow_empty_unchecked(register_index) {
let string = value.display(data);
let string = current_frame
.registers
.strings
.get(register_index)
.as_value();
let _ = stdout.write(string.as_bytes());
}
}
let _ = stdout.flush();
data.next_action = get_next_action(data);
false
}
pub fn write_line(data: &mut ThreadData, _: u16, argument_range: Range<u16>) -> bool {
pub fn write_line(data: &mut Thread, _: usize, argument_range: Range<usize>) {
let current_frame = data.current_frame_mut();
let mut stdout = stdout().lock();
for register_index in argument_range {
if let Some(value) = data.open_register_allow_empty_unchecked(register_index) {
let string = value.display(data);
let string = current_frame
.registers
.strings
.get(register_index)
.as_value();
let _ = stdout.write(string.as_bytes());
}
let _ = stdout.write(b"\n");
}
}
let _ = stdout.flush();
data.next_action = get_next_action(data);
false
}

View File

@ -5,7 +5,6 @@
mod assert;
mod io;
mod random;
mod string;
mod thread;
use std::{
@ -17,7 +16,7 @@ use std::{
use serde::{Deserialize, Serialize};
use crate::{AnnotatedError, FunctionType, Span, Type, vm::ThreadData};
use crate::{AnnotatedError, FunctionType, Span, Type, vm::Thread};
macro_rules! define_native_function {
($(($name:ident, $bytes:literal, $str:expr, $type:expr, $function:expr)),*) => {
@ -34,13 +33,13 @@ macro_rules! define_native_function {
impl NativeFunction {
pub fn call(
&self,
data: &mut ThreadData,
destination: u16,
argument_range: Range<u16>,
) -> bool {
thread: &mut Thread,
destination: usize,
argument_range: Range<usize>,
) {
match self {
$(
NativeFunction::$name => $function(data, destination, argument_range),
NativeFunction::$name => $function(thread, destination, argument_range),
)*
}
}
@ -74,7 +73,7 @@ macro_rules! define_native_function {
pub fn returns_value(&self) -> bool {
match self {
$(
NativeFunction::$name => $type.return_type != Type::None,
NativeFunction::$name => $type.return_type.as_ref() != &Type::None,
)*
}
}
@ -134,11 +133,7 @@ define_native_function! {
Panic,
3,
"panic",
FunctionType {
type_parameters: Vec::with_capacity(0),
value_parameters: Vec::with_capacity(0),
return_type: Type::None
},
FunctionType::new([], [], Type::None),
assert::panic
),
@ -147,17 +142,13 @@ define_native_function! {
// (ToByte, 5_u8, "to_byte", true),
// (ToFloat, 6_u8, "to_float", true),
// (ToInteger, 7_u8, "to_integer", true),
(
ToString,
8,
"to_string",
FunctionType {
type_parameters: Vec::with_capacity(0),
value_parameters: vec![(0, Type::Any)],
return_type: Type::String
},
string::to_string
),
// (
// ToString,
// 8,
// "to_string",
// FunctionType::new([], [Type::Any], Type::String),
// string::to_string
// ),
// // List and string
// (All, 9_u8, "all", true),
@ -212,11 +203,7 @@ define_native_function! {
ReadLine,
50,
"read_line",
FunctionType {
type_parameters: Vec::with_capacity(0),
value_parameters: Vec::with_capacity(0),
return_type: Type::String
},
FunctionType::new([], [], Type::String),
io::read_line
),
// (ReadTo, 51_u8, "read_to", false),
@ -228,11 +215,7 @@ define_native_function! {
Write,
55,
"write",
FunctionType {
type_parameters: Vec::with_capacity(0),
value_parameters: vec![(0, Type::String)],
return_type: Type::None
},
FunctionType::new([], [Type::String], Type::None),
io::write
),
// (WriteFile, 56_u8, "write_file", false),
@ -240,11 +223,7 @@ define_native_function! {
WriteLine,
57,
"write_line",
FunctionType {
type_parameters: Vec::with_capacity(0),
value_parameters: vec![(0, Type::String)],
return_type: Type::None
},
FunctionType::new([], [Type::String], Type::None),
io::write_line
),
@ -253,11 +232,7 @@ define_native_function! {
RandomInteger,
58,
"random_int",
FunctionType {
type_parameters: Vec::with_capacity(0),
value_parameters: vec![(0, Type::Integer), (1, Type::Integer)],
return_type: Type::Integer
},
FunctionType::new([], [Type::Integer, Type::Integer], Type::Integer),
random::random_int
),
@ -266,20 +241,7 @@ define_native_function! {
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
},
FunctionType::new([], [ Type::function([], [], Type::None)], Type::None),
thread::spawn
)
}

View File

@ -2,12 +2,10 @@ use std::ops::Range;
use rand::Rng;
use crate::{
Value,
vm::{Register, ThreadData, get_next_action},
};
use crate::vm::Thread;
pub fn random_int(data: &mut ThreadData, destination: u16, argument_range: Range<u16>) -> bool {
pub fn random_int(data: &mut Thread, destination: usize, argument_range: Range<usize>) {
let current_frame = data.current_frame_mut();
let mut argument_range_iter = argument_range.into_iter();
let (min, max) = {
let mut min = None;
@ -16,25 +14,24 @@ pub fn random_int(data: &mut ThreadData, destination: u16, argument_range: Range
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);
let integer = current_frame
.registers
.integers
.get(register_index)
.copy_value();
if let Some(argument) = value_option {
if let Some(integer) = argument.as_integer() {
if min.is_none() {
min = Some(integer);
} else {
if let Some(min) = min {
break (min, integer);
}
}
} else {
min = Some(integer);
}
}
};
let random_integer = rand::thread_rng().gen_range(min.unwrap()..max);
let random_integer = rand::thread_rng().gen_range(min..max);
data.set_register(destination, Register::Value(Value::integer(random_integer)));
data.next_action = get_next_action(data);
false
current_frame
.registers
.integers
.set_to_new_register(destination, random_integer);
}

View File

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

View File

@ -1,64 +1,11 @@
use std::{
ops::Range,
thread::{Builder, JoinHandle},
};
use std::{ops::Range, thread::JoinHandle};
use tracing::{Level, info, span};
use crate::vm::Thread;
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")
fn start_thread(_thread: &mut Thread, _argument_range: Range<usize>) -> JoinHandle<()> {
todo!();
}
pub fn spawn(data: &mut ThreadData, _: u16, argument_range: Range<u16>) -> bool {
pub fn spawn(data: &mut Thread, _: usize, argument_range: Range<usize>) {
let _ = start_thread(data, argument_range);
data.next_action = get_next_action(data);
false
}

View File

@ -4,25 +4,22 @@ use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
macro_rules! define_tokens {
($($variant:ident $(($data_type:ty))?),+ $(,)?) => {
($($variant:ident $(($data_type:ty))?),+) => {
/// Source token.
///
/// This is a borrowed type, i.e. some variants contain references to the source text.
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Default, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Token<'src> {
#[default]
Eof,
$(
$variant $(($data_type))?,
)*
}
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
/// Data-less representation of a source token.
///
/// If a [Token] borrows from the source text, its TokenKind omits the data.
pub enum TokenKind {
Eof,
$(
$variant,
)*
@ -31,6 +28,8 @@ macro_rules! define_tokens {
}
define_tokens! {
Eof,
// Hard-coded values
Boolean(&'src str),
Byte(&'src str),
@ -90,7 +89,7 @@ define_tokens! {
Slash,
SlashEqual,
Star,
StarEqual,
StarEqual
}
impl Token<'_> {
@ -448,7 +447,7 @@ impl Display for Token<'_> {
/// Owned representation of a source token.
///
/// If a [Token] borrows from the source text, its TokenOwned omits the data.
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum TokenOwned {
Eof,

View File

@ -7,8 +7,11 @@ use std::{
use serde::{Deserialize, Serialize};
use crate::instruction::TypeCode;
/// Description of a kind of value.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(tag = "Type", content = "Value")]
pub enum Type {
Any,
Boolean,
@ -16,43 +19,54 @@ pub enum Type {
Character,
Enum(EnumType),
Float,
Function(Box<FunctionType>),
Generic {
identifier_index: u8,
concrete_type: Option<Box<Type>>,
},
Function(FunctionType),
Generic(GenericType),
Integer,
List(Box<Type>),
Map {
pairs: Vec<(u8, Type)>,
},
List(TypeCode),
Map(Vec<Type>),
#[default]
None,
Range {
r#type: Box<Type>,
},
Range(Box<Type>),
SelfFunction,
String,
Struct(StructType),
Tuple {
fields: Vec<Type>,
},
Tuple(Vec<Type>),
}
impl Type {
pub fn function(function_type: FunctionType) -> Self {
Type::Function(Box::new(function_type))
pub fn function<T: Into<Vec<u16>>, U: Into<Vec<Type>>>(
type_parameters: T,
value_parameters: U,
return_type: Type,
) -> Self {
Type::Function(FunctionType {
type_parameters: type_parameters.into(),
value_parameters: value_parameters.into(),
return_type: Box::new(return_type),
})
}
pub fn list(element_type: Type) -> Self {
Type::List(Box::new(element_type))
pub fn type_code(&self) -> TypeCode {
match self {
Type::Boolean => TypeCode::BOOLEAN,
Type::Byte => TypeCode::BYTE,
Type::Character => TypeCode::CHARACTER,
Type::Float => TypeCode::FLOAT,
Type::Integer => TypeCode::INTEGER,
Type::None => TypeCode::NONE,
Type::String => TypeCode::STRING,
Type::List { .. } => TypeCode::LIST,
Type::Function { .. } => TypeCode::FUNCTION,
_ => todo!(),
}
}
/// Returns a concrete type, either the type itself or the concrete type of a generic type.
pub fn concrete_type(&self) -> &Type {
if let Type::Generic {
if let Type::Generic(GenericType {
concrete_type: Some(concrete_type),
..
} = self
}) = self
{
concrete_type.concrete_type()
} else {
@ -63,24 +77,22 @@ impl Type {
/// Checks that the type is compatible with another type.
pub fn check(&self, other: &Type) -> Result<(), TypeConflict> {
match (self.concrete_type(), other.concrete_type()) {
(Type::Any, _)
| (_, Type::Any)
| (Type::Boolean, Type::Boolean)
(Type::Boolean, Type::Boolean)
| (Type::Byte, Type::Byte)
| (Type::Character, Type::Character)
| (Type::Float, Type::Float)
| (Type::Integer, Type::Integer)
| (Type::None, Type::None)
| (Type::String { .. }, Type::String { .. }) => return Ok(()),
| (Type::String, Type::String) => return Ok(()),
(
Type::Generic {
Type::Generic(GenericType {
concrete_type: left,
..
},
Type::Generic {
}),
Type::Generic(GenericType {
concrete_type: right,
..
},
}),
) => match (left, right) {
(Some(left), Some(right)) => {
if left.check(right).is_ok() {
@ -92,8 +104,8 @@ impl Type {
}
_ => {}
},
(Type::Generic { concrete_type, .. }, other)
| (other, Type::Generic { concrete_type, .. }) => {
(Type::Generic(GenericType { concrete_type, .. }), other)
| (other, Type::Generic(GenericType { concrete_type, .. })) => {
if let Some(concrete_type) = concrete_type {
if other == concrete_type.as_ref() {
return Ok(());
@ -106,7 +118,7 @@ impl Type {
}
}
(Type::List(left_type), Type::List(right_type)) => {
if left_type.check(right_type).is_err() {
if left_type != right_type {
return Err(TypeConflict {
actual: other.clone(),
expected: self.clone(),
@ -120,12 +132,12 @@ impl Type {
type_parameters: left_type_parameters,
value_parameters: left_value_parameters,
return_type: left_return,
} = left_function_type.as_ref();
} = left_function_type;
let FunctionType {
type_parameters: right_type_parameters,
value_parameters: right_value_parameters,
return_type: right_return,
} = right_function_type.as_ref();
} = right_function_type;
if left_return != right_return
|| left_type_parameters != right_type_parameters
@ -139,7 +151,7 @@ impl Type {
return Ok(());
}
(Type::Range { r#type: left_type }, Type::Range { r#type: right_type }) => {
(Type::Range(left_type), Type::Range(right_type)) => {
if left_type == right_type {
return Ok(());
}
@ -164,25 +176,24 @@ impl Display for Type {
Type::Enum(EnumType { name, .. }) => write!(f, "{name}"),
Type::Float => write!(f, "float"),
Type::Function(function_type) => write!(f, "{function_type}"),
Type::Generic { concrete_type, .. } => {
Type::Generic(GenericType { concrete_type, .. }) => {
match concrete_type.clone().map(|r#box| *r#box) {
Some(Type::Generic {
identifier_index: identifier,
..
}) => write!(f, "{identifier}"),
Some(Type::Generic(GenericType {
identifier_index, ..
})) => write!(f, "C_{identifier_index}"),
Some(concrete_type) => write!(f, "implied to be {concrete_type}"),
None => write!(f, "unknown"),
}
}
Type::Integer => write!(f, "int"),
Type::List(item_type) => write!(f, "[{item_type}]"),
Type::Map { pairs } => {
Type::Map(pairs) => {
write!(f, "map ")?;
write!(f, "{{")?;
for (index, (key, value)) in pairs.iter().enumerate() {
write!(f, "{key}: {value}")?;
for (index, r#type) in pairs.iter().enumerate() {
write!(f, "???: {type}")?;
if index != pairs.len() - 1 {
write!(f, ", ")?;
@ -192,11 +203,11 @@ impl Display for Type {
write!(f, "}}")
}
Type::None => write!(f, "none"),
Type::Range { r#type } => write!(f, "{type} range"),
Type::Range(r#type) => write!(f, "{type} range"),
Type::SelfFunction => write!(f, "self"),
Type::String => write!(f, "str"),
Type::Struct(struct_type) => write!(f, "{struct_type}"),
Type::Tuple { fields } => {
Type::Tuple(fields) => {
write!(f, "(")?;
for (index, r#type) in fields.iter().enumerate() {
@ -231,13 +242,13 @@ impl Ord for Type {
(Type::Character, Type::Character) => Ordering::Equal,
(Type::Character, _) => Ordering::Greater,
(Type::Enum(left_enum), Type::Enum(right_enum)) => left_enum.cmp(right_enum),
(Type::Enum(_), _) => Ordering::Greater,
(Type::Enum { .. }, _) => Ordering::Greater,
(Type::Float, Type::Float) => Ordering::Equal,
(Type::Float, _) => Ordering::Greater,
(Type::Function(left_function), Type::Function(right_function)) => {
left_function.cmp(right_function)
}
(Type::Function(_), _) => Ordering::Greater,
(Type::Function { .. }, _) => Ordering::Greater,
(Type::Generic { .. }, Type::Generic { .. }) => Ordering::Equal,
(Type::Generic { .. }, _) => Ordering::Greater,
(Type::Integer, Type::Integer) => Ordering::Equal,
@ -246,26 +257,24 @@ impl Ord for Type {
left_item_type.cmp(right_item_type)
}
(Type::List { .. }, _) => Ordering::Greater,
(Type::Map { pairs: left_pairs }, Type::Map { pairs: right_pairs }) => {
(Type::Map(left_pairs), Type::Map(right_pairs)) => {
left_pairs.iter().cmp(right_pairs.iter())
}
(Type::Map { .. }, _) => Ordering::Greater,
(Type::None, Type::None) => Ordering::Equal,
(Type::None, _) => Ordering::Greater,
(Type::Range { r#type: left_type }, Type::Range { r#type: right_type }) => {
left_type.cmp(right_type)
}
(Type::Range(left_type), Type::Range(right_type)) => left_type.cmp(right_type),
(Type::Range { .. }, _) => Ordering::Greater,
(Type::SelfFunction, Type::SelfFunction) => Ordering::Equal,
(Type::SelfFunction, _) => Ordering::Greater,
(Type::String, Type::String) => Ordering::Equal,
(Type::String { .. }, _) => Ordering::Greater,
(Type::String, _) => Ordering::Greater,
(Type::Struct(left_struct), Type::Struct(right_struct)) => {
left_struct.cmp(right_struct)
}
(Type::Struct(_), _) => Ordering::Greater,
(Type::Struct { .. }, _) => Ordering::Greater,
(Type::Tuple { fields: left }, Type::Tuple { fields: right }) => left.cmp(right),
(Type::Tuple(left), Type::Tuple(right)) => left.cmp(right),
(Type::Tuple { .. }, _) => Ordering::Greater,
}
}
@ -274,12 +283,12 @@ impl Ord for Type {
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct FunctionType {
pub type_parameters: Vec<u16>,
pub value_parameters: Vec<(u16, Type)>,
pub return_type: Type,
pub value_parameters: Vec<Type>,
pub return_type: Box<Type>,
}
impl FunctionType {
pub fn new<T: Into<Vec<u16>>, U: Into<Vec<(u16, Type)>>>(
pub fn new<T: Into<Vec<u16>>, U: Into<Vec<Type>>>(
type_parameters: T,
value_parameters: U,
return_type: Type,
@ -287,7 +296,17 @@ impl FunctionType {
FunctionType {
type_parameters: type_parameters.into(),
value_parameters: value_parameters.into(),
return_type,
return_type: Box::new(return_type),
}
}
}
impl Default for FunctionType {
fn default() -> Self {
FunctionType {
type_parameters: Vec::new(),
value_parameters: Vec::new(),
return_type: Box::new(Type::None),
}
}
}
@ -313,7 +332,7 @@ impl Display for FunctionType {
write!(f, "(")?;
if !self.value_parameters.is_empty() {
for (index, (_, r#type)) in self.value_parameters.iter().enumerate() {
for (index, r#type) in self.value_parameters.iter().enumerate() {
if index > 0 {
write!(f, ", ")?;
}
@ -324,7 +343,7 @@ impl Display for FunctionType {
write!(f, ")")?;
if self.return_type != Type::None {
if self.return_type.as_ref() != &Type::None {
write!(f, " -> {}", self.return_type)?;
}
@ -468,6 +487,12 @@ impl Display for EnumType {
}
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct GenericType {
pub identifier_index: u8,
pub concrete_type: Option<Box<Type>>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct TypeConflict {
pub expected: Type,

View File

@ -1,27 +1,41 @@
use std::fmt::{self, Display, Formatter};
use crate::{vm::ThreadData, Pointer, Type};
use crate::{
instruction::TypeCode,
vm::{Pointer, Thread},
};
use super::DustString;
use super::{ConcreteValue, DustString};
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct AbstractList {
pub item_type: Type,
pub item_type: TypeCode,
pub item_pointers: Vec<Pointer>,
}
impl AbstractList {
pub fn display(&self, data: &ThreadData) -> DustString {
pub fn display(&self, thread: &Thread) -> DustString {
let current_frame = thread.current_frame();
let mut display = DustString::new();
display.push('[');
for (i, item) in self.item_pointers.iter().enumerate() {
for (i, pointer) in self.item_pointers.iter().copied().enumerate() {
if i > 0 {
display.push_str(", ");
}
let item_display = data.follow_pointer_unchecked(*item).display(data);
let item_display = match self.item_type {
TypeCode::BOOLEAN => current_frame.get_boolean_from_pointer(pointer).to_string(),
TypeCode::BYTE => current_frame.get_byte_from_pointer(pointer).to_string(),
TypeCode::CHARACTER => current_frame
.get_character_from_pointer(pointer)
.to_string(),
TypeCode::FLOAT => current_frame.get_float_from_pointer(pointer).to_string(),
TypeCode::INTEGER => current_frame.get_integer_from_pointer(pointer).to_string(),
TypeCode::STRING => current_frame.get_string_from_pointer(pointer).to_string(),
_ => todo!(),
};
display.push_str(&item_display);
}
@ -30,6 +44,80 @@ impl AbstractList {
display
}
pub fn to_concrete(&self, thread: &Thread) -> ConcreteValue {
let mut concrete_list = Vec::with_capacity(self.item_pointers.len());
match self.item_type {
TypeCode::BOOLEAN => {
for pointer in &self.item_pointers {
let boolean = thread.current_frame().get_boolean_from_pointer(*pointer);
concrete_list.push(ConcreteValue::Boolean(boolean));
}
}
TypeCode::BYTE => {
for pointer in &self.item_pointers {
let byte = thread.current_frame().get_byte_from_pointer(*pointer);
concrete_list.push(ConcreteValue::Byte(byte));
}
}
TypeCode::CHARACTER => {
for pointer in &self.item_pointers {
let character = thread.current_frame().get_character_from_pointer(*pointer);
concrete_list.push(ConcreteValue::Character(character));
}
}
TypeCode::FLOAT => {
for pointer in &self.item_pointers {
let float = thread.current_frame().get_float_from_pointer(*pointer);
concrete_list.push(ConcreteValue::Float(float));
}
}
TypeCode::INTEGER => {
for pointer in &self.item_pointers {
let integer = thread.current_frame().get_integer_from_pointer(*pointer);
concrete_list.push(ConcreteValue::Integer(integer));
}
}
TypeCode::STRING => {
for pointer in &self.item_pointers {
let string = thread
.current_frame()
.get_string_from_pointer(*pointer)
.clone();
concrete_list.push(ConcreteValue::String(string));
}
}
TypeCode::LIST => {
for pointer in &self.item_pointers {
let list = thread
.current_frame()
.get_list_from_pointer(pointer)
.to_concrete(thread);
concrete_list.push(list);
}
}
_ => todo!(),
}
ConcreteValue::List(concrete_list)
}
}
impl Default for AbstractList {
fn default() -> Self {
Self {
item_type: TypeCode::NONE,
item_pointers: Vec::new(),
}
}
}
impl Display for AbstractList {
@ -37,7 +125,7 @@ impl Display for AbstractList {
write!(f, "[")?;
for pointer in &self.item_pointers {
write!(f, "{}", pointer)?;
write!(f, "{:?}", pointer)?;
}
write!(f, "]")

View File

@ -4,13 +4,14 @@ use serde::{Deserialize, Serialize};
use smartstring::{LazyCompact, SmartString};
use tracing::trace;
use crate::{Type, Value, ValueError};
use crate::{Type, Value};
use super::RangeValue;
pub type DustString = SmartString<LazyCompact>;
#[derive(Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(tag = "type", content = "value")]
pub enum ConcreteValue {
Boolean(bool),
Byte(u8),
@ -27,14 +28,54 @@ impl ConcreteValue {
Value::Concrete(self)
}
pub fn list<T: Into<Vec<ConcreteValue>>>(into_list: T) -> Self {
ConcreteValue::List(into_list.into())
pub fn list<T: Into<Vec<ConcreteValue>>>(into_items: T) -> Self {
ConcreteValue::List(into_items.into())
}
pub fn string<T: Into<SmartString<LazyCompact>>>(to_string: T) -> Self {
ConcreteValue::String(to_string.into())
}
pub fn as_boolean(&self) -> Option<&bool> {
if let ConcreteValue::Boolean(boolean) = self {
Some(boolean)
} else {
None
}
}
pub fn as_byte(&self) -> Option<&u8> {
if let ConcreteValue::Byte(byte) = self {
Some(byte)
} else {
None
}
}
pub fn as_character(&self) -> Option<&char> {
if let ConcreteValue::Character(character) = self {
Some(character)
} else {
None
}
}
pub fn as_float(&self) -> Option<&f64> {
if let ConcreteValue::Float(float) = self {
Some(float)
} else {
None
}
}
pub fn as_integer(&self) -> Option<&i64> {
if let ConcreteValue::Integer(integer) = self {
Some(integer)
} else {
None
}
}
pub fn as_string(&self) -> Option<&DustString> {
if let ConcreteValue::String(string) = self {
Some(string)
@ -43,6 +84,22 @@ impl ConcreteValue {
}
}
pub fn as_list(&self) -> Option<&Vec<ConcreteValue>> {
if let ConcreteValue::List(list) = self {
Some(list)
} else {
None
}
}
pub fn as_range(&self) -> Option<&RangeValue> {
if let ConcreteValue::Range(range) = self {
Some(range)
} else {
None
}
}
pub fn display(&self) -> DustString {
DustString::from(self.to_string())
}
@ -54,236 +111,11 @@ impl ConcreteValue {
ConcreteValue::Character(_) => Type::Character,
ConcreteValue::Float(_) => Type::Float,
ConcreteValue::Integer(_) => Type::Integer,
ConcreteValue::List(list) => {
let item_type = list.first().map_or(Type::Any, |item| item.r#type());
Type::List(Box::new(item_type))
}
ConcreteValue::List(items) => items.first().map_or(Type::Any, |item| item.r#type()),
ConcreteValue::Range(range) => range.r#type(),
ConcreteValue::String(_) => Type::String,
}
}
pub fn add(&self, other: &Self) -> ConcreteValue {
use ConcreteValue::*;
match (self, other) {
(Byte(left), Byte(right)) => {
let sum = left.saturating_add(*right);
Byte(sum)
}
(Character(left), Character(right)) => {
let mut concatenated = DustString::new();
concatenated.push(*left);
concatenated.push(*right);
String(concatenated)
}
(Character(left), String(right)) => {
let mut concatenated = DustString::new();
concatenated.push(*left);
concatenated.push_str(right);
String(concatenated)
}
(Float(left), Float(right)) => {
let sum = left + right;
Float(sum)
}
(Integer(left), Integer(right)) => {
let sum = left.saturating_add(*right);
Integer(sum)
}
(String(left), Character(right)) => {
let concatenated = format!("{}{}", left, right);
String(DustString::from(concatenated))
}
(String(left), String(right)) => {
let concatenated = format!("{}{}", left, right);
String(DustString::from(concatenated))
}
_ => panic!(
"{}",
ValueError::CannotAdd(
Value::Concrete(self.clone()),
Value::Concrete(other.clone())
)
),
}
}
pub fn subtract(&self, other: &Self) -> ConcreteValue {
use ConcreteValue::*;
match (self, other) {
(Byte(left), Byte(right)) => ConcreteValue::Byte(left.saturating_sub(*right)),
(Float(left), Float(right)) => ConcreteValue::Float(left - right),
(Integer(left), Integer(right)) => ConcreteValue::Integer(left.saturating_sub(*right)),
_ => panic!(
"{}",
ValueError::CannotSubtract(
Value::Concrete(self.clone()),
Value::Concrete(other.clone())
)
),
}
}
pub fn multiply(&self, other: &Self) -> Result<ConcreteValue, ValueError> {
use ConcreteValue::*;
let product = match (self, other) {
(Byte(left), Byte(right)) => ConcreteValue::Byte(left.saturating_mul(*right)),
(Float(left), Float(right)) => ConcreteValue::Float(left * right),
(Integer(left), Integer(right)) => ConcreteValue::Integer(left.saturating_mul(*right)),
_ => {
return Err(ValueError::CannotMultiply(
self.clone().to_value(),
other.clone().to_value(),
))
}
};
Ok(product)
}
pub fn divide(&self, other: &Self) -> Result<ConcreteValue, ValueError> {
use ConcreteValue::*;
let quotient = match (self, other) {
(Byte(left), Byte(right)) => ConcreteValue::Byte(left.saturating_div(*right)),
(Float(left), Float(right)) => ConcreteValue::Float(left / right),
(Integer(left), Integer(right)) => ConcreteValue::Integer(left.saturating_div(*right)),
_ => {
return Err(ValueError::CannotMultiply(
self.clone().to_value(),
other.clone().to_value(),
))
}
};
Ok(quotient)
}
pub fn modulo(&self, other: &Self) -> Result<ConcreteValue, ValueError> {
use ConcreteValue::*;
let product = match (self, other) {
(Byte(left), Byte(right)) => ConcreteValue::Byte(left.wrapping_rem(*right)),
(Float(left), Float(right)) => ConcreteValue::Float(left % right),
(Integer(left), Integer(right)) => {
ConcreteValue::Integer(left.wrapping_rem_euclid(*right))
}
_ => {
return Err(ValueError::CannotMultiply(
self.clone().to_value(),
other.clone().to_value(),
))
}
};
Ok(product)
}
pub fn negate(&self) -> ConcreteValue {
use ConcreteValue::*;
match self {
Boolean(value) => ConcreteValue::Boolean(!value),
Byte(value) => ConcreteValue::Byte(value.wrapping_neg()),
Float(value) => ConcreteValue::Float(-value),
Integer(value) => ConcreteValue::Integer(value.wrapping_neg()),
_ => panic!("{}", ValueError::CannotNegate(self.clone().to_value())),
}
}
pub fn not(&self) -> Result<ConcreteValue, ValueError> {
use ConcreteValue::*;
let not = match self {
Boolean(value) => ConcreteValue::Boolean(!value),
_ => return Err(ValueError::CannotNot(self.clone().to_value())),
};
Ok(not)
}
pub fn equals(&self, other: &ConcreteValue) -> bool {
use ConcreteValue::*;
match (self, other) {
(Boolean(left), Boolean(right)) => left == right,
(Byte(left), Byte(right)) => left == right,
(Character(left), Character(right)) => left == right,
(Float(left), Float(right)) => left == right,
(Integer(left), Integer(right)) => left == right,
(List(left), List(right)) => left == right,
(Range(left), Range(right)) => left == right,
(String(left), String(right)) => left == right,
_ => {
panic!(
"{}",
ValueError::CannotCompare(
Value::Concrete(self.clone()),
Value::Concrete(other.clone())
)
)
}
}
}
pub fn less_than(&self, other: &ConcreteValue) -> Result<ConcreteValue, ValueError> {
use ConcreteValue::*;
let less_than = match (self, other) {
(Boolean(left), Boolean(right)) => ConcreteValue::Boolean(left < right),
(Byte(left), Byte(right)) => ConcreteValue::Boolean(left < right),
(Character(left), Character(right)) => ConcreteValue::Boolean(left < right),
(Float(left), Float(right)) => ConcreteValue::Boolean(left < right),
(Integer(left), Integer(right)) => ConcreteValue::Boolean(left < right),
(List(left), List(right)) => ConcreteValue::Boolean(left < right),
(Range(left), Range(right)) => ConcreteValue::Boolean(left < right),
(String(left), String(right)) => ConcreteValue::Boolean(left < right),
_ => {
return Err(ValueError::CannotCompare(
self.clone().to_value(),
other.clone().to_value(),
))
}
};
Ok(less_than)
}
pub fn less_than_or_equals(&self, other: &ConcreteValue) -> Result<ConcreteValue, ValueError> {
use ConcreteValue::*;
let less_than_or_equal = match (self, other) {
(Boolean(left), Boolean(right)) => ConcreteValue::Boolean(left <= right),
(Byte(left), Byte(right)) => ConcreteValue::Boolean(left <= right),
(Character(left), Character(right)) => ConcreteValue::Boolean(left <= right),
(Float(left), Float(right)) => ConcreteValue::Boolean(left <= right),
(Integer(left), Integer(right)) => ConcreteValue::Boolean(left <= right),
(List(left), List(right)) => ConcreteValue::Boolean(left <= right),
(Range(left), Range(right)) => ConcreteValue::Boolean(left <= right),
(String(left), String(right)) => ConcreteValue::Boolean(left <= right),
_ => {
return Err(ValueError::CannotCompare(
self.clone().to_value(),
other.clone().to_value(),
))
}
};
Ok(less_than_or_equal)
}
}
impl Clone for ConcreteValue {
@ -296,7 +128,7 @@ impl Clone for ConcreteValue {
ConcreteValue::Character(character) => ConcreteValue::Character(*character),
ConcreteValue::Float(float) => ConcreteValue::Float(*float),
ConcreteValue::Integer(integer) => ConcreteValue::Integer(*integer),
ConcreteValue::List(list) => ConcreteValue::List(list.clone()),
ConcreteValue::List(items) => ConcreteValue::List(items.clone()),
ConcreteValue::Range(range) => ConcreteValue::Range(*range),
ConcreteValue::String(string) => ConcreteValue::String(string.clone()),
}
@ -319,10 +151,10 @@ impl Display for ConcreteValue {
Ok(())
}
ConcreteValue::Integer(integer) => write!(f, "{integer}"),
ConcreteValue::List(list) => {
ConcreteValue::List(items) => {
write!(f, "[")?;
for (index, item) in list.iter().enumerate() {
for (index, item) in items.iter().enumerate() {
if index > 0 {
write!(f, ", ")?;
}

View File

@ -4,7 +4,7 @@ use crate::FunctionType;
use super::DustString;
#[derive(Clone, Debug, PartialEq, PartialOrd)]
#[derive(Clone, Debug, Default, PartialEq, PartialOrd)]
pub struct Function {
pub name: Option<DustString>,
pub r#type: FunctionType,

View File

@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize};
use std::fmt::{self, Debug, Display, Formatter};
use crate::{Type, vm::ThreadData};
use crate::{Type, vm::Thread};
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
pub enum Value {
@ -50,6 +50,14 @@ impl Value {
Value::Concrete(ConcreteValue::String(string.into()))
}
pub fn as_concrete(&self) -> Option<&ConcreteValue> {
if let Value::Concrete(concrete_value) = self {
Some(concrete_value)
} else {
None
}
}
pub fn as_boolean(&self) -> Option<bool> {
if let Value::Concrete(ConcreteValue::Boolean(boolean)) = self {
Some(*boolean)
@ -117,109 +125,12 @@ impl Value {
pub fn r#type(&self) -> Type {
match self {
Value::Concrete(concrete_value) => concrete_value.r#type(),
Value::AbstractList(AbstractList { item_type, .. }) => {
Type::List(Box::new(item_type.clone()))
}
Value::Function(Function { r#type, .. }) => Type::Function(Box::new(r#type.clone())),
Value::AbstractList(AbstractList { item_type, .. }) => Type::List(*item_type),
Value::Function(Function { r#type, .. }) => Type::Function(r#type.clone()),
}
}
pub fn add(&self, other: &Value) -> Value {
let sum = match (self, other) {
(Value::Concrete(left), Value::Concrete(right)) => left.add(right),
_ => panic!("{}", ValueError::CannotAdd(self.clone(), other.clone())),
};
Value::Concrete(sum)
}
pub fn subtract(&self, other: &Value) -> Value {
let difference = match (self, other) {
(Value::Concrete(left), Value::Concrete(right)) => left.subtract(right),
_ => panic!(
"{}",
ValueError::CannotSubtract(self.clone(), other.clone())
),
};
Value::Concrete(difference)
}
pub fn multiply(&self, other: &Value) -> Result<Value, ValueError> {
match (self, other) {
(Value::Concrete(left), Value::Concrete(right)) => {
left.multiply(right).map(Value::Concrete)
}
_ => Err(ValueError::CannotMultiply(
self.to_owned(),
other.to_owned(),
)),
}
}
pub fn divide(&self, other: &Value) -> Result<Value, ValueError> {
match (self, other) {
(Value::Concrete(left), Value::Concrete(right)) => {
left.divide(right).map(Value::Concrete)
}
_ => Err(ValueError::CannotDivide(self.to_owned(), other.to_owned())),
}
}
pub fn modulo(&self, other: &Value) -> Result<Value, ValueError> {
match (self, other) {
(Value::Concrete(left), Value::Concrete(right)) => {
left.modulo(right).map(Value::Concrete)
}
_ => Err(ValueError::CannotModulo(self.to_owned(), other.to_owned())),
}
}
pub fn negate(&self) -> Value {
let concrete = match self {
Value::Concrete(concrete_value) => concrete_value.negate(),
_ => panic!("{}", ValueError::CannotNegate(self.clone())),
};
Value::Concrete(concrete)
}
pub fn not(&self) -> Result<Value, ValueError> {
match self {
Value::Concrete(concrete_value) => concrete_value.not().map(Value::Concrete),
_ => Err(ValueError::CannotNot(self.to_owned())),
}
}
pub fn equals(&self, other: &Value) -> bool {
match (self, other) {
(Value::Concrete(left), Value::Concrete(right)) => left.equals(right),
_ => panic!(
"{}",
ValueError::CannotCompare(self.to_owned(), other.to_owned())
),
}
}
pub fn less(&self, other: &Value) -> Result<Value, ValueError> {
match (self, other) {
(Value::Concrete(left), Value::Concrete(right)) => {
left.less_than(right).map(Value::Concrete)
}
_ => Err(ValueError::CannotCompare(self.to_owned(), other.to_owned())),
}
}
pub fn less_than_or_equals(&self, other: &Value) -> Result<Value, ValueError> {
match (self, other) {
(Value::Concrete(left), Value::Concrete(right)) => {
left.less_than_or_equals(right).map(Value::Concrete)
}
_ => Err(ValueError::CannotCompare(self.to_owned(), other.to_owned())),
}
}
pub fn display(&self, data: &ThreadData) -> DustString {
pub fn display(&self, data: &Thread) -> DustString {
match self {
Value::AbstractList(list) => list.display(data),
Value::Concrete(concrete_value) => concrete_value.display(),

View File

@ -32,9 +32,7 @@ impl RangeValue {
}
};
Type::Range {
r#type: Box::new(inner_type),
}
Type::Range(Box::new(inner_type))
}
}

View File

@ -0,0 +1,287 @@
use std::ops::Add;
use tracing::trace;
use crate::{
vm::{call_frame::RuntimeValue, Thread},
DustString, Instruction,
};
use super::Cache;
pub fn add_bytes(_: &mut usize, instruction: &Instruction, thread: &mut Thread, _: &mut Cache) {
let destination_index = instruction.a_field() as usize;
let left_index = instruction.b_field() as usize;
let right_index = instruction.c_field() as usize;
let current_frame = thread.current_frame_mut();
let left_value = current_frame.get_byte_from_register(left_index).clone();
let right_value = current_frame.get_byte_from_register(right_index).clone();
let sum = left_value.add(&right_value);
current_frame
.registers
.bytes
.get_mut(destination_index)
.as_value_mut()
.set_inner(sum);
}
pub fn add_characters(
_: &mut usize,
instruction: &Instruction,
thread: &mut Thread,
_: &mut Cache,
) {
let destination_index = instruction.a_field() as usize;
let left_index = instruction.b_field() as usize;
let left_is_constant = instruction.b_is_constant();
let right = instruction.c_field() as usize;
let right_is_constant = instruction.c_is_constant();
let current_frame = thread.current_frame_mut();
let left_value = if left_is_constant {
current_frame.get_character_constant(left_index)
} else {
current_frame.get_character_from_register(left_index)
};
let right_value = if right_is_constant {
current_frame.get_character_constant(right)
} else {
current_frame.get_character_from_register(right)
};
let concatenated = {
let mut concatenated = DustString::from(String::with_capacity(2));
concatenated.push(left_value.clone_inner());
concatenated.push(right_value.clone_inner());
RuntimeValue::Raw(concatenated)
};
current_frame
.registers
.strings
.get_mut(destination_index)
.set(concatenated);
}
pub fn add_floats(_: &mut usize, instruction: &Instruction, thread: &mut Thread, _: &mut Cache) {
let destination = instruction.a_field() as usize;
let left = instruction.b_field() as usize;
let left_is_constant = instruction.b_is_constant();
let right = instruction.c_field() as usize;
let right_is_constant = instruction.c_is_constant();
let current_frame = thread.current_frame_mut();
let left_value = if left_is_constant {
current_frame.get_float_constant(left)
} else {
current_frame.get_float_from_register(left)
}
.clone();
let right_value = if right_is_constant {
current_frame.get_float_constant(right)
} else {
current_frame.get_float_from_register(right)
}
.clone();
let sum = left_value.add(&right_value);
current_frame
.registers
.floats
.get_mut(destination)
.as_value_mut()
.set_inner(sum);
}
pub fn add_integers(_: &mut usize, instruction: &Instruction, thread: &mut Thread, _: &mut Cache) {
let destination = instruction.a_field() as usize;
let left = instruction.b_field() as usize;
let left_is_constant = instruction.b_is_constant();
let right = instruction.c_field() as usize;
let right_is_constant = instruction.c_is_constant();
let current_frame = thread.current_frame_mut();
let left_value = if left_is_constant {
current_frame.get_integer_constant(left)
} else {
current_frame.get_integer_from_register(left)
}
.clone();
let right_value = if right_is_constant {
current_frame.get_integer_constant(right)
} else {
current_frame.get_integer_from_register(right)
}
.clone();
let sum = left_value.add(&right_value);
current_frame
.registers
.integers
.get_mut(destination)
.as_value_mut()
.set_inner(sum);
}
pub fn add_strings(_: &mut usize, instruction: &Instruction, thread: &mut Thread, _: &mut Cache) {
let destination = instruction.a_field() as usize;
let left = instruction.b_field() as usize;
let left_is_constant = instruction.b_is_constant();
let right = instruction.c_field() as usize;
let right_is_constant = instruction.c_is_constant();
let current_frame = thread.current_frame_mut();
let left_value = if left_is_constant {
current_frame.get_string_constant(left)
} else {
current_frame.get_string_from_register(left)
};
let right_value = if right_is_constant {
current_frame.get_string_constant(right)
} else {
current_frame.get_string_from_register(right)
};
let concatenated = DustString::from(format!("{left_value}{right_value}"));
current_frame
.registers
.strings
.get_mut(destination)
.as_value_mut()
.set_inner(concatenated);
}
pub fn add_character_string(
_: &mut usize,
instruction: &Instruction,
thread: &mut Thread,
_: &mut Cache,
) {
let destination = instruction.a_field() as usize;
let left = instruction.b_field() as usize;
let left_is_constant = instruction.b_is_constant();
let right = instruction.c_field() as usize;
let right_is_constant = instruction.c_is_constant();
let current_frame = thread.current_frame_mut();
let left_value = if left_is_constant {
current_frame.get_character_constant(left)
} else {
current_frame.get_character_from_register(left)
};
let right_value = if right_is_constant {
current_frame.get_string_constant(right)
} else {
current_frame.get_string_from_register(right)
};
let concatenated = DustString::from(format!("{left_value}{right_value}"));
current_frame
.registers
.strings
.get_mut(destination)
.as_value_mut()
.set_inner(concatenated);
}
pub fn add_string_character(
_: &mut usize,
instruction: &Instruction,
thread: &mut Thread,
_: &mut Cache,
) {
let destination = instruction.a_field() as usize;
let left = instruction.b_field() as usize;
let left_is_constant = instruction.b_is_constant();
let right = instruction.c_field() as usize;
let right_is_constant = instruction.c_is_constant();
let current_frame = thread.current_frame_mut();
let left_value = if left_is_constant {
current_frame.get_string_constant(left)
} else {
current_frame.get_string_from_register(left)
};
let right_value = if right_is_constant {
current_frame.get_character_constant(right)
} else {
current_frame.get_character_from_register(right)
};
let concatenated = DustString::from(format!("{left_value}{right_value}"));
current_frame
.registers
.strings
.get_mut(destination)
.as_value_mut()
.set_inner(concatenated);
}
pub fn optimized_add_integer(
_: &mut usize,
instruction: &Instruction,
thread: &mut Thread,
cache: &mut Cache,
) {
if let Cache::IntegerMath([destination, left, right]) = cache {
trace!("OPTIMIZED_ADD using integer cache");
let sum = left.add(right);
*destination.borrow_mut() = sum;
} else {
let destination_index = instruction.a_field() as usize;
let left_index = instruction.b_field() as usize;
let left_is_constant = instruction.b_is_constant();
let right_index = instruction.c_field() as usize;
let right_is_constant = instruction.c_is_constant();
let current_frame = thread.current_frame_mut();
let left_value = if left_is_constant {
let value = current_frame.get_integer_constant_mut(left_index).to_rc();
current_frame.constants.integers[left_index] = value.clone();
value
} else {
let value = current_frame
.get_integer_from_register_mut(left_index)
.to_ref_cell();
current_frame.registers.integers[left_index].set(value.clone());
value
};
let right_value = if right_is_constant {
let value = current_frame.get_integer_constant_mut(right_index).to_rc();
current_frame.constants.integers[right_index] = value.clone();
value
} else {
let value = current_frame
.get_integer_from_register_mut(right_index)
.to_ref_cell();
current_frame.registers.integers[right_index].set(value.clone());
value
};
let sum = left_value.add(&right_value);
let destination = {
let mut value = current_frame
.get_integer_from_register_mut(destination_index)
.to_ref_cell();
value.set_inner(sum);
current_frame.registers.integers[destination_index].set(value.clone());
value
};
*cache = Cache::IntegerMath([destination, left_value, right_value]);
}
}

View File

@ -0,0 +1,217 @@
use tracing::trace;
use crate::{vm::Thread, Instruction};
use super::Cache;
pub fn equal_booleans(
ip: &mut usize,
instruction: &Instruction,
thread: &mut Thread,
_: &mut Cache,
) {
let left_index = instruction.b_field() as usize;
let right_index = instruction.c_field() as usize;
let comparator = instruction.d_field();
let current_frame = thread.current_frame_mut();
let left_value = current_frame.get_boolean_from_register(left_index);
let right_value = current_frame.get_boolean_from_register(right_index);
let is_equal = left_value == right_value;
if is_equal == comparator {
*ip += 1;
}
}
pub fn equal_bytes(ip: &mut usize, instruction: &Instruction, thread: &mut Thread, _: &mut Cache) {
let left = instruction.b_field() as usize;
let right = instruction.c_field() as usize;
let comparator = instruction.d_field();
let current_frame = thread.current_frame_mut();
let left_value = current_frame.get_byte_from_register(left);
let right_value = current_frame.get_byte_from_register(right);
let is_equal = left_value == right_value;
if is_equal == comparator {
*ip += 1;
}
}
pub fn equal_characters(
ip: &mut usize,
instruction: &Instruction,
thread: &mut Thread,
_: &mut Cache,
) {
let left_index = instruction.b_field() as usize;
let left_is_constant = instruction.b_is_constant();
let right_index = instruction.c_field() as usize;
let right_is_constant = instruction.c_is_constant();
let comparator = instruction.d_field();
let current_frame = thread.current_frame_mut();
let left_value = if left_is_constant {
current_frame.get_character_constant(left_index)
} else {
current_frame.get_character_from_register(left_index)
};
let right_value = if right_is_constant {
current_frame.get_character_constant(right_index)
} else {
current_frame.get_character_from_register(right_index)
};
let is_equal = left_value == right_value;
if is_equal == comparator {
*ip += 1;
}
}
pub fn equal_floats(ip: &mut usize, instruction: &Instruction, thread: &mut Thread, _: &mut Cache) {
let left = instruction.b_field() as usize;
let left_is_constant = instruction.b_is_constant();
let right = instruction.c_field() as usize;
let right_is_constant = instruction.c_is_constant();
let comparator = instruction.d_field();
let current_frame = thread.current_frame_mut();
let left_value = if left_is_constant {
current_frame.get_float_constant(left)
} else {
current_frame.get_float_from_register(left)
};
let right_value = if right_is_constant {
current_frame.get_float_constant(right)
} else {
current_frame.get_float_from_register(right)
};
let is_equal = left_value == right_value;
if is_equal == comparator {
*ip += 1;
}
}
pub fn equal_integers(
ip: &mut usize,
instruction: &Instruction,
thread: &mut Thread,
_: &mut Cache,
) {
let left = instruction.b_field() as usize;
let left_is_constant = instruction.b_is_constant();
let right = instruction.c_field() as usize;
let right_is_constant = instruction.c_is_constant();
let comparator = instruction.d_field();
let current_frame = thread.current_frame_mut();
let left_value = if left_is_constant {
current_frame.get_integer_constant(left)
} else {
current_frame.get_integer_from_register(left)
};
let right_value = if right_is_constant {
current_frame.get_integer_constant(right)
} else {
current_frame.get_integer_from_register(right)
};
let is_equal = left_value == right_value;
if is_equal == comparator {
*ip += 1;
}
}
pub fn equal_strings(
ip: &mut usize,
instruction: &Instruction,
thread: &mut Thread,
_: &mut Cache,
) {
let left = instruction.b_field() as usize;
let left_is_constant = instruction.b_is_constant();
let right = instruction.c_field() as usize;
let right_is_constant = instruction.c_is_constant();
let comparator = instruction.d_field();
let current_frame = thread.current_frame_mut();
let left_value = if left_is_constant {
current_frame.get_string_constant(left)
} else {
current_frame.get_string_from_register(left)
};
let right_value = if right_is_constant {
current_frame.get_string_constant(right)
} else {
current_frame.get_string_from_register(right)
};
let is_equal = left_value == right_value;
if is_equal == comparator {
*ip += 1;
}
}
pub fn optimized_equal_integers(
ip: &mut usize,
instruction: &Instruction,
thread: &mut Thread,
cache: &mut Cache,
) {
if let Cache::IntegerComparison([left, right]) = cache {
trace!("equal_INTEGERS_OPTIMIZED using cache");
let is_equal = left == right;
if is_equal {
*ip += 1;
}
} else {
let left_index = instruction.b_field() as usize;
let left_is_constant = instruction.b_is_constant();
let right_index = instruction.c_field() as usize;
let right_is_constant = instruction.c_is_constant();
let comparator = instruction.d_field();
let current_frame = thread.current_frame_mut();
let left_value = if left_is_constant {
let value = current_frame.get_integer_constant_mut(left_index).to_rc();
current_frame.constants.integers[left_index] = value.clone();
value
} else {
let value = current_frame
.get_integer_from_register_mut(left_index)
.to_ref_cell();
current_frame.registers.integers[left_index].set(value.clone());
value
};
let right_value = if right_is_constant {
let value = current_frame.get_integer_constant_mut(right_index).to_rc();
current_frame.constants.integers[right_index] = value.clone();
value
} else {
let value = current_frame
.get_integer_from_register_mut(right_index)
.to_ref_cell();
current_frame.registers.integers[right_index].set(value.clone());
value
};
let is_equal = left_value == right_value;
if is_equal == comparator {
*ip += 1;
}
*cache = Cache::IntegerComparison([left_value, right_value]);
}
}

View File

@ -0,0 +1,48 @@
use tracing::trace;
use crate::{vm::Thread, Instruction};
use super::Cache;
pub fn jump(ip: &mut usize, instruction: &Instruction, _: &mut Thread, _: &mut Cache) {
let offset = instruction.b_field() as usize;
let is_positive = instruction.c_field() != 0;
if is_positive {
trace!("JUMP +{}", offset);
} else {
trace!("JUMP -{}", offset);
}
if is_positive {
*ip += offset;
} else {
*ip -= offset + 1;
}
}
pub fn optimized_jump_forward(
ip: &mut usize,
instruction: &Instruction,
_: &mut Thread,
_: &mut Cache,
) {
let offset = instruction.b_field() as usize;
trace!("JUMP +{}", offset);
*ip += offset;
}
pub fn optimized_jump_backward(
ip: &mut usize,
instruction: &Instruction,
_: &mut Thread,
_: &mut Cache,
) {
let offset = instruction.b_field() as usize;
trace!("JUMP -{}", offset);
*ip -= offset + 1;
}

View File

@ -0,0 +1,212 @@
use tracing::trace;
use crate::{vm::Thread, Instruction};
use super::Cache;
pub fn less_booleans(
ip: &mut usize,
instruction: &Instruction,
thread: &mut Thread,
_: &mut Cache,
) {
let left_index = instruction.b_field() as usize;
let right_index = instruction.c_field() as usize;
let comparator = instruction.d_field();
let current_frame = thread.current_frame_mut();
let left_value = current_frame.get_boolean_from_register(left_index);
let right_value = current_frame.get_boolean_from_register(right_index);
let is_less_than = left_value < right_value;
if is_less_than == comparator {
*ip += 1;
}
}
pub fn less_bytes(ip: &mut usize, instruction: &Instruction, thread: &mut Thread, _: &mut Cache) {
let left = instruction.b_field() as usize;
let right = instruction.c_field() as usize;
let comparator = instruction.d_field();
let current_frame = thread.current_frame_mut();
let left_value = current_frame.get_byte_from_register(left);
let right_value = current_frame.get_byte_from_register(right);
let is_less_than = left_value < right_value;
if is_less_than == comparator {
*ip += 1;
}
}
pub fn less_characters(
ip: &mut usize,
instruction: &Instruction,
thread: &mut Thread,
_: &mut Cache,
) {
let left_index = instruction.b_field() as usize;
let left_is_constant = instruction.b_is_constant();
let right_index = instruction.c_field() as usize;
let right_is_constant = instruction.c_is_constant();
let comparator = instruction.d_field();
let current_frame = thread.current_frame_mut();
let left_value = if left_is_constant {
current_frame.get_character_constant(left_index)
} else {
current_frame.get_character_from_register(left_index)
};
let right_value = if right_is_constant {
current_frame.get_character_constant(right_index)
} else {
current_frame.get_character_from_register(right_index)
};
let is_less_than = left_value < right_value;
if is_less_than == comparator {
*ip += 1;
}
}
pub fn less_floats(ip: &mut usize, instruction: &Instruction, thread: &mut Thread, _: &mut Cache) {
let left = instruction.b_field() as usize;
let left_is_constant = instruction.b_is_constant();
let right = instruction.c_field() as usize;
let right_is_constant = instruction.c_is_constant();
let comparator = instruction.d_field();
let current_frame = thread.current_frame_mut();
let left_value = if left_is_constant {
current_frame.get_float_constant(left)
} else {
current_frame.get_float_from_register(left)
};
let right_value = if right_is_constant {
current_frame.get_float_constant(right)
} else {
current_frame.get_float_from_register(right)
};
let is_less_than = left_value < right_value;
if is_less_than == comparator {
*ip += 1;
}
}
pub fn less_integers(
ip: &mut usize,
instruction: &Instruction,
thread: &mut Thread,
_: &mut Cache,
) {
let left = instruction.b_field() as usize;
let left_is_constant = instruction.b_is_constant();
let right = instruction.c_field() as usize;
let right_is_constant = instruction.c_is_constant();
let comparator = instruction.d_field();
let current_frame = thread.current_frame_mut();
let left_value = if left_is_constant {
current_frame.get_integer_constant(left)
} else {
current_frame.get_integer_from_register(left)
};
let right_value = if right_is_constant {
current_frame.get_integer_constant(right)
} else {
current_frame.get_integer_from_register(right)
};
let is_less_than = left_value < right_value;
if is_less_than == comparator {
*ip += 1;
}
}
pub fn less_strings(ip: &mut usize, instruction: &Instruction, thread: &mut Thread, _: &mut Cache) {
let left = instruction.b_field() as usize;
let left_is_constant = instruction.b_is_constant();
let right = instruction.c_field() as usize;
let right_is_constant = instruction.c_is_constant();
let comparator = instruction.d_field();
let current_frame = thread.current_frame_mut();
let left_value = if left_is_constant {
current_frame.get_string_constant(left)
} else {
current_frame.get_string_from_register(left)
};
let right_value = if right_is_constant {
current_frame.get_string_constant(right)
} else {
current_frame.get_string_from_register(right)
};
let is_less_than = left_value < right_value;
if is_less_than == comparator {
*ip += 1;
}
}
pub fn optimized_less_integers(
ip: &mut usize,
instruction: &Instruction,
thread: &mut Thread,
cache: &mut Cache,
) {
if let Cache::IntegerComparison([left, right]) = cache {
trace!("OPTIMIZED_LESS using integer cache");
let is_less_than = left < right;
if is_less_than {
*ip += 1;
}
} else {
let left_index = instruction.b_field() as usize;
let left_is_constant = instruction.b_is_constant();
let right_index = instruction.c_field() as usize;
let right_is_constant = instruction.c_is_constant();
let comparator = instruction.d_field();
let current_frame = thread.current_frame_mut();
let left_value = if left_is_constant {
let value = current_frame.get_integer_constant_mut(left_index).to_rc();
current_frame.constants.integers[left_index] = value.clone();
value
} else {
let value = current_frame
.get_integer_from_register_mut(left_index)
.to_ref_cell();
current_frame.registers.integers[left_index].set(value.clone());
value
};
let right_value = if right_is_constant {
let value = current_frame.get_integer_constant_mut(right_index).to_rc();
current_frame.constants.integers[right_index] = value.clone();
value
} else {
let value = current_frame
.get_integer_from_register_mut(right_index)
.to_ref_cell();
current_frame.registers.integers[right_index].set(value.clone());
value
};
let is_less_than = left_value < right_value;
if is_less_than == comparator {
*ip += 1;
}
*cache = Cache::IntegerComparison([left_value, right_value]);
}
}

View File

@ -0,0 +1,227 @@
use tracing::trace;
use crate::{vm::Thread, Instruction};
use super::Cache;
pub fn less_equal_booleans(
ip: &mut usize,
instruction: &Instruction,
thread: &mut Thread,
_: &mut Cache,
) {
let left_index = instruction.b_field() as usize;
let right_index = instruction.c_field() as usize;
let comparator = instruction.d_field();
let current_frame = thread.current_frame_mut();
let left_value = current_frame.get_boolean_from_register(left_index);
let right_value = current_frame.get_boolean_from_register(right_index);
let is_less_than_or_equal = left_value <= right_value;
if is_less_than_or_equal == comparator {
*ip += 1;
}
}
pub fn less_equal_bytes(
ip: &mut usize,
instruction: &Instruction,
thread: &mut Thread,
_: &mut Cache,
) {
let left = instruction.b_field() as usize;
let right = instruction.c_field() as usize;
let comparator = instruction.d_field();
let current_frame = thread.current_frame_mut();
let left_value = current_frame.get_byte_from_register(left);
let right_value = current_frame.get_byte_from_register(right);
let is_less_than_or_equal = left_value <= right_value;
if is_less_than_or_equal == comparator {
*ip += 1;
}
}
pub fn less_equal_characters(
ip: &mut usize,
instruction: &Instruction,
thread: &mut Thread,
_: &mut Cache,
) {
let left_index = instruction.b_field() as usize;
let left_is_constant = instruction.b_is_constant();
let right_index = instruction.c_field() as usize;
let right_is_constant = instruction.c_is_constant();
let comparator = instruction.d_field();
let current_frame = thread.current_frame_mut();
let left_value = if left_is_constant {
current_frame.get_character_constant(left_index)
} else {
current_frame.get_character_from_register(left_index)
};
let right_value = if right_is_constant {
current_frame.get_character_constant(right_index)
} else {
current_frame.get_character_from_register(right_index)
};
let is_less_than_or_equal = left_value <= right_value;
if is_less_than_or_equal == comparator {
*ip += 1;
}
}
pub fn less_equal_floats(
ip: &mut usize,
instruction: &Instruction,
thread: &mut Thread,
_: &mut Cache,
) {
let left = instruction.b_field() as usize;
let left_is_constant = instruction.b_is_constant();
let right = instruction.c_field() as usize;
let right_is_constant = instruction.c_is_constant();
let comparator = instruction.d_field();
let current_frame = thread.current_frame_mut();
let left_value = if left_is_constant {
current_frame.get_float_constant(left)
} else {
current_frame.get_float_from_register(left)
};
let right_value = if right_is_constant {
current_frame.get_float_constant(right)
} else {
current_frame.get_float_from_register(right)
};
let is_less_than_or_equal = left_value <= right_value;
if is_less_than_or_equal == comparator {
*ip += 1;
}
}
pub fn less_equal_integers(
ip: &mut usize,
instruction: &Instruction,
thread: &mut Thread,
_: &mut Cache,
) {
let left = instruction.b_field() as usize;
let left_is_constant = instruction.b_is_constant();
let right = instruction.c_field() as usize;
let right_is_constant = instruction.c_is_constant();
let comparator = instruction.d_field();
let current_frame = thread.current_frame_mut();
let left_value = if left_is_constant {
current_frame.get_integer_constant(left)
} else {
current_frame.get_integer_from_register(left)
};
let right_value = if right_is_constant {
current_frame.get_integer_constant(right)
} else {
current_frame.get_integer_from_register(right)
};
let is_less_than_or_equal = left_value <= right_value;
if is_less_than_or_equal == comparator {
*ip += 1;
}
}
pub fn less_equal_strings(
ip: &mut usize,
instruction: &Instruction,
thread: &mut Thread,
_: &mut Cache,
) {
let left = instruction.b_field() as usize;
let left_is_constant = instruction.b_is_constant();
let right = instruction.c_field() as usize;
let right_is_constant = instruction.c_is_constant();
let comparator = instruction.d_field();
let current_frame = thread.current_frame_mut();
let left_value = if left_is_constant {
current_frame.get_string_constant(left)
} else {
current_frame.get_string_from_register(left)
};
let right_value = if right_is_constant {
current_frame.get_string_constant(right)
} else {
current_frame.get_string_from_register(right)
};
let is_less_than_or_equal = left_value <= right_value;
if is_less_than_or_equal == comparator {
*ip += 1;
}
}
pub fn optimized_less_equal_integers(
ip: &mut usize,
instruction: &Instruction,
thread: &mut Thread,
cache: &mut Cache,
) {
if let Cache::IntegerComparison([left, right]) = cache {
trace!("LESS_INTEGERS_OPTIMIZED using cache");
let is_less_than_or_equal = left <= right;
if is_less_than_or_equal {
*ip += 1;
}
} else {
let left_index = instruction.b_field() as usize;
let left_is_constant = instruction.b_is_constant();
let right_index = instruction.c_field() as usize;
let right_is_constant = instruction.c_is_constant();
let comparator = instruction.d_field();
let current_frame = thread.current_frame_mut();
let left_value = if left_is_constant {
let value = current_frame.get_integer_constant_mut(left_index).to_rc();
current_frame.constants.integers[left_index] = value.clone();
value
} else {
let value = current_frame
.get_integer_from_register_mut(left_index)
.to_ref_cell();
current_frame.registers.integers[left_index].set(value.clone());
value
};
let right_value = if right_is_constant {
let value = current_frame.get_integer_constant_mut(right_index).to_rc();
current_frame.constants.integers[right_index] = value.clone();
value
} else {
let value = current_frame
.get_integer_from_register_mut(right_index)
.to_ref_cell();
current_frame.registers.integers[right_index].set(value.clone());
value
};
let is_less_than_or_equal = left_value <= right_value;
if is_less_than_or_equal == comparator {
*ip += 1;
}
*cache = Cache::IntegerComparison([left_value, right_value]);
}
}

View File

@ -0,0 +1,623 @@
mod add;
mod equal;
mod jump;
mod less;
mod less_equal;
use add::{
add_bytes, add_character_string, add_characters, add_floats, add_integers,
add_string_character, add_strings, optimized_add_integer,
};
use equal::{
equal_booleans, equal_bytes, equal_characters, equal_floats, equal_integers, equal_strings,
optimized_equal_integers,
};
use jump::{jump, optimized_jump_backward, optimized_jump_forward};
use less::{
less_booleans, less_bytes, less_characters, less_floats, less_integers, less_strings,
optimized_less_integers,
};
use less_equal::{
less_equal_booleans, less_equal_bytes, less_equal_characters, less_equal_floats,
less_equal_integers, less_equal_strings, optimized_less_equal_integers,
};
use tracing::info;
use std::fmt::{self, Display, Formatter};
use crate::{instruction::TypeCode, AbstractList, ConcreteValue, Instruction, Operation, Value};
use super::{call_frame::RuntimeValue, thread::Thread, Pointer};
pub type ActionLogic = fn(&mut usize, &Instruction, &mut Thread, &mut Cache);
#[derive(Debug)]
pub struct ActionSequence {
actions: Vec<Action>,
}
impl ActionSequence {
pub fn new<T: IntoIterator<Item = Instruction> + DoubleEndedIterator<Item = Instruction>>(
instructions: T,
) -> Self {
let mut actions = Vec::new();
let mut instructions_reversed = instructions.rev();
while let Some(instruction) = instructions_reversed.next() {
if instruction.operation() == Operation::JUMP {
let backward_offset = instruction.b_field() as usize;
let is_positive = instruction.c_field() != 0;
if !is_positive {
let mut loop_actions = Vec::with_capacity(backward_offset);
let jump_action = Action::optimized(instruction);
loop_actions.push(jump_action);
for _ in 0..backward_offset {
let instruction = instructions_reversed.next().unwrap();
let action = Action::optimized(instruction);
loop_actions.push(action);
}
loop_actions.reverse();
let cache = Cache::LoopActions(ActionSequence {
actions: loop_actions,
});
let action = Action {
instruction,
logic: r#loop,
cache,
};
actions.push(action);
continue;
}
}
let action = Action::unoptimized(instruction);
actions.push(action);
}
actions.reverse();
ActionSequence { actions }
}
pub fn run(&mut self, thread: &mut Thread) {
let mut local_ip = 0;
while local_ip < self.actions.len() {
let action = if cfg!(debug_assertions) {
self.actions.get_mut(local_ip).unwrap()
} else {
unsafe { self.actions.get_unchecked_mut(local_ip) }
};
local_ip += 1;
info!("Run {action}");
(action.logic)(
&mut local_ip,
&action.instruction,
thread,
&mut action.cache,
);
}
}
}
impl Display for ActionSequence {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "[")?;
for (index, action) in self.actions.iter().enumerate() {
if index > 0 {
write!(f, ", ")?;
}
write!(f, "{action}")?;
}
write!(f, "]")
}
}
#[derive(Debug)]
pub struct Action {
instruction: Instruction,
logic: ActionLogic,
cache: Cache,
}
#[derive(Debug)]
pub enum Cache {
Empty,
IntegerMath([RuntimeValue<i64>; 3]),
IntegerComparison([RuntimeValue<i64>; 2]),
LoopActions(ActionSequence),
}
impl Action {
pub fn unoptimized(instruction: Instruction) -> Self {
let logic = match instruction.operation() {
Operation::POINT => point,
Operation::CLOSE => close,
Operation::LOAD_ENCODED => load_encoded,
Operation::LOAD_CONSTANT => load_constant,
Operation::LOAD_LIST => load_list,
Operation::LOAD_FUNCTION => load_function,
Operation::LOAD_SELF => load_self,
Operation::ADD => match (instruction.b_type(), instruction.c_type()) {
(TypeCode::INTEGER, TypeCode::INTEGER) => add_integers,
(TypeCode::FLOAT, TypeCode::FLOAT) => add_floats,
(TypeCode::BYTE, TypeCode::BYTE) => add_bytes,
(TypeCode::STRING, TypeCode::STRING) => add_strings,
(TypeCode::CHARACTER, TypeCode::CHARACTER) => add_characters,
(TypeCode::STRING, TypeCode::CHARACTER) => add_string_character,
(TypeCode::CHARACTER, TypeCode::STRING) => add_character_string,
_ => unreachable!(),
},
Operation::SUBTRACT => subtract,
Operation::MULTIPLY => multiply,
Operation::DIVIDE => divide,
Operation::MODULO => modulo,
Operation::NEGATE => negate,
Operation::NOT => not,
Operation::EQUAL => match (instruction.b_type(), instruction.c_type()) {
(TypeCode::BOOLEAN, TypeCode::BOOLEAN) => equal_booleans,
(TypeCode::BYTE, TypeCode::BYTE) => equal_bytes,
(TypeCode::CHARACTER, TypeCode::CHARACTER) => equal_characters,
(TypeCode::FLOAT, TypeCode::FLOAT) => equal_floats,
(TypeCode::INTEGER, TypeCode::INTEGER) => equal_integers,
(TypeCode::STRING, TypeCode::STRING) => equal_strings,
_ => todo!(),
},
Operation::LESS => match (instruction.b_type(), instruction.c_type()) {
(TypeCode::BOOLEAN, TypeCode::BOOLEAN) => less_booleans,
(TypeCode::BYTE, TypeCode::BYTE) => less_bytes,
(TypeCode::CHARACTER, TypeCode::CHARACTER) => less_characters,
(TypeCode::FLOAT, TypeCode::FLOAT) => less_floats,
(TypeCode::INTEGER, TypeCode::INTEGER) => less_integers,
(TypeCode::STRING, TypeCode::STRING) => less_strings,
_ => todo!(),
},
Operation::LESS_EQUAL => match (instruction.b_type(), instruction.c_type()) {
(TypeCode::BOOLEAN, TypeCode::BOOLEAN) => less_equal_booleans,
(TypeCode::BYTE, TypeCode::BYTE) => less_equal_bytes,
(TypeCode::CHARACTER, TypeCode::CHARACTER) => less_equal_characters,
(TypeCode::FLOAT, TypeCode::FLOAT) => less_equal_floats,
(TypeCode::INTEGER, TypeCode::INTEGER) => less_equal_integers,
(TypeCode::STRING, TypeCode::STRING) => less_equal_strings,
_ => todo!(),
},
Operation::TEST => test,
Operation::TEST_SET => test_set,
Operation::CALL => call,
Operation::CALL_NATIVE => call_native,
Operation::JUMP => jump,
Operation::RETURN => r#return,
_ => todo!(),
};
Action {
instruction,
logic,
cache: Cache::Empty,
}
}
pub fn optimized(instruction: Instruction) -> Self {
let logic = match instruction.operation() {
Operation::JUMP => match instruction.c_field() {
0 => optimized_jump_backward,
_ => optimized_jump_forward,
},
Operation::ADD => match (instruction.b_type(), instruction.c_type()) {
(TypeCode::INTEGER, TypeCode::INTEGER) => optimized_add_integer,
_ => todo!(),
},
Operation::EQUAL => match (instruction.b_type(), instruction.c_type()) {
(TypeCode::INTEGER, TypeCode::INTEGER) => optimized_equal_integers,
_ => todo!(),
},
Operation::LESS => match (instruction.b_type(), instruction.c_type()) {
(TypeCode::INTEGER, TypeCode::INTEGER) => optimized_less_integers,
_ => todo!(),
},
Operation::LESS_EQUAL => match (instruction.b_type(), instruction.c_type()) {
(TypeCode::INTEGER, TypeCode::INTEGER) => optimized_less_equal_integers,
_ => todo!(),
},
_ => todo!(),
};
Action {
instruction,
logic,
cache: Cache::Empty,
}
}
}
impl Display for Action {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if let Cache::LoopActions(actions) = &self.cache {
write!(f, "LOOP: {actions}")?;
} else {
write!(f, "{}", self.instruction.operation())?;
}
Ok(())
}
}
fn r#loop(_: &mut usize, _: &Instruction, thread: &mut Thread, cache: &mut Cache) {
if let Cache::LoopActions(actions) = cache {
actions.run(thread);
}
}
fn point(_: &mut usize, instruction: &Instruction, thread: &mut Thread, _: &mut Cache) {
todo!()
}
fn close(_: &mut usize, instruction: &Instruction, thread: &mut Thread, _: &mut Cache) {
todo!()
}
fn load_encoded(ip: &mut usize, instruction: &Instruction, thread: &mut Thread, _: &mut Cache) {
let destination = instruction.a_field();
let value_type = instruction.b_type();
let jump_next = instruction.c_field() != 0;
match value_type {
TypeCode::BOOLEAN => {
let value = instruction.b_field() != 0;
thread
.current_frame_mut()
.registers
.booleans
.get_mut(destination as usize)
.as_value_mut()
.set_inner(value);
}
TypeCode::BYTE => {
let value = instruction.b_field() as u8;
thread
.current_frame_mut()
.registers
.bytes
.get_mut(destination as usize)
.as_value_mut()
.set_inner(value);
}
_ => unreachable!(),
}
if jump_next {
*ip += 1;
}
}
fn load_constant(ip: &mut usize, instruction: &Instruction, thread: &mut Thread, _: &mut Cache) {
let destination = instruction.a_field() as usize;
let constant_index = instruction.b_field() as usize;
let constant_type = instruction.b_type();
let jump_next = instruction.c_field() != 0;
let current_frame = thread.current_frame_mut();
match constant_type {
TypeCode::CHARACTER => {
let constant = current_frame.get_character_constant(constant_index).clone();
current_frame
.registers
.characters
.get_mut(destination)
.set(constant);
}
TypeCode::FLOAT => {
let constant = current_frame.get_float_constant(constant_index).clone();
current_frame
.registers
.floats
.get_mut(destination)
.set(constant);
}
TypeCode::INTEGER => {
let constant = current_frame.get_integer_constant(constant_index).clone();
current_frame
.registers
.integers
.get_mut(destination)
.set(constant);
}
TypeCode::STRING => {
let constant = current_frame.get_string_constant(constant_index).clone();
current_frame
.registers
.strings
.get_mut(destination)
.set(constant);
}
_ => unreachable!(),
}
if jump_next {
*ip += 1;
}
}
fn load_list(ip: &mut usize, instruction: &Instruction, thread: &mut Thread, _: &mut Cache) {
let destination = instruction.a_field() as usize;
let start_register = instruction.b_field() as usize;
let item_type = instruction.b_type();
let end_register = instruction.c_field() as usize;
let jump_next = instruction.d_field();
let current_frame = thread.current_frame_mut();
let mut item_pointers = Vec::with_capacity(end_register - start_register + 1);
match item_type {
TypeCode::BOOLEAN => {
for register_index in start_register..=end_register {
let register_is_closed = current_frame.registers.booleans.is_closed(register_index);
if register_is_closed {
continue;
}
item_pointers.push(Pointer::Register(register_index));
}
}
TypeCode::BYTE => {
for register_index in start_register..=end_register {
let register_is_closed = current_frame.registers.bytes.is_closed(register_index);
if register_is_closed {
continue;
}
item_pointers.push(Pointer::Register(register_index));
}
}
TypeCode::CHARACTER => {
for register_index in start_register..=end_register {
let register_is_closed =
current_frame.registers.characters.is_closed(register_index);
if register_is_closed {
continue;
}
item_pointers.push(Pointer::Register(register_index));
}
}
TypeCode::FLOAT => {
for register_index in start_register..=end_register {
let register_is_closed = current_frame.registers.floats.is_closed(register_index);
if register_is_closed {
continue;
}
item_pointers.push(Pointer::Register(register_index));
}
}
TypeCode::INTEGER => {
for register_index in start_register..=end_register {
let register_is_closed = current_frame.registers.integers.is_closed(register_index);
if register_is_closed {
continue;
}
item_pointers.push(Pointer::Register(register_index));
}
}
TypeCode::STRING => {
for register_index in start_register..=end_register {
let register_is_closed = current_frame.registers.strings.is_closed(register_index);
if register_is_closed {
continue;
}
item_pointers.push(Pointer::Register(register_index));
}
}
_ => unreachable!(),
}
let list = RuntimeValue::Raw(AbstractList {
item_type,
item_pointers,
});
current_frame.registers.lists.get_mut(destination).set(list);
if jump_next {
*ip += 1;
}
}
fn load_function(_: &mut usize, _: &Instruction, _: &mut Thread, _: &mut Cache) {
todo!()
}
fn load_self(_: &mut usize, _: &Instruction, _: &mut Thread, _: &mut Cache) {
todo!()
}
fn subtract(_: &mut usize, instruction: &Instruction, thread: &mut Thread, _: &mut Cache) {
todo!()
}
fn multiply(_: &mut usize, instruction: &Instruction, thread: &mut Thread, _: &mut Cache) {
todo!()
}
fn divide(_: &mut usize, instruction: &Instruction, thread: &mut Thread, _: &mut Cache) {
todo!()
}
fn modulo(_: &mut usize, instruction: &Instruction, thread: &mut Thread, _: &mut Cache) {
todo!()
}
fn test(ip: &mut usize, instruction: &Instruction, thread: &mut Thread, _: &mut Cache) {
todo!()
}
fn test_set(_: &mut usize, _: &Instruction, _: &mut Thread, _: &mut Cache) {
todo!()
}
fn negate(_: &mut usize, _: &Instruction, _: &mut Thread, _: &mut Cache) {
todo!()
}
fn not(_: &mut usize, _: &Instruction, _: &mut Thread, _: &mut Cache) {
todo!()
}
fn call(_: &mut usize, _: &Instruction, _: &mut Thread, _: &mut Cache) {
todo!()
}
fn call_native(_: &mut usize, _: &Instruction, _: &mut Thread, _: &mut Cache) {
todo!()
}
fn r#return(_: &mut usize, instruction: &Instruction, thread: &mut Thread, _: &mut Cache) {
let should_return_value = instruction.b_field() != 0;
let return_register = instruction.c_field() as usize;
let return_type = instruction.b_type();
let current_frame = thread.current_frame();
// if should_return_value {
// match return_type {
// TypeCode::BOOLEAN => {
// let return_value = current_frame
// .get_boolean_from_register(return_register)
// .clone_inner();
// thread.return_value = Some(Value::boolean(return_value));
// }
// TypeCode::BYTE => {
// let return_value = current_frame
// .get_byte_from_register(return_register)
// .clone_inner();
// thread.return_value = Some(Value::byte(return_value));
// }
// TypeCode::CHARACTER => {
// let return_value = current_frame
// .get_character_from_register(return_register)
// .clone_inner();
// thread.return_value = Some(Value::character(return_value));
// }
// TypeCode::FLOAT => {
// let return_value = current_frame
// .get_float_from_register(return_register)
// .clone_inner();
// thread.return_value = Some(Value::float(return_value));
// }
// TypeCode::INTEGER => {
// let return_value = current_frame
// .get_integer_from_register(return_register)
// .clone_inner();
// thread.return_value = Some(Value::integer(return_value));
// }
// TypeCode::STRING => {
// let return_value = current_frame
// .get_string_from_register(return_register)
// .clone_inner();
// thread.return_value = Some(Value::string(return_value));
// }
// TypeCode::LIST => {
// let abstract_list = current_frame
// .get_list_from_register(return_register)
// .clone_inner();
// let mut concrete_list = Vec::with_capacity(abstract_list.item_pointers.len());
// match abstract_list.item_type {
// TypeCode::BOOLEAN => {
// for pointer in abstract_list.item_pointers {
// let boolean = current_frame
// .get_boolean_from_pointer(&pointer)
// .clone_inner();
// let value = ConcreteValue::Boolean(boolean);
// concrete_list.push(value);
// }
// }
// TypeCode::BYTE => {
// for pointer in abstract_list.item_pointers {
// let byte = current_frame.get_byte_from_pointer(&pointer).clone_inner();
// let value = ConcreteValue::Byte(byte);
// concrete_list.push(value);
// }
// }
// TypeCode::CHARACTER => {
// for pointer in abstract_list.item_pointers {
// let character = current_frame
// .get_character_from_pointer(&pointer)
// .clone_inner();
// let value = ConcreteValue::Character(character);
// concrete_list.push(value);
// }
// }
// TypeCode::FLOAT => {
// for pointer in abstract_list.item_pointers {
// let float =
// current_frame.get_float_from_pointer(&pointer).clone_inner();
// let value = ConcreteValue::Float(float);
// concrete_list.push(value);
// }
// }
// TypeCode::INTEGER => {
// for pointer in abstract_list.item_pointers {
// let integer = current_frame
// .get_integer_from_pointer(&pointer)
// .clone_inner();
// let value = ConcreteValue::Integer(integer);
// concrete_list.push(value);
// }
// }
// TypeCode::STRING => {
// for pointer in abstract_list.item_pointers {
// let string = current_frame
// .get_string_from_pointer(&pointer)
// .clone_inner();
// let value = ConcreteValue::String(string);
// concrete_list.push(value);
// }
// }
// _ => todo!(),
// }
// thread.return_value = Some(Value::Concrete(ConcreteValue::list(
// concrete_list,
// abstract_list.item_type,
// )));
// }
// _ => unreachable!(),
// }
// }
}

View File

@ -0,0 +1,317 @@
use std::{
fmt::{self, Debug, Display, Formatter},
ops::{Index, IndexMut, RangeInclusive},
sync::Arc,
};
use smallvec::SmallVec;
use crate::{AbstractList, Chunk, DustString, Function};
#[derive(Debug)]
pub struct CallFrame {
pub chunk: Arc<Chunk>,
pub ip: usize,
pub return_register: u16,
pub registers: RegisterTable,
}
impl CallFrame {
pub fn new(chunk: Arc<Chunk>, return_register: u16) -> Self {
let registers = RegisterTable {
booleans: RegisterList::new(chunk.boolean_register_count as usize),
bytes: RegisterList::new(chunk.byte_register_count as usize),
characters: RegisterList::new(chunk.character_register_count as usize),
floats: RegisterList::new(chunk.float_register_count as usize),
integers: RegisterList::new(chunk.integer_register_count as usize),
strings: RegisterList::new(chunk.string_register_count as usize),
lists: RegisterList::new(chunk.list_register_count as usize),
functions: RegisterList::new(chunk.function_register_count as usize),
};
Self {
chunk,
ip: 0,
return_register,
registers,
}
}
pub fn get_boolean_from_pointer(&self, pointer: Pointer) -> bool {
match pointer {
Pointer::Register(register_index) => {
*self.registers.booleans.get(register_index).as_value()
}
Pointer::Constant(_) => panic!("Attempted to get boolean from constant pointer"),
}
}
pub fn get_byte_from_pointer(&self, pointer: Pointer) -> u8 {
match pointer {
Pointer::Register(register_index) => {
*self.registers.bytes.get(register_index).as_value()
}
Pointer::Constant(_) => panic!("Attempted to get byte from constant pointer"),
}
}
pub fn get_character_from_pointer(&self, pointer: Pointer) -> char {
match pointer {
Pointer::Register(register_index) => {
*self.registers.characters.get(register_index).as_value()
}
Pointer::Constant(constant_index) => self.get_character_constant(constant_index),
}
}
pub fn get_character_constant(&self, constant_index: usize) -> char {
if cfg!(debug_assertions) {
*self.chunk.character_constants.get(constant_index).unwrap()
} else {
unsafe { *self.chunk.character_constants.get_unchecked(constant_index) }
}
}
pub fn get_float_from_pointer(&self, pointer: Pointer) -> f64 {
match pointer {
Pointer::Register(register_index) => {
*self.registers.floats.get(register_index).as_value()
}
Pointer::Constant(constant_index) => self.get_float_constant(constant_index),
}
}
pub fn get_float_constant(&self, constant_index: usize) -> f64 {
if cfg!(debug_assertions) {
*self.chunk.float_constants.get(constant_index).unwrap()
} else {
unsafe { *self.chunk.float_constants.get_unchecked(constant_index) }
}
}
pub fn get_integer_from_pointer(&self, pointer: Pointer) -> i64 {
match pointer {
Pointer::Register(register_index) => {
*self.registers.integers.get(register_index).as_value()
}
Pointer::Constant(constant_index) => self.get_integer_constant(constant_index),
}
}
pub fn get_integer_constant(&self, constant_index: usize) -> i64 {
if cfg!(debug_assertions) {
*self.chunk.integer_constants.get(constant_index).unwrap()
} else {
unsafe { *self.chunk.integer_constants.get_unchecked(constant_index) }
}
}
pub fn get_string_from_pointer(&self, pointer: Pointer) -> &DustString {
match pointer {
Pointer::Register(register_index) => {
self.registers.strings.get(register_index).as_value()
}
Pointer::Constant(constant_index) => self.get_string_constant(constant_index),
}
}
pub fn get_string_constant(&self, constant_index: usize) -> &DustString {
if cfg!(debug_assertions) {
self.chunk.string_constants.get(constant_index).unwrap()
} else {
unsafe { self.chunk.string_constants.get_unchecked(constant_index) }
}
}
pub fn get_list_from_pointer(&self, pointer: &Pointer) -> &AbstractList {
match pointer {
Pointer::Register(register_index) => {
self.registers.lists.get(*register_index).as_value()
}
Pointer::Constant(_) => panic!("Attempted to get list from constant pointer"),
}
}
pub fn get_function_from_pointer(&self, pointer: &Pointer) -> &Function {
match pointer {
Pointer::Register(register_index) => {
self.registers.functions.get(*register_index).as_value()
}
Pointer::Constant(_) => panic!("Attempted to get function from constant pointer"),
}
}
}
impl Display for CallFrame {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"FunctionCall: {} | IP: {}",
self.chunk
.name
.as_ref()
.unwrap_or(&DustString::from("anonymous")),
self.ip,
)
}
}
#[derive(Debug)]
pub struct RegisterTable {
pub booleans: RegisterList<bool>,
pub bytes: RegisterList<u8>,
pub characters: RegisterList<char>,
pub floats: RegisterList<f64>,
pub integers: RegisterList<i64>,
pub strings: RegisterList<DustString>,
pub lists: RegisterList<AbstractList>,
pub functions: RegisterList<Function>,
}
#[derive(Debug)]
pub struct RegisterList<T, const STACK_LEN: usize = 64> {
pub registers: SmallVec<[Register<T>; STACK_LEN]>,
}
impl<T, const STACK_LEN: usize> RegisterList<T, STACK_LEN>
where
T: Clone + Default,
{
pub fn new(length: usize) -> Self {
let mut registers = SmallVec::with_capacity(length);
for _ in 0..length {
registers.push(Register::default());
}
Self { registers }
}
pub fn get(&self, index: usize) -> &Register<T> {
if cfg!(debug_assertions) {
self.registers.get(index).unwrap()
} else {
unsafe { self.registers.get_unchecked(index) }
}
}
pub fn get_many_mut(&mut self, indices: RangeInclusive<usize>) -> &mut [Register<T>] {
let registers = if cfg!(debug_assertions) {
self.registers.get_disjoint_mut([indices]).unwrap()
} else {
unsafe { self.registers.get_disjoint_unchecked_mut([indices]) }
};
registers[0]
}
pub fn get_mut(&mut self, index: usize) -> &mut Register<T> {
if cfg!(debug_assertions) {
let length = self.registers.len();
self.registers
.get_mut(index)
.unwrap_or_else(|| panic!("Index out of bounds: {index}. Length is {length}"))
} else {
unsafe { self.registers.get_unchecked_mut(index) }
}
}
pub fn set_to_new_register(&mut self, index: usize, new_value: T) {
assert!(index < self.registers.len(), "Register index out of bounds");
self.registers[index] = Register::value(new_value)
}
pub fn close(&mut self, index: usize) {
if cfg!(debug_assertions) {
self.registers.get_mut(index).unwrap().close()
} else {
unsafe { self.registers.get_unchecked_mut(index).close() }
}
}
pub fn is_closed(&self, index: usize) -> bool {
if cfg!(debug_assertions) {
self.registers.get(index).unwrap().is_closed()
} else {
unsafe { self.registers.get_unchecked(index).is_closed() }
}
}
}
impl<T> Index<usize> for RegisterList<T> {
type Output = Register<T>;
fn index(&self, index: usize) -> &Self::Output {
&self.registers[index]
}
}
impl<T> IndexMut<usize> for RegisterList<T> {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.registers[index]
}
}
#[derive(Clone, Copy, Debug)]
pub struct Register<T> {
value: T,
is_closed: bool,
}
impl<T> Register<T> {
pub fn value(value: T) -> Self {
Self {
value,
is_closed: false,
}
}
pub fn is_closed(&self) -> bool {
self.is_closed
}
pub fn close(&mut self) {
self.is_closed = true;
}
pub fn set(&mut self, new_value: T) {
self.value = new_value;
}
pub fn as_value(&self) -> &T {
&self.value
}
pub fn as_value_mut(&mut self) -> &mut T {
&mut self.value
}
}
impl<T: Copy> Register<T> {
pub fn copy_value(&self) -> T {
self.value
}
}
impl<T: Clone> Register<T> {
pub fn clone_value(&self) -> T {
self.value.clone()
}
}
impl<T: Default> Default for Register<T> {
fn default() -> Self {
Self {
value: Default::default(),
is_closed: false,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum Pointer {
Register(usize),
Constant(usize),
}

View File

@ -1,44 +0,0 @@
use std::{
fmt::{self, Debug, Display, Formatter},
sync::Arc,
};
use crate::{Chunk, DustString};
use super::Register;
#[derive(Debug)]
pub struct FunctionCall {
pub chunk: Arc<Chunk>,
pub ip: usize,
pub return_register: u16,
pub registers: Vec<Register>,
}
impl FunctionCall {
pub fn new(chunk: Arc<Chunk>, return_register: u16) -> Self {
let register_count = chunk.register_count;
Self {
chunk,
ip: 0,
return_register,
registers: vec![Register::Empty; register_count],
}
}
}
impl Display for FunctionCall {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"FunctionCall: {} | IP: {} | Registers: {}",
self.chunk
.name
.as_ref()
.unwrap_or(&DustString::from("anonymous")),
self.ip,
self.registers.len()
)
}
}

View File

@ -1,25 +1,17 @@
//! Virtual machine and errors
mod function_call;
mod run_action;
mod stack;
// mod action;
mod call_frame;
mod thread;
use std::{
fmt::{self, Debug, Display, Formatter},
sync::Arc,
thread::Builder,
};
use std::{sync::Arc, thread::Builder};
pub use function_call::FunctionCall;
pub use run_action::RunAction;
pub(crate) use run_action::get_next_action;
pub use stack::Stack;
pub use thread::{Thread, ThreadData};
pub use call_frame::{CallFrame, Pointer, Register, RegisterTable};
pub use thread::Thread;
use crossbeam_channel::bounded;
use tracing::{Level, span};
use tracing::{span, Level};
use crate::{Chunk, DustError, Value, compile};
use crate::{compile, Chunk, DustError, Value};
pub fn run(source: &str) -> Result<Option<Value>, DustError> {
let chunk = compile(source)?;
@ -40,20 +32,22 @@ impl Vm {
pub fn run(self) -> Option<Value> {
let span = span!(Level::INFO, "Run");
let _enter = span.enter();
let thread_name = self
.main_chunk
.name
.as_ref()
.map(|name| name.to_string())
.unwrap_or_else(|| "anonymous".to_string());
let mut main_thread = Thread::new(Arc::new(self.main_chunk));
let (tx, rx) = bounded(1);
Builder::new()
.name(thread_name)
.spawn(move || {
let value_option = main_thread.run();
let _ = tx.send(value_option);
let main_chunk = Arc::new(self.main_chunk);
let main_thread = Thread::new(main_chunk);
let return_value = main_thread.run();
let _ = tx.send(return_value);
})
.unwrap()
.join()
@ -62,39 +56,3 @@ impl Vm {
rx.recv().unwrap_or(None)
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Register {
Empty,
Value(Value),
Pointer(Pointer),
}
impl Display for Register {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Empty => write!(f, "empty"),
Self::Value(value) => write!(f, "{}", value),
Self::Pointer(pointer) => write!(f, "{}", pointer),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum Pointer {
Register(u16),
Constant(u16),
Stack(usize, u16),
}
impl Display for Pointer {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Register(index) => write!(f, "PR{}", index),
Self::Constant(index) => write!(f, "PC{}", index),
Self::Stack(call_index, register_index) => {
write!(f, "PS{}R{}", call_index, register_index)
}
}
}
}

View File

@ -1,893 +0,0 @@
use tracing::trace;
use crate::{
AbstractList, ConcreteValue, Instruction, Operand, Type, Value,
instruction::{
Add, Call, CallNative, Close, Divide, Equal, GetLocal, Jump, Less, LessEqual, LoadBoolean,
LoadConstant, LoadFunction, LoadList, LoadSelf, Modulo, Multiply, Negate, Not, Point,
Return, SetLocal, Subtract, Test, TestSet, TypeCode,
},
vm::FunctionCall,
};
use super::{Pointer, Register, thread::ThreadData};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct RunAction {
pub logic: RunnerLogic,
pub instruction: Instruction,
}
impl From<Instruction> for RunAction {
fn from(instruction: Instruction) -> Self {
let operation = instruction.operation();
let logic = RUNNER_LOGIC_TABLE[operation.0 as usize];
RunAction { logic, instruction }
}
}
pub type RunnerLogic = fn(Instruction, &mut ThreadData) -> bool;
pub const RUNNER_LOGIC_TABLE: [RunnerLogic; 25] = [
point,
close,
load_boolean,
load_constant,
load_function,
load_list,
load_self,
get_local,
set_local,
add,
subtract,
multiply,
divide,
modulo,
equal,
less,
less_equal,
negate,
not,
test,
test_set,
call,
call_native,
jump,
r#return,
];
pub(crate) fn get_next_action(data: &mut ThreadData) -> RunAction {
let current_call = data.call_stack.last_mut_unchecked();
let instruction = current_call.chunk.instructions[current_call.ip];
let operation = instruction.operation();
let logic = RUNNER_LOGIC_TABLE[operation.0 as usize];
current_call.ip += 1;
RunAction { logic, instruction }
}
pub fn point(instruction: Instruction, data: &mut ThreadData) -> bool {
let Point { from, to } = instruction.into();
let from_register = data.get_register_unchecked(from);
let from_register_is_empty = matches!(from_register, Register::Empty);
if !from_register_is_empty {
let register = Register::Pointer(Pointer::Register(to));
data.set_register(from, register);
}
data.next_action = get_next_action(data);
false
}
pub fn close(instruction: Instruction, data: &mut ThreadData) -> bool {
let Close { from, to } = instruction.into();
for register_index in from..to {
data.set_register(register_index, Register::Empty);
}
data.next_action = get_next_action(data);
false
}
pub fn load_boolean(instruction: Instruction, data: &mut ThreadData) -> bool {
let LoadBoolean {
destination,
value,
jump_next,
} = instruction.into();
let boolean = Value::Concrete(ConcreteValue::Boolean(value));
let register = Register::Value(boolean);
data.set_register(destination, register);
if jump_next {
let current_call = data.call_stack.last_mut_unchecked();
current_call.ip += 1;
}
data.next_action = get_next_action(data);
false
}
pub fn load_constant(instruction: Instruction, data: &mut ThreadData) -> bool {
let LoadConstant {
destination,
constant_index,
jump_next,
} = instruction.into();
let register = Register::Pointer(Pointer::Constant(constant_index));
trace!("Load constant {constant_index} into R{destination}");
data.set_register(destination, register);
if jump_next {
let current_call = data.call_stack.last_mut_unchecked();
current_call.ip += 1;
}
data.next_action = get_next_action(data);
false
}
pub fn load_list(instruction: Instruction, data: &mut ThreadData) -> bool {
let LoadList {
destination,
start_register,
jump_next,
} = instruction.into();
let mut item_pointers = Vec::with_capacity((destination - start_register) as usize);
let mut item_type = Type::Any;
for register_index in start_register..destination {
match data.get_register_unchecked(register_index) {
Register::Empty => continue,
Register::Value(value) => {
if item_type == Type::Any {
item_type = value.r#type();
}
}
Register::Pointer(pointer) => {
if item_type == Type::Any {
item_type = data.follow_pointer_unchecked(*pointer).r#type();
}
}
}
let pointer = Pointer::Register(register_index);
item_pointers.push(pointer);
}
let list_value = Value::AbstractList(AbstractList {
item_type,
item_pointers,
});
let register = Register::Value(list_value);
data.set_register(destination, register);
data.next_action = get_next_action(data);
false
}
pub fn load_function(instruction: Instruction, data: &mut ThreadData) -> bool {
let LoadFunction {
destination,
prototype_index,
jump_next,
} = instruction.into();
let prototype_index = prototype_index as usize;
let current_call = data.call_stack.last_mut_unchecked();
let prototype = &current_call.chunk.prototypes[prototype_index];
let function = prototype.as_function();
let register = Register::Value(Value::Function(function));
data.set_register(destination, register);
data.next_action = get_next_action(data);
false
}
pub fn load_self(instruction: Instruction, data: &mut ThreadData) -> bool {
let LoadSelf {
destination,
jump_next,
} = instruction.into();
let current_call = data.call_stack.last_mut_unchecked();
let prototype = &current_call.chunk;
let function = prototype.as_function();
let register = Register::Value(Value::Function(function));
data.set_register(destination, register);
data.next_action = get_next_action(data);
false
}
pub fn get_local(instruction: Instruction, data: &mut ThreadData) -> bool {
let GetLocal {
destination,
local_index,
} = instruction.into();
let local_register_index = data.get_local_register(local_index);
let register = Register::Pointer(Pointer::Register(local_register_index));
data.set_register(destination, register);
data.next_action = get_next_action(data);
false
}
pub fn set_local(instruction: Instruction, data: &mut ThreadData) -> bool {
let SetLocal {
register_index,
local_index,
} = instruction.into();
let local_register_index = data.get_local_register(local_index);
let register = Register::Pointer(Pointer::Register(register_index));
data.set_register(local_register_index, register);
data.next_action = get_next_action(data);
false
}
pub fn add(instruction: Instruction, data: &mut ThreadData) -> bool {
let Add {
destination,
left,
left_type,
right,
right_type,
} = instruction.into();
let sum = match (left_type, right_type) {
(TypeCode::INTEGER, TypeCode::INTEGER) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_integer()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_integer()
.unwrap_unchecked()
};
ConcreteValue::Integer(left + right)
}
(TypeCode::FLOAT, TypeCode::FLOAT) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_float()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_float()
.unwrap_unchecked()
};
ConcreteValue::Float(left + right)
}
_ => panic!("VM Error: Cannot add values"),
};
let register = Register::Value(Value::Concrete(sum));
data.set_register(destination, register);
data.next_action = get_next_action(data);
false
}
pub fn subtract(instruction: Instruction, data: &mut ThreadData) -> bool {
let Subtract {
destination,
left,
left_type,
right,
right_type,
} = instruction.into();
let difference = match (left_type, right_type) {
(TypeCode::INTEGER, TypeCode::INTEGER) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_integer()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_integer()
.unwrap_unchecked()
};
ConcreteValue::Integer(left - right)
}
(TypeCode::FLOAT, TypeCode::FLOAT) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_float()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_float()
.unwrap_unchecked()
};
ConcreteValue::Float(left - right)
}
_ => panic!("VM Error: Cannot subtract values"),
};
let register = Register::Value(Value::Concrete(difference));
data.set_register(destination, register);
data.next_action = get_next_action(data);
false
}
pub fn multiply(instruction: Instruction, data: &mut ThreadData) -> bool {
let Multiply {
destination,
left,
left_type,
right,
right_type,
} = instruction.into();
let product = match (left_type, right_type) {
(TypeCode::INTEGER, TypeCode::INTEGER) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_integer()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_integer()
.unwrap_unchecked()
};
ConcreteValue::Integer(left * right)
}
(TypeCode::FLOAT, TypeCode::FLOAT) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_float()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_float()
.unwrap_unchecked()
};
ConcreteValue::Float(left * right)
}
_ => panic!("VM Error: Cannot multiply values"),
};
let register = Register::Value(Value::Concrete(product));
data.set_register(destination, register);
data.next_action = get_next_action(data);
false
}
pub fn divide(instruction: Instruction, data: &mut ThreadData) -> bool {
let Divide {
destination,
left,
left_type,
right,
right_type,
} = instruction.into();
let quotient = match (left_type, right_type) {
(TypeCode::INTEGER, TypeCode::INTEGER) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_integer()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_integer()
.unwrap_unchecked()
};
ConcreteValue::Integer(left / right)
}
(TypeCode::FLOAT, TypeCode::FLOAT) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_float()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_float()
.unwrap_unchecked()
};
ConcreteValue::Float(left / right)
}
_ => panic!("VM Error: Cannot divide values"),
};
let register = Register::Value(Value::Concrete(quotient));
data.set_register(destination, register);
data.next_action = get_next_action(data);
false
}
pub fn modulo(instruction: Instruction, data: &mut ThreadData) -> bool {
let Modulo {
destination,
left,
left_type,
right,
right_type,
} = instruction.into();
let remainder = match (left_type, right_type) {
(TypeCode::INTEGER, TypeCode::INTEGER) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_integer()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_integer()
.unwrap_unchecked()
};
ConcreteValue::Integer(left % right)
}
_ => panic!("VM Error: Cannot modulo values"),
};
let register = Register::Value(Value::Concrete(remainder));
data.set_register(destination, register);
data.next_action = get_next_action(data);
false
}
pub fn test(instruction: Instruction, data: &mut ThreadData) -> bool {
let Test {
operand_register,
test_value,
} = instruction.into();
let value = data.open_register_unchecked(operand_register);
let boolean = if let Value::Concrete(ConcreteValue::Boolean(boolean)) = value {
*boolean
} else {
panic!("VM Error: Expected boolean value for TEST operation",);
};
if boolean == test_value {
let current_call = data.call_stack.last_mut_unchecked();
current_call.ip += 1;
}
data.next_action = get_next_action(data);
false
}
pub fn test_set(instruction: Instruction, data: &mut ThreadData) -> bool {
let TestSet {
destination,
argument,
test_value,
} = instruction.into();
let value = data.get_argument_unchecked(argument);
let boolean = if let Value::Concrete(ConcreteValue::Boolean(boolean)) = value {
*boolean
} else {
panic!("VM Error: Expected boolean value for TEST_SET operation",);
};
if boolean == test_value {
} else {
let pointer = match argument {
Operand::Constant(constant_index) => Pointer::Constant(constant_index),
Operand::Register(register_index) => Pointer::Register(register_index),
};
let register = Register::Pointer(pointer);
data.set_register(destination, register);
}
data.next_action = get_next_action(data);
false
}
pub fn equal(instruction: Instruction, data: &mut ThreadData) -> bool {
let Equal {
comparator,
left,
left_type,
right,
right_type,
} = instruction.into();
let is_equal = match (left_type, right_type) {
(TypeCode::INTEGER, TypeCode::INTEGER) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_integer()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_integer()
.unwrap_unchecked()
};
left == right
}
(TypeCode::FLOAT, TypeCode::FLOAT) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_float()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_float()
.unwrap_unchecked()
};
left == right
}
(TypeCode::BOOLEAN, TypeCode::BOOLEAN) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_boolean()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_boolean()
.unwrap_unchecked()
};
left == right
}
(TypeCode::STRING, TypeCode::STRING) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_string()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_string()
.unwrap_unchecked()
};
left == right
}
_ => panic!("VM Error: Cannot compare values"),
};
if is_equal == comparator {
let current_call = data.call_stack.last_mut_unchecked();
current_call.ip += 1;
}
data.next_action = get_next_action(data);
false
}
pub fn less(instruction: Instruction, data: &mut ThreadData) -> bool {
let Less {
comparator,
left,
left_type,
right,
right_type,
} = instruction.into();
let is_less = match (left_type, right_type) {
(TypeCode::INTEGER, TypeCode::INTEGER) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_integer()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_integer()
.unwrap_unchecked()
};
left < right
}
(TypeCode::FLOAT, TypeCode::FLOAT) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_float()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_float()
.unwrap_unchecked()
};
left < right
}
_ => panic!("VM Error: Cannot compare values"),
};
if is_less == comparator {
let current_call = data.call_stack.last_mut_unchecked();
current_call.ip += 1;
}
data.next_action = get_next_action(data);
false
}
pub fn less_equal(instruction: Instruction, data: &mut ThreadData) -> bool {
let LessEqual {
comparator,
left,
left_type,
right,
right_type,
} = instruction.into();
let is_less_or_equal = match (left_type, right_type) {
(TypeCode::INTEGER, TypeCode::INTEGER) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_integer()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_integer()
.unwrap_unchecked()
};
left <= right
}
(TypeCode::FLOAT, TypeCode::FLOAT) => {
let left = unsafe {
data.get_argument_unchecked(left)
.as_float()
.unwrap_unchecked()
};
let right = unsafe {
data.get_argument_unchecked(right)
.as_float()
.unwrap_unchecked()
};
left <= right
}
_ => panic!("VM Error: Cannot compare values"),
};
if is_less_or_equal == comparator {
let current_call = data.call_stack.last_mut_unchecked();
current_call.ip += 1;
}
data.next_action = get_next_action(data);
false
}
pub fn negate(instruction: Instruction, data: &mut ThreadData) -> bool {
let Negate {
destination,
argument,
argument_type,
} = instruction.into();
let argument = data.get_argument_unchecked(argument);
let negated = argument.negate();
let register = Register::Value(negated);
data.set_register(destination, register);
data.next_action = get_next_action(data);
false
}
pub fn not(instruction: Instruction, data: &mut ThreadData) -> bool {
let Not {
destination,
argument,
} = instruction.into();
let argument = data.get_argument_unchecked(argument);
let not = match argument {
Value::Concrete(ConcreteValue::Boolean(boolean)) => ConcreteValue::Boolean(!boolean),
_ => panic!("VM Error: Expected boolean value for NOT operation"),
};
let register = Register::Value(Value::Concrete(not));
data.set_register(destination, register);
data.next_action = get_next_action(data);
false
}
pub fn jump(instruction: Instruction, data: &mut ThreadData) -> bool {
let Jump {
offset,
is_positive,
} = instruction.into();
let offset = offset as usize;
let current_call = data.call_stack.last_mut_unchecked();
if is_positive {
current_call.ip += offset;
} else {
current_call.ip -= offset + 1;
}
data.next_action = get_next_action(data);
false
}
pub fn call(instruction: Instruction, data: &mut ThreadData) -> bool {
let Call {
destination: return_register,
function_register,
argument_count,
is_recursive,
} = instruction.into();
let current_call = data.call_stack.last_unchecked();
let first_argument_register = return_register - argument_count;
let prototype = if is_recursive {
current_call.chunk.clone()
} else {
let function = data
.open_register_unchecked(function_register)
.as_function()
.unwrap();
current_call.chunk.prototypes[function.prototype_index as usize].clone()
};
let mut next_call = FunctionCall::new(prototype, return_register);
let mut argument_index = 0;
for register_index in first_argument_register..return_register {
let value_option = data.open_register_allow_empty_unchecked(register_index);
let argument = if let Some(value) = value_option {
value.clone()
} else {
continue;
};
next_call.registers[argument_index] = Register::Value(argument);
argument_index += 1;
}
data.call_stack.push(next_call);
data.next_action = get_next_action(data);
false
}
pub fn call_native(instruction: Instruction, data: &mut ThreadData) -> bool {
let CallNative {
destination,
function,
argument_count,
} = instruction.into();
let first_argument_index = destination - argument_count;
let argument_range = first_argument_index..destination;
function.call(data, destination, argument_range)
}
pub fn r#return(instruction: Instruction, data: &mut ThreadData) -> bool {
trace!("Returning with call stack:\n{}", data.call_stack);
let Return {
should_return_value,
return_register,
} = instruction.into();
let (destination, return_value) = if data.call_stack.len() == 1 {
if should_return_value {
data.return_value_index = Some(return_register);
};
return true;
} else {
let return_value = data.empty_register_or_clone_constant_unchecked(return_register);
let destination = data.call_stack.pop_unchecked().return_register;
(destination, return_value)
};
if should_return_value {
data.set_register(destination, Register::Value(return_value));
}
data.next_action = get_next_action(data);
false
}
#[cfg(test)]
mod tests {
use crate::Operation;
use super::*;
const ALL_OPERATIONS: [(Operation, RunnerLogic); 24] = [
(Operation::POINT, point),
(Operation::CLOSE, close),
(Operation::LOAD_BOOLEAN, load_boolean),
(Operation::LOAD_CONSTANT, load_constant),
(Operation::LOAD_LIST, load_list),
(Operation::LOAD_SELF, load_self),
(Operation::GET_LOCAL, get_local),
(Operation::SET_LOCAL, set_local),
(Operation::ADD, add),
(Operation::SUBTRACT, subtract),
(Operation::MULTIPLY, multiply),
(Operation::DIVIDE, divide),
(Operation::MODULO, modulo),
(Operation::TEST, test),
(Operation::TEST_SET, test_set),
(Operation::EQUAL, equal),
(Operation::LESS, less),
(Operation::LESS_EQUAL, less_equal),
(Operation::NEGATE, negate),
(Operation::NOT, not),
(Operation::CALL, call),
(Operation::CALL_NATIVE, call_native),
(Operation::JUMP, jump),
(Operation::RETURN, r#return),
];
#[test]
fn operations_map_to_the_correct_runner() {
for (operation, expected_runner) in ALL_OPERATIONS {
let actual_runner = RUNNER_LOGIC_TABLE[operation.0 as usize];
assert_eq!(
expected_runner, actual_runner,
"{operation} runner is incorrect"
);
}
}
}

View File

@ -1,137 +0,0 @@
use std::{
fmt::{self, Debug, Display, Formatter},
ops::{Index, IndexMut, Range},
};
use super::FunctionCall;
#[derive(Clone, PartialEq)]
pub struct Stack<T> {
items: Vec<T>,
}
impl<T> Stack<T> {
pub fn new() -> Self {
Stack {
items: Vec::with_capacity(1),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Stack {
items: Vec::with_capacity(capacity),
}
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn len(&self) -> usize {
self.items.len()
}
pub fn get_unchecked(&self, index: usize) -> &T {
if cfg!(debug_assertions) {
assert!(index < self.len(), "Stack underflow");
&self.items[index]
} else {
unsafe { self.items.get_unchecked(index) }
}
}
pub fn get_unchecked_mut(&mut self, index: usize) -> &mut T {
if cfg!(debug_assertions) {
assert!(index < self.len(), "Stack underflow");
&mut self.items[index]
} else {
unsafe { self.items.get_unchecked_mut(index) }
}
}
pub fn push(&mut self, item: T) {
self.items.push(item);
}
pub fn pop(&mut self) -> Option<T> {
self.items.pop()
}
pub fn last(&self) -> Option<&T> {
self.items.last()
}
pub fn last_mut(&mut self) -> Option<&mut T> {
self.items.last_mut()
}
pub fn pop_unchecked(&mut self) -> T {
if cfg!(debug_assertions) {
assert!(!self.is_empty(), "Stack underflow");
self.items.pop().unwrap()
} else {
unsafe { self.items.pop().unwrap_unchecked() }
}
}
pub fn last_unchecked(&self) -> &T {
if cfg!(debug_assertions) {
assert!(!self.is_empty(), "Stack underflow");
self.items.last().unwrap()
} else {
unsafe { self.items.last().unwrap_unchecked() }
}
}
pub fn last_mut_unchecked(&mut self) -> &mut T {
if cfg!(debug_assertions) {
assert!(!self.is_empty(), "Stack underflow");
self.items.last_mut().unwrap()
} else {
unsafe { self.items.last_mut().unwrap_unchecked() }
}
}
}
impl<T> Default for Stack<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> Index<Range<usize>> for Stack<T> {
type Output = [T];
fn index(&self, index: Range<usize>) -> &Self::Output {
&self.items[index]
}
}
impl<T> IndexMut<Range<usize>> for Stack<T> {
fn index_mut(&mut self, index: Range<usize>) -> &mut Self::Output {
&mut self.items[index]
}
}
impl<T: Debug> Debug for Stack<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{:?}", self.items)
}
}
impl Display for Stack<FunctionCall> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
writeln!(f, "----- DUST CALL STACK -----")?;
for (index, function_call) in self.items.iter().enumerate().rev() {
writeln!(f, "{index:02} | {function_call}")?;
}
write!(f, "---------------------------")
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,129 +0,0 @@
use dust_lang::*;
#[test]
fn constant() {
let source = "42";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Integer
},
vec![
(Instruction::load_constant(0, 0, false), Span(0, 2)),
(Instruction::r#return(true), Span(2, 2))
],
vec![ConcreteValue::Integer(42)],
vec![]
))
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(42))));
}
#[test]
fn empty() {
let source = "";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::None
},
vec![(Instruction::r#return(false), Span(0, 0))],
vec![],
vec![]
))
);
assert_eq!(run(source), Ok(None));
}
#[test]
fn parentheses_precedence() {
let source = "(1 + 2) * 3";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Integer
},
vec![
(
Instruction::add(0, Operand::Constant(0), Operand::Constant(1)),
Span(3, 4)
),
(
Instruction::multiply(1, Operand::Register(0), Operand::Constant(2)),
Span(8, 9)
),
(Instruction::r#return(true), Span(11, 11)),
],
vec![
ConcreteValue::Integer(1),
ConcreteValue::Integer(2),
ConcreteValue::Integer(3)
],
vec![]
))
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(9))));
}
#[test]
fn math_operator_precedence() {
let source = "1 + 2 - 3 * 4 / 5";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Integer,
},
vec![
(
Instruction::add(0, Operand::Constant(0), Operand::Constant(1)),
Span(2, 3)
),
(
Instruction::multiply(1, Operand::Constant(2), Operand::Constant(3)),
Span(10, 11)
),
(
Instruction::divide(2, Operand::Register(1), Operand::Constant(4)),
Span(14, 15)
),
(
Instruction::subtract(3, Operand::Register(0), Operand::Register(2)),
Span(6, 7)
),
(Instruction::r#return(true), Span(17, 17)),
],
vec![
ConcreteValue::Integer(1),
ConcreteValue::Integer(2),
ConcreteValue::Integer(3),
ConcreteValue::Integer(4),
ConcreteValue::Integer(5),
],
vec![]
))
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(1))));
}

View File

@ -1,169 +0,0 @@
use dust_lang::*;
#[test]
fn equal() {
let source = "1 == 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Boolean
},
vec![
(
Instruction::equal(0, true, Operand::Constant(0), Operand::Constant(1)),
Span(2, 4)
),
(Instruction::r#return(true), Span(6, 6)),
],
vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(false))));
}
#[test]
fn greater() {
let source = "1 > 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Boolean
},
vec![
(
Instruction::less_equal(0, false, Operand::Constant(0), Operand::Constant(1)),
Span(2, 3)
),
(Instruction::r#return(true), Span(5, 5)),
],
vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(false))));
}
#[test]
fn greater_than_or_equal() {
let source = "1 >= 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Boolean
},
vec![
(
Instruction::less(0, false, Operand::Constant(0), Operand::Constant(1)),
Span(2, 4)
),
(Instruction::r#return(true), Span(6, 6)),
],
vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(false))));
}
#[test]
fn less_than() {
let source = "1 < 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Boolean
},
vec![
(
Instruction::less(0, true, Operand::Constant(0), Operand::Constant(1)),
Span(2, 3)
),
(Instruction::r#return(true), Span(5, 5)),
],
vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(true))));
}
#[test]
fn less_than_or_equal() {
let source = "1 <= 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Boolean
},
vec![
(
Instruction::less_equal(0, true, Operand::Constant(0), Operand::Constant(1)),
Span(2, 4)
),
(Instruction::r#return(true), Span(6, 6)),
],
vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(true))));
}
#[test]
fn not_equal() {
let source = "1 != 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Boolean
},
vec![
(
Instruction::equal(0, false, Operand::Constant(0), Operand::Constant(1)),
Span(2, 4)
),
(Instruction::r#return(true), Span(6, 6)),
],
vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(true))));
}

View File

@ -0,0 +1,209 @@
use dust_lang::{
compile, instruction::TypeCode, run, Chunk, DustString, FunctionType, Instruction, Operand,
Span, Type, Value,
};
#[test]
fn equal_bytes() {
let source = "0x0A == 0x03";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, 0x0A, TypeCode::BYTE, false),
Instruction::load_encoded(1, 0x03, TypeCode::BYTE, false),
Instruction::equal(
true,
Operand::Register(0, TypeCode::BYTE),
Operand::Register(1, TypeCode::BYTE),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(8, 12),
Span(0, 12),
Span(0, 12),
Span(0, 12),
Span(0, 12),
Span(12, 12),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn equal_characters() {
let source = "'a' == 'b'";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::equal(
true,
Operand::Constant(0, TypeCode::CHARACTER),
Operand::Constant(1, TypeCode::CHARACTER),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 10),
Span(0, 10),
Span(0, 10),
Span(0, 10),
Span(10, 10),
],
character_constants: vec!['a', 'b'],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn equal_floats() {
let source = "10.0 == 3.0";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::equal(
true,
Operand::Constant(0, TypeCode::FLOAT),
Operand::Constant(1, TypeCode::FLOAT),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 11),
Span(0, 11),
Span(0, 11),
Span(0, 11),
Span(11, 11),
],
float_constants: vec![10.0, 3.0],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn equal_integers() {
let source = "10 == 3";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::equal(
true,
Operand::Constant(0, TypeCode::INTEGER),
Operand::Constant(1, TypeCode::INTEGER),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![Span(0, 7), Span(0, 7), Span(0, 7), Span(0, 7), Span(7, 7)],
integer_constants: vec![10, 3],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn equal_strings() {
let source = "\"abc\" == \"def\"";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::equal(
true,
Operand::Constant(0, TypeCode::STRING),
Operand::Constant(1, TypeCode::STRING),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 14),
Span(0, 14),
Span(0, 14),
Span(0, 14),
Span(14, 14),
],
string_constants: vec![DustString::from("abc"), DustString::from("def")],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn equal_lists() {
let source = "[1, 2, 3] == [4, 5, 6]";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_constant(0, 0, TypeCode::INTEGER, false),
Instruction::load_constant(1, 1, TypeCode::INTEGER, false),
Instruction::load_constant(2, 2, TypeCode::INTEGER, false),
Instruction::load_list(0, TypeCode::INTEGER, 0, 2, false),
Instruction::load_constant(3, 3, TypeCode::INTEGER, false),
Instruction::load_constant(4, 4, TypeCode::INTEGER, false),
Instruction::load_constant(5, 5, TypeCode::INTEGER, false),
Instruction::load_list(1, TypeCode::INTEGER, 3, 5, false),
Instruction::equal(
true,
Operand::Register(0, TypeCode::LIST),
Operand::Register(1, TypeCode::LIST),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(1, 2),
Span(4, 5),
Span(7, 8),
Span(0, 9),
Span(14, 15),
Span(17, 18),
Span(20, 21),
Span(13, 22),
Span(0, 22),
Span(0, 22),
Span(0, 22),
Span(0, 22),
Span(22, 22),
],
integer_constants: vec![1, 2, 3, 4, 5, 6],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}

View File

@ -0,0 +1,203 @@
use dust_lang::{
compile, instruction::TypeCode, run, Chunk, DustString, FunctionType, Instruction, Operand,
Span, Type, Value,
};
#[test]
fn greater_bytes() {
let source = "0x0A > 0x03";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, 0x0A, TypeCode::BYTE, false),
Instruction::load_encoded(1, 0x03, TypeCode::BYTE, false),
Instruction::less_equal(
false,
Operand::Register(0, TypeCode::BYTE),
Operand::Register(1, TypeCode::BYTE),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(7, 11),
Span(0, 11),
Span(0, 11),
Span(0, 11),
Span(0, 11),
Span(11, 11),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn greater_characters() {
let source = "'a' > 'b'";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::less_equal(
false,
Operand::Constant(0, TypeCode::CHARACTER),
Operand::Constant(1, TypeCode::CHARACTER),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![Span(0, 9), Span(0, 9), Span(0, 9), Span(0, 9), Span(9, 9)],
character_constants: vec!['a', 'b'],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn greater_floats() {
let source = "10.0 > 3.0";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::less_equal(
false,
Operand::Constant(0, TypeCode::FLOAT),
Operand::Constant(1, TypeCode::FLOAT),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 10),
Span(0, 10),
Span(0, 10),
Span(0, 10),
Span(10, 10),
],
float_constants: vec![10.0, 3.0],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn greater_integers() {
let source = "10 > 3";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::less_equal(
false,
Operand::Constant(0, TypeCode::INTEGER),
Operand::Constant(1, TypeCode::INTEGER),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![Span(0, 6), Span(0, 6), Span(0, 6), Span(0, 6), Span(6, 6)],
integer_constants: vec![10, 3],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn greater_strings() {
let source = "\"abc\" > \"def\"";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::less_equal(
false,
Operand::Constant(0, TypeCode::STRING),
Operand::Constant(1, TypeCode::STRING),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 13),
Span(0, 13),
Span(0, 13),
Span(0, 13),
Span(13, 13),
],
string_constants: vec![DustString::from("abc"), DustString::from("def")],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn greater_lists() {
let source = "[1, 2, 3] > [4, 5, 6]";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_constant(0, 0, TypeCode::INTEGER, false),
Instruction::load_constant(1, 1, TypeCode::INTEGER, false),
Instruction::load_constant(2, 2, TypeCode::INTEGER, false),
Instruction::load_list(0, TypeCode::INTEGER, 0, 2, false),
Instruction::load_constant(3, 3, TypeCode::INTEGER, false),
Instruction::load_constant(4, 4, TypeCode::INTEGER, false),
Instruction::load_constant(5, 5, TypeCode::INTEGER, false),
Instruction::load_list(1, TypeCode::INTEGER, 3, 5, false),
Instruction::less_equal(
false,
Operand::Register(0, TypeCode::LIST),
Operand::Register(1, TypeCode::LIST),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(1, 2),
Span(4, 5),
Span(7, 8),
Span(0, 9),
Span(13, 14),
Span(16, 17),
Span(19, 20),
Span(12, 21),
Span(0, 21),
Span(0, 21),
Span(0, 21),
Span(0, 21),
Span(21, 21),
],
integer_constants: vec![1, 2, 3, 4, 5, 6],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}

View File

@ -0,0 +1,244 @@
use dust_lang::{
compile, instruction::TypeCode, run, Chunk, DustString, FunctionType, Instruction, Operand,
Span, Type, Value,
};
#[test]
fn great_equal_booleans() {
let source = "true >= false";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::load_encoded(1, false as u8, TypeCode::BOOLEAN, false),
Instruction::less(
false,
Operand::Register(0, TypeCode::BOOLEAN),
Operand::Register(1, TypeCode::BOOLEAN),
),
Instruction::jump(1, true),
Instruction::load_encoded(2, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(2, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 2, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(8, 13),
Span(0, 13),
Span(0, 13),
Span(0, 13),
Span(0, 13),
Span(13, 13),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn greater_equal_bytes() {
let source = "0x0A >= 0x03";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, 0x0A, TypeCode::BYTE, false),
Instruction::load_encoded(1, 0x03, TypeCode::BYTE, false),
Instruction::less(
false,
Operand::Register(0, TypeCode::BYTE),
Operand::Register(1, TypeCode::BYTE),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(8, 12),
Span(0, 12),
Span(0, 12),
Span(0, 12),
Span(0, 12),
Span(12, 12),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn greater_equal_characters() {
let source = "'a' >= 'b'";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::less(
false,
Operand::Constant(0, TypeCode::CHARACTER),
Operand::Constant(1, TypeCode::CHARACTER),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 10),
Span(0, 10),
Span(0, 10),
Span(0, 10),
Span(10, 10),
],
character_constants: vec!['a', 'b'],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn greater_equal_floats() {
let source = "10.0 >= 3.0";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::less(
false,
Operand::Constant(0, TypeCode::FLOAT),
Operand::Constant(1, TypeCode::FLOAT),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 11),
Span(0, 11),
Span(0, 11),
Span(0, 11),
Span(11, 11),
],
float_constants: vec![10.0, 3.0],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn greater_equal_integers() {
let source = "10 >= 3";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::less(
false,
Operand::Constant(0, TypeCode::INTEGER),
Operand::Constant(1, TypeCode::INTEGER),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![Span(0, 7), Span(0, 7), Span(0, 7), Span(0, 7), Span(7, 7)],
integer_constants: vec![10, 3],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn greater_equal_strings() {
let source = "\"abc\" >= \"def\"";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::less(
false,
Operand::Constant(0, TypeCode::STRING),
Operand::Constant(1, TypeCode::STRING),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 14),
Span(0, 14),
Span(0, 14),
Span(0, 14),
Span(14, 14),
],
string_constants: vec![DustString::from("abc"), DustString::from("def")],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn greater_equal_lists() {
let source = "[1, 2, 3] >= [4, 5, 6]";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_constant(0, 0, TypeCode::INTEGER, false),
Instruction::load_constant(1, 1, TypeCode::INTEGER, false),
Instruction::load_constant(2, 2, TypeCode::INTEGER, false),
Instruction::load_list(0, TypeCode::INTEGER, 0, 2, false),
Instruction::load_constant(3, 3, TypeCode::INTEGER, false),
Instruction::load_constant(4, 4, TypeCode::INTEGER, false),
Instruction::load_constant(5, 5, TypeCode::INTEGER, false),
Instruction::load_list(1, TypeCode::INTEGER, 3, 5, false),
Instruction::less(
false,
Operand::Register(0, TypeCode::LIST),
Operand::Register(1, TypeCode::LIST),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(1, 2),
Span(4, 5),
Span(7, 8),
Span(0, 9),
Span(14, 15),
Span(17, 18),
Span(20, 21),
Span(13, 22),
Span(0, 22),
Span(0, 22),
Span(0, 22),
Span(0, 22),
Span(22, 22),
],
integer_constants: vec![1, 2, 3, 4, 5, 6],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}

View File

@ -0,0 +1,238 @@
use dust_lang::{
compile, instruction::TypeCode, run, Chunk, DustString, FunctionType, Instruction, Operand,
Span, Type, Value,
};
#[test]
fn less_booleans() {
let source = "true < false";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::load_encoded(1, false as u8, TypeCode::BOOLEAN, false),
Instruction::less(
true,
Operand::Register(0, TypeCode::BOOLEAN),
Operand::Register(1, TypeCode::BOOLEAN),
),
Instruction::jump(1, true),
Instruction::load_encoded(2, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(2, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 2, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(7, 12),
Span(0, 12),
Span(0, 12),
Span(0, 12),
Span(0, 12),
Span(12, 12),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn less_bytes() {
let source = "0x0A < 0x03";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, 0x0A, TypeCode::BYTE, false),
Instruction::load_encoded(1, 0x03, TypeCode::BYTE, false),
Instruction::less(
true,
Operand::Register(0, TypeCode::BYTE),
Operand::Register(1, TypeCode::BYTE),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(7, 11),
Span(0, 11),
Span(0, 11),
Span(0, 11),
Span(0, 11),
Span(11, 11),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn less_characters() {
let source = "'a' < 'b'";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::less(
true,
Operand::Constant(0, TypeCode::CHARACTER),
Operand::Constant(1, TypeCode::CHARACTER),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![Span(0, 9), Span(0, 9), Span(0, 9), Span(0, 9), Span(9, 9)],
character_constants: vec!['a', 'b'],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn less_floats() {
let source = "10.0 < 3.0";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::less(
true,
Operand::Constant(0, TypeCode::FLOAT),
Operand::Constant(1, TypeCode::FLOAT),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 10),
Span(0, 10),
Span(0, 10),
Span(0, 10),
Span(10, 10),
],
float_constants: vec![10.0, 3.0],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn less_integers() {
let source = "10 < 3";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::less(
true,
Operand::Constant(0, TypeCode::INTEGER),
Operand::Constant(1, TypeCode::INTEGER),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![Span(0, 6), Span(0, 6), Span(0, 6), Span(0, 6), Span(6, 6)],
integer_constants: vec![10, 3],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn less_strings() {
let source = "\"abc\" < \"def\"";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::less(
true,
Operand::Constant(0, TypeCode::STRING),
Operand::Constant(1, TypeCode::STRING),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 13),
Span(0, 13),
Span(0, 13),
Span(0, 13),
Span(13, 13),
],
string_constants: vec![DustString::from("abc"), DustString::from("def")],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn less_lists() {
let source = "[1, 2, 3] < [4, 5, 6]";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_constant(0, 0, TypeCode::INTEGER, false),
Instruction::load_constant(1, 1, TypeCode::INTEGER, false),
Instruction::load_constant(2, 2, TypeCode::INTEGER, false),
Instruction::load_list(0, TypeCode::INTEGER, 0, 2, false),
Instruction::load_constant(3, 3, TypeCode::INTEGER, false),
Instruction::load_constant(4, 4, TypeCode::INTEGER, false),
Instruction::load_constant(5, 5, TypeCode::INTEGER, false),
Instruction::load_list(1, TypeCode::INTEGER, 3, 5, false),
Instruction::less(
true,
Operand::Register(0, TypeCode::LIST),
Operand::Register(1, TypeCode::LIST),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(1, 2),
Span(4, 5),
Span(7, 8),
Span(0, 9),
Span(13, 14),
Span(16, 17),
Span(19, 20),
Span(12, 21),
Span(0, 21),
Span(0, 21),
Span(0, 21),
Span(0, 21),
Span(21, 21),
],
integer_constants: vec![1, 2, 3, 4, 5, 6],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}

View File

@ -0,0 +1,244 @@
use dust_lang::{
compile, instruction::TypeCode, run, Chunk, DustString, FunctionType, Instruction, Operand,
Span, Type, Value,
};
#[test]
fn less_equal_booleans() {
let source = "true <= false";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::load_encoded(1, false as u8, TypeCode::BOOLEAN, false),
Instruction::less_equal(
true,
Operand::Register(0, TypeCode::BOOLEAN),
Operand::Register(1, TypeCode::BOOLEAN),
),
Instruction::jump(1, true),
Instruction::load_encoded(2, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(2, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 2, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(8, 13),
Span(0, 13),
Span(0, 13),
Span(0, 13),
Span(0, 13),
Span(13, 13),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn less_equal_bytes() {
let source = "0x0A <= 0x03";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, 0x0A, TypeCode::BYTE, false),
Instruction::load_encoded(1, 0x03, TypeCode::BYTE, false),
Instruction::less_equal(
true,
Operand::Register(0, TypeCode::BYTE),
Operand::Register(1, TypeCode::BYTE),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(8, 12),
Span(0, 12),
Span(0, 12),
Span(0, 12),
Span(0, 12),
Span(12, 12),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn less_equal_characters() {
let source = "'a' <= 'b'";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::less_equal(
true,
Operand::Constant(0, TypeCode::CHARACTER),
Operand::Constant(1, TypeCode::CHARACTER),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 10),
Span(0, 10),
Span(0, 10),
Span(0, 10),
Span(10, 10),
],
character_constants: vec!['a', 'b'],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn less_equal_floats() {
let source = "10.0 <= 3.0";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::less_equal(
true,
Operand::Constant(0, TypeCode::FLOAT),
Operand::Constant(1, TypeCode::FLOAT),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 11),
Span(0, 11),
Span(0, 11),
Span(0, 11),
Span(11, 11),
],
float_constants: vec![10.0, 3.0],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn less_equal_integers() {
let source = "10 <= 3";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::less_equal(
true,
Operand::Constant(0, TypeCode::INTEGER),
Operand::Constant(1, TypeCode::INTEGER),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![Span(0, 7), Span(0, 7), Span(0, 7), Span(0, 7), Span(7, 7)],
integer_constants: vec![10, 3],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn less_equal_strings() {
let source = "\"abc\" <= \"def\"";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::less_equal(
true,
Operand::Constant(0, TypeCode::STRING),
Operand::Constant(1, TypeCode::STRING),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 14),
Span(0, 14),
Span(0, 14),
Span(0, 14),
Span(14, 14),
],
string_constants: vec![DustString::from("abc"), DustString::from("def")],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn less_equal_lists() {
let source = "[1, 2, 3] <= [4, 5, 6]";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_constant(0, 0, TypeCode::INTEGER, false),
Instruction::load_constant(1, 1, TypeCode::INTEGER, false),
Instruction::load_constant(2, 2, TypeCode::INTEGER, false),
Instruction::load_list(0, TypeCode::INTEGER, 0, 2, false),
Instruction::load_constant(3, 3, TypeCode::INTEGER, false),
Instruction::load_constant(4, 4, TypeCode::INTEGER, false),
Instruction::load_constant(5, 5, TypeCode::INTEGER, false),
Instruction::load_list(1, TypeCode::INTEGER, 3, 5, false),
Instruction::less_equal(
true,
Operand::Register(0, TypeCode::LIST),
Operand::Register(1, TypeCode::LIST),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(1, 2),
Span(4, 5),
Span(7, 8),
Span(0, 9),
Span(14, 15),
Span(17, 18),
Span(20, 21),
Span(13, 22),
Span(0, 22),
Span(0, 22),
Span(0, 22),
Span(0, 22),
Span(22, 22),
],
integer_constants: vec![1, 2, 3, 4, 5, 6],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}

View File

@ -0,0 +1,244 @@
use dust_lang::{
compile, instruction::TypeCode, run, Chunk, DustString, FunctionType, Instruction, Operand,
Span, Type, Value,
};
#[test]
fn not_equal_booleans() {
let source = "true != false";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::load_encoded(1, false as u8, TypeCode::BOOLEAN, false),
Instruction::equal(
false,
Operand::Register(0, TypeCode::BOOLEAN),
Operand::Register(1, TypeCode::BOOLEAN),
),
Instruction::jump(1, true),
Instruction::load_encoded(2, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(2, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 2, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(8, 13),
Span(0, 13),
Span(0, 13),
Span(0, 13),
Span(0, 13),
Span(13, 13),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn not_equal_bytes() {
let source = "0x0A != 0x03";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, 0x0A, TypeCode::BYTE, false),
Instruction::load_encoded(1, 0x03, TypeCode::BYTE, false),
Instruction::equal(
false,
Operand::Register(0, TypeCode::BYTE),
Operand::Register(1, TypeCode::BYTE),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(8, 12),
Span(0, 12),
Span(0, 12),
Span(0, 12),
Span(0, 12),
Span(12, 12),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn not_equal_characters() {
let source = "'a' != 'b'";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::equal(
false,
Operand::Constant(0, TypeCode::CHARACTER),
Operand::Constant(1, TypeCode::CHARACTER),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 10),
Span(0, 10),
Span(0, 10),
Span(0, 10),
Span(10, 10),
],
character_constants: vec!['a', 'b'],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn not_equal_floats() {
let source = "10.0 != 3.0";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::equal(
false,
Operand::Constant(0, TypeCode::FLOAT),
Operand::Constant(1, TypeCode::FLOAT),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 11),
Span(0, 11),
Span(0, 11),
Span(0, 11),
Span(11, 11),
],
float_constants: vec![10.0, 3.0],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn not_equal_integers() {
let source = "10 != 3";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::equal(
false,
Operand::Constant(0, TypeCode::INTEGER),
Operand::Constant(1, TypeCode::INTEGER),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![Span(0, 7), Span(0, 7), Span(0, 7), Span(0, 7), Span(7, 7)],
integer_constants: vec![10, 3],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn not_equal_strings() {
let source = "\"abc\" != \"def\"";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::equal(
false,
Operand::Constant(0, TypeCode::STRING),
Operand::Constant(1, TypeCode::STRING),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 14),
Span(0, 14),
Span(0, 14),
Span(0, 14),
Span(14, 14),
],
string_constants: vec![DustString::from("abc"), DustString::from("def")],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn not_equal_lists() {
let source = "[1, 2, 3] != [4, 5, 6]";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_constant(0, 0, TypeCode::INTEGER, false),
Instruction::load_constant(1, 1, TypeCode::INTEGER, false),
Instruction::load_constant(2, 2, TypeCode::INTEGER, false),
Instruction::load_list(0, TypeCode::INTEGER, 0, 2, false),
Instruction::load_constant(3, 3, TypeCode::INTEGER, false),
Instruction::load_constant(4, 4, TypeCode::INTEGER, false),
Instruction::load_constant(5, 5, TypeCode::INTEGER, false),
Instruction::load_list(1, TypeCode::INTEGER, 3, 5, false),
Instruction::equal(
false,
Operand::Register(0, TypeCode::LIST),
Operand::Register(1, TypeCode::LIST),
),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(1, 2),
Span(4, 5),
Span(7, 8),
Span(0, 9),
Span(14, 15),
Span(17, 18),
Span(20, 21),
Span(13, 22),
Span(0, 22),
Span(0, 22),
Span(0, 22),
Span(0, 22),
Span(22, 22),
],
integer_constants: vec![1, 2, 3, 4, 5, 6],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}

View File

@ -1,133 +0,0 @@
use dust_lang::*;
use smallvec::smallvec;
#[test]
fn function() {
let source = "fn(a: int, b: int) -> int { a + b }";
assert_eq!(
run(source),
Ok(Some(ConcreteValue::function(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::function(FunctionType {
type_parameters: None,
value_parameters: Some(smallvec![(0, Type::Integer), (1, Type::Integer)]),
return_type: Type::Integer,
})
},
vec![
(
Instruction::add(2, Operand::Register(0), Operand::Register(1)),
Span(30, 31)
),
(Instruction::r#return(true), Span(34, 35)),
],
vec![ConcreteValue::string("a"), ConcreteValue::string("b"),],
vec![
Local::new(0, 0, false, Scope::default()),
Local::new(1, 1, false, Scope::default())
]
))))
);
}
#[test]
fn function_call() {
let source = "fn(a: int, b: int) -> int { a + b }(1, 2)";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Integer
},
vec![
(Instruction::load_constant(0, 0, false), Span(0, 35)),
(Instruction::load_constant(1, 1, false), Span(36, 37)),
(Instruction::load_constant(2, 2, false), Span(39, 40)),
(Instruction::call(3, Operand::Constant(0), 2), Span(35, 41)),
(Instruction::r#return(true), Span(41, 41)),
],
vec![
ConcreteValue::function(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: Some(smallvec![(0, Type::Integer), (1, Type::Integer)]),
return_type: Type::Integer
},
vec![
(
Instruction::add(2, Operand::Register(0), Operand::Register(1)),
Span(30, 31)
),
(Instruction::r#return(true), Span(34, 35)),
],
vec![ConcreteValue::string("a"), ConcreteValue::string("b"),],
vec![
Local::new(0, 0, false, Scope::default()),
Local::new(1, 1, false, Scope::default())
]
)),
ConcreteValue::Integer(1),
ConcreteValue::Integer(2)
],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(3))));
}
#[test]
fn function_declaration() {
let source = "fn add (a: int, b: int) -> int { a + b }";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::None
},
vec![
(Instruction::load_constant(0, 0, false), Span(0, 40)),
(Instruction::r#return(false), Span(40, 40))
],
vec![
ConcreteValue::function(Chunk::with_data(
Some("add".into()),
FunctionType {
type_parameters: None,
value_parameters: Some(smallvec![(0, Type::Integer), (1, Type::Integer)]),
return_type: Type::Integer
},
vec![
(
Instruction::add(2, Operand::Register(0), Operand::Register(1)),
Span(35, 36)
),
(Instruction::r#return(true), Span(39, 40)),
],
vec![ConcreteValue::string("a"), ConcreteValue::string("b")],
vec![
Local::new(0, 0, false, Scope::default()),
Local::new(1, 1, false, Scope::default())
]
)),
ConcreteValue::string("add"),
],
vec![Local::new(1, 0, false, Scope::default(),),],
)),
);
assert_eq!(run(source), Ok(None));
}

View File

@ -1,159 +0,0 @@
use dust_lang::*;
#[test]
fn empty_list() {
let source = "[]";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::List(Box::new(Type::Any)),
},
vec![
(Instruction::load_list(0, 0), Span(0, 2)),
(Instruction::r#return(true), Span(2, 2)),
],
vec![],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::list([]))));
}
#[test]
fn list() {
let source = "[1, 2, 3]";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::List(Box::new(Type::Integer)),
},
vec![
(Instruction::load_constant(0, 0, false), Span(1, 2)),
(Instruction::load_constant(1, 1, false), Span(4, 5)),
(Instruction::load_constant(2, 2, false), Span(7, 8)),
(Instruction::load_list(3, 0), Span(0, 9)),
(Instruction::r#return(true), Span(9, 9)),
],
vec![
ConcreteValue::Integer(1),
ConcreteValue::Integer(2),
ConcreteValue::Integer(3)
],
vec![]
)),
);
assert_eq!(
run(source),
Ok(Some(ConcreteValue::list([
ConcreteValue::Integer(1),
ConcreteValue::Integer(2),
ConcreteValue::Integer(3)
])))
);
}
#[test]
fn list_with_complex_expression() {
let source = "[1, 2 + 3 - 4 * 5]";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::List(Box::new(Type::Integer)),
},
vec![
(Instruction::load_constant(0, 0, false), Span(1, 2)),
(
Instruction::add(1, Operand::Constant(1), Operand::Constant(2)),
Span(6, 7)
),
(
Instruction::multiply(2, Operand::Constant(3), Operand::Constant(4)),
Span(14, 15)
),
(
Instruction::subtract(3, Operand::Register(1), Operand::Register(2)),
Span(10, 11)
),
(Instruction::close(1, 3), Span(17, 18)),
(Instruction::load_list(4, 0), Span(0, 18)),
(Instruction::r#return(true), Span(18, 18)),
],
vec![
ConcreteValue::Integer(1),
ConcreteValue::Integer(2),
ConcreteValue::Integer(3),
ConcreteValue::Integer(4),
ConcreteValue::Integer(5)
],
vec![]
)),
);
assert_eq!(
run(source),
Ok(Some(ConcreteValue::list([
ConcreteValue::Integer(1),
ConcreteValue::Integer(-15)
])))
);
}
#[test]
fn list_with_simple_expression() {
let source = "[1, 2 + 3, 4]";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::List(Box::new(Type::Integer)),
},
vec![
(Instruction::load_constant(0, 0, false), Span(1, 2)),
(
Instruction::add(1, Operand::Constant(1), Operand::Constant(2)),
Span(6, 7)
),
(Instruction::load_constant(2, 3, false), Span(11, 12)),
(Instruction::load_list(3, 0), Span(0, 13)),
(Instruction::r#return(true), Span(13, 13)),
],
vec![
ConcreteValue::Integer(1),
ConcreteValue::Integer(2),
ConcreteValue::Integer(3),
ConcreteValue::Integer(4),
],
vec![]
)),
);
assert_eq!(
run(source),
Ok(Some(ConcreteValue::list([
ConcreteValue::Integer(1),
ConcreteValue::Integer(5),
ConcreteValue::Integer(4),
])))
);
}

View File

@ -1,146 +1,111 @@
use dust_lang::*;
use smallvec::smallvec;
use dust_lang::{
Chunk, FunctionType, Instruction, Span, Type, Value, compile, instruction::TypeCode, run,
};
#[test]
fn true_and_true() {
let source = "true && true";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Boolean,
},
smallvec![
Instruction::load_boolean(0, true, false),
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(1, true),
Instruction::load_boolean(1, true, false),
Instruction::r#return(true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
smallvec![
positions: vec![
Span(0, 4),
Span(5, 7),
Span(5, 7),
Span(8, 12),
Span(12, 12),
],
smallvec![],
smallvec![],
vec![]
))
);
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn true_and_false() {
let source = "true && false";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(1, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(5, 7),
Span(5, 7),
Span(8, 13),
Span(13, 13),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn false_and_true() {
let source = "false && true";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 5),
Span(6, 8),
Span(6, 8),
Span(9, 13),
Span(13, 13),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn false_and_false() {
let source = "false && false";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Boolean,
},
smallvec![
Instruction::load_boolean(0, false, false),
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(1, true),
Instruction::load_boolean(1, false, false),
Instruction::r#return(true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
smallvec![
positions: vec![
Span(0, 5),
Span(6, 8),
Span(6, 8),
Span(9, 14),
Span(14, 14),
],
smallvec![],
smallvec![],
vec![]
))
);
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
}
#[test]
fn false_and_true() {
let source = "false && true";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Boolean,
},
smallvec![
Instruction::load_boolean(0, false, false),
Instruction::test(0, true),
Instruction::jump(1, true),
Instruction::load_boolean(1, true, false),
Instruction::r#return(true),
],
smallvec![
Span(0, 5),
Span(6, 8),
Span(6, 8),
Span(9, 13),
Span(13, 13)
],
smallvec![],
smallvec![],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
}
#[test]
fn true_and_false() {
let source = "true && false";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Boolean,
},
smallvec![
Instruction::load_boolean(0, true, false),
Instruction::test(0, true),
Instruction::jump(1, true),
Instruction::load_boolean(1, false, false),
Instruction::r#return(true),
],
smallvec![
Span(0, 4),
Span(5, 7),
Span(5, 7),
Span(8, 13),
Span(13, 13)
],
smallvec![],
smallvec![],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}

View File

@ -1,30 +1,23 @@
use dust_lang::*;
use smallvec::smallvec;
use dust_lang::{
Chunk, FunctionType, Instruction, Span, Type, Value, compile, instruction::TypeCode, run,
};
#[test]
fn true_and_true_and_true() {
let source = "true && true && true";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Boolean,
},
smallvec![
Instruction::load_boolean(0, true, false),
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(4, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(1, true),
Instruction::load_boolean(1, true, false),
Instruction::test(1, true),
Instruction::jump(1, true),
Instruction::load_boolean(2, true, false),
Instruction::r#return(true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
smallvec![
positions: vec![
Span(0, 4),
Span(5, 7),
Span(5, 7),
@ -32,11 +25,243 @@ fn true_and_true_and_true() {
Span(13, 15),
Span(13, 15),
Span(16, 20),
Span(20, 20)
Span(20, 20),
],
smallvec![],
smallvec![],
vec![],
))
);
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn true_and_true_and_false() {
let source = "true && true && false";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(4, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(1, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(5, 7),
Span(5, 7),
Span(8, 12),
Span(13, 15),
Span(13, 15),
Span(16, 21),
Span(21, 21),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn true_and_false_and_true() {
let source = "true && false && true";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(4, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(5, 7),
Span(5, 7),
Span(8, 13),
Span(14, 16),
Span(14, 16),
Span(17, 21),
Span(21, 21),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn true_and_false_and_false() {
let source = "true && false && false";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(4, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(1, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(5, 7),
Span(5, 7),
Span(8, 13),
Span(14, 16),
Span(14, 16),
Span(17, 22),
Span(22, 22),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn false_and_true_and_true() {
let source = "false && true && true";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(4, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 5),
Span(6, 8),
Span(6, 8),
Span(9, 13),
Span(14, 16),
Span(14, 16),
Span(17, 21),
Span(21, 21),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn false_and_true_and_false() {
let source = "false && true && false";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(4, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(1, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 5),
Span(6, 8),
Span(6, 8),
Span(9, 13),
Span(14, 16),
Span(14, 16),
Span(17, 22),
Span(22, 22),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn false_and_false_and_true() {
let source = "false && false && true";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(4, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 5),
Span(6, 8),
Span(6, 8),
Span(9, 14),
Span(15, 17),
Span(15, 17),
Span(18, 22),
Span(22, 22),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn false_and_false_and_false() {
let source = "false && false && false";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(4, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(1, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 5),
Span(6, 8),
Span(6, 8),
Span(9, 14),
Span(15, 17),
Span(15, 17),
Span(18, 23),
Span(23, 23),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}

View File

@ -0,0 +1,267 @@
use dust_lang::{
Chunk, FunctionType, Instruction, Span, Type, Value, compile, instruction::TypeCode, run,
};
#[test]
fn true_and_true_or_true() {
let source = "true && true || true";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(4, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(5, 7),
Span(5, 7),
Span(8, 12),
Span(13, 15),
Span(13, 15),
Span(16, 20),
Span(20, 20),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn true_and_false_or_true() {
let source = "true && false || true";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(4, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(5, 7),
Span(5, 7),
Span(8, 13),
Span(14, 16),
Span(14, 16),
Span(17, 21),
Span(21, 21),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn true_and_true_or_false() {
let source = "true && true || false";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(4, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(1, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(5, 7),
Span(5, 7),
Span(8, 12),
Span(13, 15),
Span(13, 15),
Span(16, 21),
Span(21, 21),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn true_and_false_or_false() {
let source = "true && false || false";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(4, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(1, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(5, 7),
Span(5, 7),
Span(8, 13),
Span(14, 16),
Span(14, 16),
Span(17, 22),
Span(22, 22),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn false_and_true_or_true() {
let source = "false && true || true";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(4, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 5),
Span(6, 8),
Span(6, 8),
Span(9, 13),
Span(14, 16),
Span(14, 16),
Span(17, 21),
Span(21, 21),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn false_and_false_or_true() {
let source = "false && false || true";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(4, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 5),
Span(6, 8),
Span(6, 8),
Span(9, 14),
Span(15, 17),
Span(15, 17),
Span(18, 22),
Span(22, 22),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn false_and_true_or_false() {
let source = "false && true || false";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(4, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(1, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 5),
Span(6, 8),
Span(6, 8),
Span(9, 13),
Span(14, 16),
Span(14, 16),
Span(17, 22),
Span(22, 22),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn false_and_false_or_false() {
let source = "false && false || false";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(4, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(1, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 5),
Span(6, 8),
Span(6, 8),
Span(9, 14),
Span(15, 17),
Span(15, 17),
Span(18, 23),
Span(23, 23),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}

View File

@ -1,38 +1,111 @@
use dust_lang::*;
use smallvec::smallvec;
use dust_lang::{
Chunk, FunctionType, Instruction, Span, Type, Value, compile, instruction::TypeCode, run,
};
#[test]
fn true_or_true() {
let source = "true || true";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(5, 7),
Span(5, 7),
Span(8, 12),
Span(12, 12),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn true_or_false() {
let source = "true || false";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Boolean,
},
smallvec![
Instruction::load_boolean(0, true, false),
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(1, true),
Instruction::load_boolean(1, false, false),
Instruction::r#return(true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
smallvec![
positions: vec![
Span(0, 4),
Span(5, 7),
Span(5, 7),
Span(8, 13),
Span(13, 13),
],
smallvec![],
smallvec![],
vec![]
))
);
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn false_or_true() {
let source = "false || true";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 5),
Span(6, 8),
Span(6, 8),
Span(9, 13),
Span(13, 13),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn false_or_false() {
let source = "false || false";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(1, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 5),
Span(6, 8),
Span(6, 8),
Span(9, 14),
Span(14, 14),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}

View File

@ -0,0 +1,266 @@
use dust_lang::{
Chunk, FunctionType, Instruction, Span, Type, Value, compile, instruction::TypeCode, run,
};
#[test]
fn true_or_true_and_true() {
let source = "true || true && true";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(4, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(5, 7),
Span(5, 7),
Span(8, 12),
Span(13, 15),
Span(13, 15),
Span(16, 20),
Span(20, 20),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn true_or_false_or_true() {
let source = "true || false || true";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(4, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(5, 7),
Span(5, 7),
Span(8, 13),
Span(14, 16),
Span(14, 16),
Span(17, 21),
Span(21, 21),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn true_or_true_and_false() {
let source = "true || true && false";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(4, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(1, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(5, 7),
Span(5, 7),
Span(8, 12),
Span(13, 15),
Span(13, 15),
Span(16, 21),
Span(21, 21),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn true_or_false_and_false() {
let source = "true || false && false";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(4, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(1, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(5, 7),
Span(5, 7),
Span(8, 13),
Span(14, 16),
Span(14, 16),
Span(17, 22),
Span(22, 22),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn false_or_true_and_true() {
let source = "false || true && true";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(4, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 5),
Span(6, 8),
Span(6, 8),
Span(9, 13),
Span(14, 16),
Span(14, 16),
Span(17, 21),
Span(21, 21),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn false_or_false_and_true() {
let source = "false || false && true";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(4, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 5),
Span(6, 8),
Span(6, 8),
Span(9, 14),
Span(15, 17),
Span(15, 17),
Span(18, 22),
Span(22, 22),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn false_or_true_and_false() {
let source = "false || true && false";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(4, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(1, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 5),
Span(6, 8),
Span(6, 8),
Span(9, 13),
Span(14, 16),
Span(14, 16),
Span(17, 22),
Span(22, 22),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn false_or_false_and_false() {
let source = "false || false && false";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(4, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, true),
Instruction::jump(1, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 5),
Span(6, 8),
Span(6, 8),
Span(9, 14),
Span(15, 17),
Span(15, 17),
Span(18, 23),
Span(23, 23),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}

View File

@ -0,0 +1,267 @@
use dust_lang::{
Chunk, FunctionType, Instruction, Span, Type, Value, compile, instruction::TypeCode, run,
};
#[test]
fn true_or_true_or_true() {
let source = "true || true || true";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(4, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(5, 7),
Span(5, 7),
Span(8, 12),
Span(13, 15),
Span(13, 15),
Span(16, 20),
Span(20, 20),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn true_or_true_or_false() {
let source = "true || true || false";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(4, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(1, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(5, 7),
Span(5, 7),
Span(8, 12),
Span(13, 15),
Span(13, 15),
Span(16, 21),
Span(21, 21),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn true_or_false_or_true() {
let source = "true || false || true";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(4, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(5, 7),
Span(5, 7),
Span(8, 13),
Span(14, 16),
Span(14, 16),
Span(17, 21),
Span(21, 21),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn true_or_false_or_false() {
let source = "true || false || false";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(4, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(1, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 4),
Span(5, 7),
Span(5, 7),
Span(8, 13),
Span(14, 16),
Span(14, 16),
Span(17, 22),
Span(22, 22),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn false_or_true_or_true() {
let source = "false || true || true";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(4, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 5),
Span(6, 8),
Span(6, 8),
Span(9, 13),
Span(14, 16),
Span(14, 16),
Span(17, 21),
Span(21, 21),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn false_or_true_or_false() {
let source = "false || true || false";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(4, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(1, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 5),
Span(6, 8),
Span(6, 8),
Span(9, 13),
Span(14, 16),
Span(14, 16),
Span(17, 22),
Span(22, 22),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn false_or_false_or_true() {
let source = "false || false || true";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(4, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(1, true),
Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 5),
Span(6, 8),
Span(6, 8),
Span(9, 14),
Span(15, 17),
Span(15, 17),
Span(18, 22),
Span(22, 22),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(true));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn false_or_false_or_false() {
let source = "false || false || false";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Boolean),
instructions: vec![
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(4, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::test(0, false),
Instruction::jump(1, true),
Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false),
Instruction::r#return(true, 0, TypeCode::BOOLEAN),
],
positions: vec![
Span(0, 5),
Span(6, 8),
Span(6, 8),
Span(9, 14),
Span(15, 17),
Span(15, 17),
Span(18, 23),
Span(23, 23),
],
..Chunk::default()
};
let return_value = Some(Value::boolean(false));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}

View File

@ -1,42 +0,0 @@
use dust_lang::*;
#[test]
fn r#while() {
let source = "let mut x = 0; while x < 5 { x = x + 1 } x";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Integer,
},
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(
Instruction::less(0, true, Operand::Register(0), Operand::Constant(2)),
Span(23, 24)
),
(Instruction::jump(2, true), Span(41, 42)),
(
Instruction::add(0, Operand::Register(0), Operand::Constant(3)),
Span(35, 36)
),
(Instruction::jump(3, false), Span(41, 42)),
(Instruction::get_local(1, 0), Span(41, 42)),
(Instruction::r#return(true), Span(42, 42)),
],
vec![
ConcreteValue::Integer(0),
ConcreteValue::string("x"),
ConcreteValue::Integer(5),
ConcreteValue::Integer(1),
],
vec![Local::new(1, 0, true, Scope::default())]
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(5))));
}

View File

@ -1,270 +1,302 @@
use dust_lang::*;
use smallvec::smallvec;
use dust_lang::{
compile, instruction::TypeCode, run, Chunk, DustString, FunctionType, Instruction, Operand,
Span, Type, Value,
};
#[test]
fn add_bytes() {
let source = "0xfe + 0x01";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Byte,
},
smallvec![
Instruction::add(0, Argument::Constant(0), Argument::Constant(1)),
Instruction::r#return(true),
let source = "0x28 + 0x02";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Byte),
instructions: vec![
Instruction::load_encoded(0, 40, TypeCode::BYTE, false),
Instruction::load_encoded(1, 2, TypeCode::BYTE, false),
Instruction::add(
2,
Operand::Register(0, TypeCode::BYTE),
Operand::Register(1, TypeCode::BYTE),
),
Instruction::r#return(true, 2, TypeCode::BYTE),
],
smallvec![Span(5, 6), Span(11, 11),],
smallvec![Value::byte(0xfe), Value::byte(0x01)],
smallvec![],
vec![]
))
);
positions: vec![Span(0, 4), Span(7, 11), Span(0, 11), Span(11, 11)],
..Chunk::default()
};
let return_value = Some(Value::byte(0x2A));
assert_eq!(run(source), Ok(Some(Value::byte(0xff))));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn add_bytes_saturate() {
let source = "0xff + 0x01";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Byte,
},
smallvec![
Instruction::add(0, Argument::Constant(0), Argument::Constant(1)),
Instruction::r#return(true),
fn add_many_bytes() {
let source = "0x28 + 0x02 + 0x02 + 0x02";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Byte),
instructions: vec![
Instruction::load_encoded(0, 40, TypeCode::BYTE, false),
Instruction::load_encoded(1, 2, TypeCode::BYTE, false),
Instruction::add(
2,
Operand::Register(0, TypeCode::BYTE),
Operand::Register(1, TypeCode::BYTE),
),
Instruction::load_encoded(3, 2, TypeCode::BYTE, false),
Instruction::add(
4,
Operand::Register(2, TypeCode::BYTE),
Operand::Register(3, TypeCode::BYTE),
),
Instruction::load_encoded(5, 2, TypeCode::BYTE, false),
Instruction::add(
6,
Operand::Register(4, TypeCode::BYTE),
Operand::Register(5, TypeCode::BYTE),
),
Instruction::r#return(true, 6, TypeCode::BYTE),
],
smallvec![Span(5, 6), Span(11, 11)],
smallvec![Value::byte(0xff), Value::byte(0x01)],
smallvec![],
vec![]
))
);
positions: vec![
Span(0, 4),
Span(7, 11),
Span(0, 11),
Span(14, 18),
Span(0, 18),
Span(21, 25),
Span(0, 25),
Span(25, 25),
],
..Chunk::default()
};
let return_value = Some(Value::byte(46));
assert_eq!(run(source), Ok(Some(Value::byte(0xff))));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn add_characters() {
let source = "'a' + 'b'";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::String,
},
smallvec![
Instruction::add(0, Argument::Constant(0), Argument::Constant(1)),
Instruction::r#return(true)
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::String),
instructions: vec![
Instruction::add(
0,
Operand::Constant(0, TypeCode::CHARACTER),
Operand::Constant(1, TypeCode::CHARACTER),
),
Instruction::r#return(true, 0, TypeCode::STRING),
],
smallvec![Span(4, 5), Span(11, 11)],
smallvec![Value::character('a'), Value::character('b')],
smallvec![],
vec![]
))
);
positions: vec![Span(0, 9), Span(9, 9)],
character_constants: vec!['a', 'b'],
..Chunk::default()
};
let return_value = Some(Value::string("ab"));
assert_eq!(run(source), Ok(Some(Value::string("ab"))));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn add_character_and_string() {
let source = "'a' + \"b\"";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::String,
},
smallvec![
Instruction::add(0, Argument::Constant(0), Argument::Constant(1)),
Instruction::r#return(true),
fn add_many_characters() {
let source = "'a' + 'b' + 'c' + 'd'";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::String),
instructions: vec![
Instruction::add(
0,
Operand::Constant(0, TypeCode::CHARACTER),
Operand::Constant(1, TypeCode::CHARACTER),
),
Instruction::add(
1,
Operand::Register(0, TypeCode::STRING),
Operand::Constant(2, TypeCode::CHARACTER),
),
Instruction::add(
2,
Operand::Register(1, TypeCode::STRING),
Operand::Constant(3, TypeCode::CHARACTER),
),
Instruction::r#return(true, 2, TypeCode::STRING),
],
smallvec![Span(4, 5), Span(9, 9),],
smallvec![Value::character('a'), Value::string("b")],
smallvec![],
vec![]
))
);
positions: vec![Span(0, 9), Span(0, 15), Span(0, 21), Span(21, 21)],
character_constants: vec!['a', 'b', 'c', 'd'],
..Chunk::default()
};
let return_value = Some(Value::string("abcd"));
assert_eq!(run(source), Ok(Some(Value::string("ab"))));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn add_floats() {
let source = "1.0 + 2.0";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Float,
},
smallvec![
Instruction::add(0, Argument::Constant(0), Argument::Constant(1)),
Instruction::r#return(true),
let source = "2.40 + 40.02";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Float),
instructions: vec![
Instruction::add(
0,
Operand::Constant(0, TypeCode::FLOAT),
Operand::Constant(1, TypeCode::FLOAT),
),
Instruction::r#return(true, 0, TypeCode::FLOAT),
],
smallvec![Span(4, 5), Span(9, 9),],
smallvec![Value::float(1.0), Value::float(2.0)],
smallvec![],
vec![]
))
);
positions: vec![Span(0, 12), Span(12, 12)],
float_constants: vec![2.40, 40.02],
..Chunk::default()
};
let return_value = Some(Value::float(42.42));
assert_eq!(run(source), Ok(Some(Value::float(3.0))));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn add_floats_saturatate() {
let source = "1.7976931348623157E+308 + 0.00000001";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Float,
},
smallvec![
Instruction::add(0, Argument::Constant(0), Argument::Constant(1)),
Instruction::r#return(true),
fn add_many_floats() {
let source = "2.40 + 40.02 + 2.40 + 40.02";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Float),
instructions: vec![
Instruction::add(
0,
Operand::Constant(0, TypeCode::FLOAT),
Operand::Constant(1, TypeCode::FLOAT),
),
Instruction::add(
1,
Operand::Register(0, TypeCode::FLOAT),
Operand::Constant(0, TypeCode::FLOAT),
),
Instruction::add(
2,
Operand::Register(1, TypeCode::FLOAT),
Operand::Constant(1, TypeCode::FLOAT),
),
Instruction::r#return(true, 2, TypeCode::FLOAT),
],
smallvec![Span(24, 25), Span(36, 36),],
smallvec![Value::float(f64::MAX), Value::float(0.00000001)],
smallvec![],
vec![]
))
);
positions: vec![Span(0, 12), Span(0, 19), Span(0, 27), Span(27, 27)],
float_constants: vec![2.40, 40.02],
..Chunk::default()
};
let return_value = Some(Value::float(84.84));
assert_eq!(run(source), Ok(Some(Value::float(f64::MAX))));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn add_integers() {
let source = "1 + 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Integer,
},
smallvec![
Instruction::add(0, Argument::Constant(0), Argument::Constant(1)),
Instruction::r#return(true)
let source = "40 + 2";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Integer),
instructions: vec![
Instruction::add(
0,
Operand::Constant(0, TypeCode::INTEGER),
Operand::Constant(1, TypeCode::INTEGER),
),
Instruction::r#return(true, 0, TypeCode::INTEGER),
],
smallvec![Span(2, 3), Span(5, 5),],
smallvec![Value::integer(1), Value::integer(2)],
smallvec![],
vec![]
))
);
positions: vec![Span(0, 6), Span(6, 6)],
integer_constants: vec![40, 2],
..Chunk::default()
};
let return_value = Some(Value::integer(42));
assert_eq!(run(source), Ok(Some(Value::integer(3))));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn add_integers_saturate() {
let source = "9223372036854775807 + 1";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Integer,
},
smallvec![
Instruction::add(0, Argument::Constant(0), Argument::Constant(1)),
Instruction::r#return(true)
fn add_many_integers() {
let source = "40 + 2 + 40 + 2";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Integer),
instructions: vec![
Instruction::add(
0,
Operand::Constant(0, TypeCode::INTEGER),
Operand::Constant(1, TypeCode::INTEGER),
),
Instruction::add(
1,
Operand::Register(0, TypeCode::INTEGER),
Operand::Constant(0, TypeCode::INTEGER),
),
Instruction::add(
2,
Operand::Register(1, TypeCode::INTEGER),
Operand::Constant(1, TypeCode::INTEGER),
),
Instruction::r#return(true, 2, TypeCode::INTEGER),
],
smallvec![Span(20, 21), Span(23, 23),],
smallvec![Value::integer(i64::MAX), Value::integer(1)],
smallvec![],
vec![]
))
);
positions: vec![Span(0, 6), Span(0, 11), Span(0, 15), Span(15, 15)],
integer_constants: vec![40, 2],
..Chunk::default()
};
let return_value = Some(Value::integer(84));
assert_eq!(run(source), Ok(Some(Value::integer(i64::MAX))));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn add_strings() {
let source = "\"Hello, \" + \"world!\"";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::String,
},
smallvec![
Instruction::add(0, Argument::Constant(0), Argument::Constant(1)),
Instruction::r#return(true),
let source = "\"Hello, \" + \"World!\"";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::String),
instructions: vec![
Instruction::add(
0,
Operand::Constant(0, TypeCode::STRING),
Operand::Constant(1, TypeCode::STRING),
),
Instruction::r#return(true, 0, TypeCode::STRING),
],
smallvec![Span(10, 11), Span(20, 20)],
smallvec![Value::string("Hello, "), Value::string("world!")],
smallvec![],
vec![]
))
);
positions: vec![Span(0, 20), Span(20, 20)],
string_constants: vec![DustString::from("Hello, "), DustString::from("World!")],
..Chunk::default()
};
let return_value = Some(Value::string("Hello, World!"));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn add_string_and_character() {
let source = "\"a\" + 'b'";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::String,
},
smallvec![
Instruction::add(0, Argument::Constant(0), Argument::Constant(1)),
Instruction::r#return(true),
fn add_many_strings() {
let source = "\"foo\" + \"bar\" + \"baz\" + \"buzz\"";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::String),
instructions: vec![
Instruction::add(
0,
Operand::Constant(0, TypeCode::STRING),
Operand::Constant(1, TypeCode::STRING),
),
Instruction::add(
1,
Operand::Register(0, TypeCode::STRING),
Operand::Constant(2, TypeCode::STRING),
),
Instruction::add(
2,
Operand::Register(1, TypeCode::STRING),
Operand::Constant(3, TypeCode::STRING),
),
Instruction::r#return(true, 2, TypeCode::STRING),
],
smallvec![Span(4, 5), Span(9, 9),],
smallvec![Value::string("a"), Value::character('b')],
smallvec![],
vec![]
))
);
positions: vec![Span(0, 13), Span(0, 21), Span(0, 30), Span(30, 30)],
string_constants: vec![
DustString::from("foo"),
DustString::from("bar"),
DustString::from("baz"),
DustString::from("buzz"),
],
..Chunk::default()
};
let return_value = Some(Value::string("foobarbazbuzz"));
assert_eq!(run(source), Ok(Some(Value::string("ab"))));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}

View File

@ -1,31 +0,0 @@
use dust_lang::*;
use smallvec::smallvec;
#[test]
fn add_assign() {
let source = "let mut a = 1; a += 2; a";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Integer,
},
smallvec![
Instruction::load_constant(0, 0, false),
Instruction::add(0, Argument::Register(0), Argument::Constant(2)),
Instruction::get_local(1, 0),
Instruction::r#return(true)
],
smallvec![Span(12, 13), Span(17, 19), Span(23, 24), Span(24, 24)],
smallvec![Value::integer(1), Value::string("a"), Value::integer(2)],
smallvec![Local::new(1, 0, true, Scope::default())],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(3))));
}

View File

@ -1,405 +0,0 @@
use dust_lang::*;
#[test]
fn add_boolean_left() {
let source = "true + 1";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddType {
argument_type: Type::Boolean,
position: Span(0, 4)
},
source,
})
);
}
#[test]
fn add_boolean_right() {
let source = "1 + true";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddType {
argument_type: Type::Boolean,
position: Span(4, 8)
},
source,
})
);
}
#[test]
fn add_function_left() {
let source = "fn(){} + 1";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddType {
argument_type: Type::function(FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::None
}),
position: Span(0, 6)
},
source,
})
);
}
#[test]
fn add_function_right() {
let source = "1 + fn(){}";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddType {
argument_type: Type::function(FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::None
}),
position: Span(4, 10)
},
source,
})
);
}
#[test]
fn add_list_left() {
let source = "[1, 2] + 1";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddType {
argument_type: Type::List(Box::new(Type::Integer)),
position: Span(0, 6)
},
source,
})
);
}
#[test]
fn add_list_right() {
let source = "1 + [1, 2]";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddType {
argument_type: Type::List(Box::new(Type::Integer)),
position: Span(4, 10)
},
source,
})
);
}
// #[test]
// fn add_range_left() {
// todo!("Add ranges")
// }
// #[test]
// fn add_range_right() {
// todo!("Add ranges")
// }
//
#[test]
fn add_byte_and_character() {
let source = "0xff + 'a'";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddArguments {
left_type: Type::Byte,
right_type: Type::Character,
position: Span(0, 10)
},
source,
})
);
}
#[test]
fn add_byte_and_integer() {
let source = "0xff + 1";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddArguments {
left_type: Type::Byte,
right_type: Type::Integer,
position: Span(0, 8)
},
source,
})
);
}
#[test]
fn add_byte_and_string() {
let source = "0xff + \"hello\"";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddArguments {
left_type: Type::Byte,
right_type: Type::String,
position: Span(0, 14)
},
source,
})
);
}
#[test]
fn add_character_and_byte() {
let source = "'a' + 0xff";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddArguments {
left_type: Type::Character,
right_type: Type::Byte,
position: Span(0, 10)
},
source,
})
);
}
#[test]
fn add_character_and_float() {
let source = "'a' + 1.0";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddArguments {
left_type: Type::Character,
right_type: Type::Float,
position: Span(0, 9)
},
source,
})
);
}
#[test]
fn add_character_and_integer() {
let source = "'a' + 1";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddArguments {
left_type: Type::Character,
right_type: Type::Integer,
position: Span(0, 7)
},
source,
})
);
}
#[test]
fn add_float_and_byte() {
let source = "1.0 + 0xff";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddArguments {
left_type: Type::Float,
right_type: Type::Byte,
position: Span(0, 10)
},
source,
})
);
}
#[test]
fn add_float_and_character() {
let source = "1.0 + 'a'";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddArguments {
left_type: Type::Float,
right_type: Type::Character,
position: Span(0, 9)
},
source,
})
);
}
#[test]
fn add_float_and_integer() {
let source = "1.0 + 1";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddArguments {
left_type: Type::Float,
right_type: Type::Integer,
position: Span(0, 7)
},
source,
})
);
}
#[test]
fn add_float_and_string() {
let source = "1.0 + \"hello\"";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddArguments {
left_type: Type::Float,
right_type: Type::String,
position: Span(0, 13)
},
source,
})
);
}
#[test]
fn add_integer_and_byte() {
let source = "1 + 0xff";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddArguments {
left_type: Type::Integer,
right_type: Type::Byte,
position: Span(0, 8)
},
source,
})
);
}
#[test]
fn add_integer_and_character() {
let source = "1 + 'a'";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddArguments {
left_type: Type::Integer,
right_type: Type::Character,
position: Span(0, 7)
},
source,
})
);
}
#[test]
fn add_integer_and_float() {
let source = "1 + 1.0";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddArguments {
left_type: Type::Integer,
right_type: Type::Float,
position: Span(0, 7)
},
source,
})
);
}
#[test]
fn add_integer_and_string() {
let source = "1 + \"hello\"";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddArguments {
left_type: Type::Integer,
right_type: Type::String,
position: Span(0, 11)
},
source,
})
);
}
#[test]
fn add_string_and_byte() {
let source = "\"hello\" + 0xff";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddArguments {
left_type: Type::String,
right_type: Type::Byte,
position: Span(0, 14)
},
source,
})
);
}
#[test]
fn add_string_and_float() {
let source = "\"hello\" + 1.0";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddArguments {
left_type: Type::String,
right_type: Type::Float,
position: Span(0, 13)
},
source,
})
);
}
#[test]
fn add_string_and_integer() {
let source = "\"hello\" + 1";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::CannotAddArguments {
left_type: Type::String,
right_type: Type::Integer,
position: Span(0, 11)
},
source,
})
);
}

View File

@ -1,85 +1,167 @@
use dust_lang::*;
use dust_lang::{
compile, instruction::TypeCode, run, Chunk, FunctionType, Instruction, Operand, Span, Type,
Value,
};
#[test]
fn divide_bytes() {
let source = "0xff / 0x01";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Byte,
},
vec![
(
Instruction::divide(0, Operand::Constant(0), Operand::Constant(1)),
Span(5, 6)
let source = "0x0A / 0x02";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Byte),
instructions: vec![
Instruction::load_encoded(0, 10, TypeCode::BYTE, false),
Instruction::load_encoded(1, 2, TypeCode::BYTE, false),
Instruction::divide(
2,
Operand::Register(0, TypeCode::BYTE),
Operand::Register(1, TypeCode::BYTE),
),
(Instruction::r#return(true), Span(11, 11))
Instruction::r#return(true, 2, TypeCode::BYTE),
],
vec![ConcreteValue::Byte(0xff), ConcreteValue::Byte(0x01)],
vec![]
))
);
positions: vec![Span(0, 4), Span(7, 11), Span(0, 11), Span(11, 11)],
..Chunk::default()
};
let return_value = Some(Value::byte(0x05));
assert_eq!(run(source), Ok(Some(ConcreteValue::Byte(0xff))));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn divide_many_bytes() {
let source = "0x0A / 0x02 / 0x02";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Byte),
instructions: vec![
Instruction::load_encoded(0, 10, TypeCode::BYTE, false),
Instruction::load_encoded(1, 2, TypeCode::BYTE, false),
Instruction::divide(
2,
Operand::Register(0, TypeCode::BYTE),
Operand::Register(1, TypeCode::BYTE),
),
Instruction::load_encoded(3, 2, TypeCode::BYTE, false),
Instruction::divide(
4,
Operand::Register(2, TypeCode::BYTE),
Operand::Register(3, TypeCode::BYTE),
),
Instruction::r#return(true, 4, TypeCode::BYTE),
],
positions: vec![
Span(0, 4),
Span(7, 11),
Span(0, 11),
Span(14, 18),
Span(0, 18),
Span(18, 18),
],
..Chunk::default()
};
let return_value = Some(Value::byte(0x02));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn divide_floats() {
let source = "2.0 / 2.0";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Float,
},
vec![
(
Instruction::divide(0, Operand::Constant(0), Operand::Constant(0)),
Span(4, 5)
let source = "1.0 / 0.25";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Float),
instructions: vec![
Instruction::divide(
0,
Operand::Constant(0, TypeCode::FLOAT),
Operand::Constant(1, TypeCode::FLOAT),
),
(Instruction::r#return(true), Span(9, 9))
Instruction::r#return(true, 0, TypeCode::FLOAT),
],
vec![ConcreteValue::Float(2.0)],
vec![]
))
);
positions: vec![Span(0, 10), Span(10, 10)],
float_constants: vec![1.0, 0.25],
..Chunk::default()
};
let return_value = Some(Value::float(4.0));
assert_eq!(run(source), Ok(Some(ConcreteValue::Float(1.0))));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn divide_many_floats() {
let source = "1.0 / 0.25 / 0.5";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Float),
instructions: vec![
Instruction::divide(
0,
Operand::Constant(0, TypeCode::FLOAT),
Operand::Constant(1, TypeCode::FLOAT),
),
Instruction::divide(
1,
Operand::Register(0, TypeCode::FLOAT),
Operand::Constant(2, TypeCode::FLOAT),
),
Instruction::r#return(true, 1, TypeCode::FLOAT),
],
positions: vec![Span(0, 10), Span(0, 16), Span(16, 16)],
float_constants: vec![1.0, 0.25, 0.5],
..Chunk::default()
};
let return_value = Some(Value::float(8.0));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn divide_integers() {
let source = "2 / 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Type::Integer,
},
vec![
(
Instruction::divide(0, Operand::Constant(0), Operand::Constant(0)),
Span(2, 3)
let source = "10 / 2";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Integer),
instructions: vec![
Instruction::divide(
0,
Operand::Constant(0, TypeCode::INTEGER),
Operand::Constant(1, TypeCode::INTEGER),
),
(Instruction::r#return(true), Span(5, 5))
Instruction::r#return(true, 0, TypeCode::INTEGER),
],
vec![ConcreteValue::Integer(2)],
vec![]
))
);
positions: vec![Span(0, 6), Span(6, 6)],
integer_constants: vec![10, 2],
..Chunk::default()
};
let return_value = Some(Value::integer(5));
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(1))));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}
#[test]
fn divide_many_integers() {
let source = "10 / 2 / 2";
let chunk = Chunk {
r#type: FunctionType::new([], [], Type::Integer),
instructions: vec![
Instruction::divide(
0,
Operand::Constant(0, TypeCode::INTEGER),
Operand::Constant(1, TypeCode::INTEGER),
),
Instruction::divide(
1,
Operand::Register(0, TypeCode::INTEGER),
Operand::Constant(1, TypeCode::INTEGER),
),
Instruction::r#return(true, 1, TypeCode::INTEGER),
],
positions: vec![Span(0, 6), Span(0, 10), Span(10, 10)],
integer_constants: vec![10, 2],
..Chunk::default()
};
let return_value = Some(Value::integer(2));
assert_eq!(chunk, compile(source).unwrap());
assert_eq!(return_value, run(source).unwrap());
}

Some files were not shown because too many files have changed in this diff Show More