1
0

Compare commits

..

No commits in common. "main" and "0.3.6-yield" have entirely different histories.

133 changed files with 34934 additions and 11249 deletions

1952
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,30 @@
[workspace]
members = ["dust-lang", "dust-shell"]
default-members = ["dust-lang"]
resolver = "2"
[workspace.package]
authors = ["Jeff Anderson"]
[package]
name = "dust-lang"
description = "Data-Oriented Programming Language"
version = "0.3.5"
repository = "https://github.com/tree-sitter/tree-sitter-dust"
edition = "2021"
license = "MIT"
readme = "README.md"
repository = "https://git.jeffa.io/jeff/dust.git"
[profile.dev]
opt-level = 1
[profile.dev.package."*"]
opt-level = 3
[[bin]]
name = "dust"
path = "src/main.rs"
[dependencies]
ansi_term = "0.12.1"
async-std = { version = "1.12.0", features = ["attributes"] }
clap = { version = "4.4.4", features = ["derive"] }
comfy-table = "7.0.1"
csv = "1.2.2"
git2 = "0.18.1"
rand = "0.8.5"
rayon = "1.8.0"
reqwest = { version = "0.11.20", features = ["blocking", "json"] }
rustyline = { version = "12.0.0", features = ["derive", "with-file-history"] }
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.107"
toml = "0.8.1"
tree-sitter = "0.20.10"
[build-dependencies]
cc = "1.0"

338
README.md
View File

@ -1,50 +1,324 @@
# Dust
Dust is a high-level interpreted programming language with static types that focuses on ease of use,
performance and correctness.
Dust is a programming language and interactive shell. Dust can be used as a replacement for a traditional command line shell, as a scripting language and as a data format. Dust is fast, efficient and easy to learn.
A basic dust program:
```dust
(output "Hello world!")
```
Dust can do two (or more) things at the same time with effortless concurrency:
```dust
async {
(output 'will this one finish first?')
(output 'or will this one?')
}
```
Dust is an interpreted, general purpose language with first class functions. It is *data-oriented*, with extensive tools to manage structured and relational data. Dust also includes built-in tooling to import and export data in a variety of formats, including JSON, TOML, YAML and CSV.
<!--toc:start-->
- [Dust](#dust)
- [Features](#features)
- [Usage](#usage)
- [Installation](#installation)
- [Benchmarks](#benchmarks)
- [Implementation](#implementation)
- [The Dust Programming Language](#the-dust-programming-language)
- [Declaring Variables](#declaring-variables)
- [Lists](#lists)
- [Maps](#maps)
- [Loops](#loops)
- [Tables](#tables)
- [Functions](#functions)
- [Concurrency](#concurrency)
<!--toc:end-->
## Features
- Simplicity: Dust is designed to be easy to learn.
- Speed: Dust is built on [Tree Sitter] and [Rust] to prioritize performance and correctness.
- Data format: Dust is data-oriented, making it a great language for defining data.
- Format conversion: Effortlessly convert between dust and formats like JSON, CSV and TOML.
- Structured data: Dust can represent data with more than just strings. Lists, maps and tables are easy to make and manage.
## Usage
Dust is an experimental project under active development. At this stage, features come and go and the API is always changing. It should not be considered for serious use yet.
To get help with the shell you can use the "help" tool.
```dust
(help) # Returns a table with tool info.
```
## Installation
You must have the default rust toolchain installed and up-to-date. Install [rustup] if it is not already installed. Run `cargo install dust-lang` then run `dust` to start the interactive shell. Use `dust --help` to see the full command line options.
To build from source, clone the repository and build the parser. To do so, enter the `tree-sitter-dust` directory and run `tree-sitter-generate`. In the project root, run `cargo run` to start the shell. To see other command line options, use `cargo run -- --help`.
## Benchmarks
Dust is at a very early development stage but performs strongly in preliminary benchmarks. The examples given were tested using [Hyperfine] on a single-core cloud instance with 1024 MB RAM. Each test was run 1000 times. The test script is shown below. Each test asks the program to read a JSON file and count the objects. The programs produced identical output with the exception that NodeJS printed in color.
For the first test, a file with four entries was used.
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `dust -c '(length (from_json input))' -p seaCreatures.json` | 3.1 ± 0.5 | 2.4 | 8.4 | 1.00 |
| `jq 'length' seaCreatures.json` | 33.7 ± 2.2 | 30.0 | 61.8 | 10.85 ± 1.87 |
| `node --eval "require('node:fs').readFile('seaCreatures.json', (err, data)=>{console.log(JSON.parse(data).length)})"` | 226.4 ± 13.1 | 197.6 | 346.2 | 73.02 ± 12.33 |
| `nu -c 'open seaCreatures.json \| length'` | 51.6 ± 3.7 | 45.4 | 104.3 | 16.65 ± 2.90 |
The second set of data is from the GitHub API, it consists of 100 commits from the jq GitHub repo.
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `dust -c '(length (from_json input))' -p jq_data.json` | 6.8 ± 0.6 | 5.7 | 12.0 | 2.20 ± 0.40 |
| `jq 'length' jq_data.json` | 43.3 ± 3.6 | 37.6 | 81.6 | 13.95 ± 2.49 |
| `node --eval "require('node:fs').readFile('jq_data.json', (err, data)=>{console.log(JSON.parse(data).length)})"` | 224.9 ± 12.3 | 194.8 | 298.5 | 72.52 ± 12.17 |
| `nu -c 'open jq_data.json \| length'` | 59.2 ± 5.7 | 49.7 | 125.0 | 19.11 ± 3.55 |
This data came from CERN, it is a massive file of 100,000 entries.
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `dust -c '(length (from_json input))' -p dielectron.json` | 1080.8 ± 38.7 | 975.3 | 1326.6 | 348.61 ± 56.71 |
| `jq 'length' dielectron.json` | 1305.3 ± 64.3 | 1159.7 | 1925.1 | 421.00 ± 69.94 |
| `node --eval "require('node:fs').readFile('dielectron.json', (err, data)=>{console.log(JSON.parse(data).length)})"` | 1850.5 ± 72.5 | 1641.9 | 2395.1 | 596.85 ± 97.54 |
| `nu -c 'open dielectron.json \| length'` | 1850.5 ± 86.2 | 1625.5 | 2400.7 | 596.87 ± 98.70 |
The tests were run after 5 warmup runs and the cache was cleared before each run.
```sh
hyperfine \
--shell none \
--warmup 5 \
--prepare "rm -rf /root/.cache" \
--runs 1000 \
--parameter-list data_path seaCreatures.json,jq_data.json,dielectron.json \
--export-markdown test_output.md \
"dust -c '(length (from_json input))' -p {data_path}" \
"jq 'length' {data_path}" \
"node --eval \"require('node:fs').readFile('{data_path}',(err,data)=>{console.log(JSON.parse(data).length)})\"" \
"nu -c 'open {data_path} | length'"
```
## Implementation
Dust is implemented in Rust and is divided into several parts, primarily the lexer, compiler, and
virtual machine. All of Dust's components are designed with performance in mind and the codebase
uses as few dependencies as possible.
Dust is formally defined as a Tree Sitter grammar in the tree-sitter-dust directory. Tree sitter generates a parser, written in C, from a set of rules defined in JavaScript. Dust itself is a rust binary that calls the C parser using FFI.
### Lexer
Tests are written in three places: in the Rust library, in Dust as examples and in the Tree Sitter test format. Generally, features are added by implementing and testing the syntax in the tree-sitter-dust repository, then writing library tests to evaluate the new syntax. Implementation tests run the Dust files in the "examples" directory and should be used to demonstrate and verify that features work together.
The lexer emits tokens from the source code. Dust makes extensive use of Rust's zero-copy
capabilities to avoid unnecessary allocations when creating tokens. A token, depending on its type,
may contain a reference to some data from the source code. The data is only copied in the case of an
error, because it improves the usability of the codebase for errors to own their data when possible.
In a successfully executed program, no part of the source code is copied unless it is a string
literal or identifier.
Tree Sitter generates a concrete syntax tree, which Dust traverses to create an abstract syntax tree that can run the Dust code. The CST generation is an extra step but it allows easy testing of the parser, defining the language in one file and makes the syntax easy to modify and expand. Because it uses Tree Sitter, developer-friendly features like syntax highlighting and code navigation are already available in any text editor that supports Tree Sitter.
### Compiler
## The Dust Programming Language
The compiler creates a chunk, which contains all of the data needed by the virtual machine to run a
Dust program. It does so by emitting bytecode instructions, constants and locals while parsing the
tokens, which are generated one at a time by the lexer.
Dust is easy to learn. Aside from this guide, the best way to learn Dust is to read the examples and tests to get a better idea of what it can do.
#### Parsing
### Declaring Variables
Dust's compiler uses a custom Pratt parser, a kind of recursive descent parser, to translate a
sequence of tokens into a chunk.
Variables have two parts: a key and a value. The key is always a string. The value can be any of the following data types:
#### Optimizing
- string
- integer
- floating point value
- boolean
- list
- map
- table
- function
When generating instructions for a register-based virtual machine, there are opportunities to
optimize the generated code, usually by consolidating register use or reusing registers within an
expression. While it is best to output optimal code in the first place, it is not always possible.
Dust's compiler has a simple peephole optimizer that can be used to modify isolated sections of the
instruction list through a mutable reference.
Here are some examples of variables in dust.
### Instructions
```dust
string = "The answer is 42."
integer = 42
float = 42.42
list = [1 2 string integer float] # Commas are optional when writing lists.
map = {
key = 'value'
}
```
### Virtual Machine
Note that strings can be wrapped with any kind of quote: single, double or backticks. Numbers are always integers by default. Floats are declared by adding a decimal. If you divide integers or do any kind of math with a float, you will create a float value.
## Previous Implementations
### Lists
## Inspiration
Lists are sequential collections. They can be built by grouping values with square brackets. Commas are optional. Values can be indexed by their position using dot notation with an integer. Dust lists are zero-indexed.
- [The Implementation of Lua 5.0](https://www.lua.org/doc/jucs05.pdf)
- [A No-Frills Introduction to Lua 5.1 VM Instructions](https://www.mcours.net/cours/pdf/hasclic3/hasssclic818.pdf)
- [Crafting Interpreters](https://craftinginterpreters.com/)
```dust
list = [true 41 "Ok"]
(assert_equal list.0 true)
the_answer = list.1 + 1
(assert_equal the_answer, 42) # You can also use commas when passing values to
# a function.
```
### Maps
Maps are flexible collections with arbitrary key-value pairs, similar to JSON objects. A map is created with a pair of curly braces and its entries are variables declared inside those braces. Map contents can be accessed using dot notation.
```dust
reminder = {
message = "Buy milk"
tags = ["groceries", "home"]
}
(output reminder.message)
```
### Loops
A **while** loop continues until a predicate is false.
```dust
i = 0
while i < 10 {
(output i)
i += 1
}
```
A **for** loop operates on a list without mutating it or the items inside. It does not return a value.
```dust
list = [ 1, 2, 3 ]
for number in list {
(output number + 1)
}
```
To create a new list, use a **transform** loop, which modifies the values into a new list without changing the original.
```dust
list = [1 2 3]
new_list = transform number in list {
number - 1
}
list
-> filter()
-> ()
(output new_list)
# Output: [ 0 1 2 ]
(output list)
# Output: [ 1 2 3 ]
```
To filter out some of the values in a list, use a **filter** loop.
```dust
list = filter number in [1 2 3] {
number >= 2
}
(output list)
# Output: [ 2 3 ]
```
A **find** loop will return a single value, the first item that satisfies the predicate.
```dust
found = find number in [1 2 1] {
number != 1
}
(output found)
# Output: 2
```
### Tables
Tables are strict collections, each row must have a value for each column. If a value is "missing" it should be set to an appropriate value for that type. For example, a string can be empty and a number can be set to zero. Dust table declarations consist of a list of column names, which are identifiers enclosed in pointed braces, followed by a list of rows.
```dust
animals = table <name species age> [
["rover" "cat" 14]
["spot" "snake" 9]
["bob" "giraffe" 2]
]
```
Querying a table is similar to SQL.
```dust
names = select name from animals
youngins = select species from animals {
age <= 10
}
```
The keywords `table` and `insert` make sure that all of the memory used to hold the rows is allocated at once, so it is good practice to group your rows together instead of using a call for each row.
```dust
insert into animals [
["eliza" "ostrich" 4]
["pat" "white rhino" 7]
["jim" "walrus" 9]
]
(assert_equal 6 (length animals))
```
### Functions
Functions are first-class values in dust, so they are assigned to variables like any other value. The function body is wrapped in single parentheses. To create a function, use the "function" keyword. The function's arguments are identifiers inside of a set of pointed braces and the function body is enclosed in curly braces. To call a fuction, invoke its variable name inside a set of parentheses. You don't need commas when listing arguments and you don't need to add whitespace inside the function body but doing so may make your code easier to read.
```dust
say_hi = function <> {
(output "hi")
}
add_one = function <number> {
(number + 1)
}
(say_hi)
(assert_equal (add_one 3), 4)
```
This function simply passes the input to the shell's standard output.
```dust
print = function <input> {
(output input)
}
```
### Concurrency
As a language written in Rust, Dust features effortless concurrency anywhere in your code.
```dust
async {
(output (random_integer))
(output (random_float))
(output (random_boolean))
}
```
In an **async** block, each statement is run in parallel. In this case, we want to read from a file and assign the data to a variable. It doesn't matter which statement finishes first, the last statement in the block will be used as the assigned value. If one of the statements in an **async** block produces an error, the other statements will stop running if they have not already finished.
```dust
data = async {
(output "Reading a file...")
(read "examples/assets/faithful.csv")
}
```
[Tree Sitter]: https://tree-sitter.github.io/tree-sitter/
[Rust]: https://rust-lang.org
[dnf]: https://dnf.readthedocs.io/en/latest/index.html
[evalexpr]: https://github.com/ISibboI/evalexpr
[rustup]: https://rustup.rs

15
build.rs Normal file
View File

@ -0,0 +1,15 @@
fn main() {
let src_dir = std::path::Path::new("tree-sitter-dust/src");
let mut c_config = cc::Build::new();
c_config.include(src_dir);
c_config
.flag_if_supported("-Wno-unused-parameter")
.flag_if_supported("-Wno-unused-but-set-variable")
.flag_if_supported("-Wno-trigraphs");
let parser_path = src_dir.join("parser.c");
c_config.file(&parser_path);
c_config.compile("parser");
println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap());
}

View File

@ -1,23 +0,0 @@
[package]
name = "dust-lang"
description = "Interpreter library for the Dust programming language"
version = "0.5.0"
authors.workspace = true
edition.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
[dependencies]
annotate-snippets = "0.11.4"
colored = "2.1.0"
log = "0.4.22"
rand = "0.8.5"
serde = { version = "1.0.203", features = ["derive"] }
serde_json = "1.0.117"
getrandom = { version = "0.2", features = [
"js",
] } # Indirect dependency, for wasm builds
[dev-dependencies]
env_logger = "0.11.5"

View File

@ -1,402 +0,0 @@
//! In-memory representation of a Dust program or function.
//!
//! A chunk consists of a sequence of instructions and their positions, a list of constants, and a
//! list of locals that can be executed by the Dust virtual machine. Chunks have a name when they
//! belong to a named function.
use std::{
cmp::Ordering,
fmt::{self, Debug, Display, Formatter},
};
use serde::{Deserialize, Serialize};
use crate::{Disassembler, Instruction, Operation, Span, Type, Value};
/// In-memory representation of a Dust program or function.
///
/// See the [module-level documentation](index.html) for more information.
#[derive(Clone, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Chunk {
name: Option<String>,
pub is_poisoned: bool,
instructions: Vec<(Instruction, Span)>,
constants: Vec<Value>,
locals: Vec<Local>,
current_scope: Scope,
block_index: u8,
}
impl Chunk {
pub fn new(name: Option<String>) -> Self {
Self {
name,
is_poisoned: false,
instructions: Vec::new(),
constants: Vec::new(),
locals: Vec::new(),
current_scope: Scope::default(),
block_index: 0,
}
}
pub fn with_data(
name: Option<String>,
instructions: Vec<(Instruction, Span)>,
constants: Vec<Value>,
locals: Vec<Local>,
) -> Self {
Self {
name,
is_poisoned: false,
instructions,
constants,
locals,
current_scope: Scope::default(),
block_index: 0,
}
}
pub fn name(&self) -> Option<&String> {
self.name.as_ref()
}
pub fn set_name(&mut self, name: String) {
self.name = Some(name);
}
pub fn len(&self) -> usize {
self.instructions.len()
}
pub fn is_empty(&self) -> bool {
self.instructions.is_empty()
}
pub fn constants(&self) -> &Vec<Value> {
&self.constants
}
pub fn constants_mut(&mut self) -> &mut Vec<Value> {
&mut self.constants
}
pub fn take_constants(self) -> Vec<Value> {
self.constants
}
pub fn instructions(&self) -> &Vec<(Instruction, Span)> {
&self.instructions
}
pub fn instructions_mut(&mut self) -> &mut Vec<(Instruction, Span)> {
&mut self.instructions
}
pub fn get_instruction(&self, index: usize) -> Result<&(Instruction, Span), ChunkError> {
self.instructions
.get(index)
.ok_or(ChunkError::InstructionIndexOutOfBounds { index })
}
pub fn locals(&self) -> &Vec<Local> {
&self.locals
}
pub fn locals_mut(&mut self) -> &mut Vec<Local> {
&mut self.locals
}
pub fn get_local(&self, index: u8) -> Result<&Local, ChunkError> {
self.locals
.get(index as usize)
.ok_or(ChunkError::LocalIndexOutOfBounds {
index: index as usize,
})
}
pub fn get_local_mut(&mut self, index: u8) -> Result<&mut Local, ChunkError> {
self.locals
.get_mut(index as usize)
.ok_or(ChunkError::LocalIndexOutOfBounds {
index: index as usize,
})
}
pub fn current_scope(&self) -> Scope {
self.current_scope
}
pub fn get_constant(&self, index: u8) -> Result<&Value, ChunkError> {
self.constants
.get(index as usize)
.ok_or(ChunkError::ConstantIndexOutOfBounds {
index: index as usize,
})
}
pub fn push_or_get_constant(&mut self, value: Value) -> u8 {
if let Some(index) = self
.constants
.iter()
.position(|constant| constant == &value)
{
return index as u8;
}
self.constants.push(value);
(self.constants.len() - 1) as u8
}
pub fn get_identifier(&self, local_index: u8) -> Option<String> {
self.locals.get(local_index as usize).and_then(|local| {
self.constants
.get(local.identifier_index as usize)
.map(|value| value.to_string())
})
}
pub fn begin_scope(&mut self) {
self.block_index += 1;
self.current_scope.block_index = self.block_index;
self.current_scope.depth += 1;
}
pub fn end_scope(&mut self) {
self.current_scope.depth -= 1;
if self.current_scope.depth == 0 {
self.current_scope.block_index = 0;
} else {
self.current_scope.block_index -= 1;
}
}
pub fn expect_not_poisoned(&self) -> Result<(), ChunkError> {
if self.is_poisoned {
Err(ChunkError::PoisonedChunk)
} else {
Ok(())
}
}
pub fn get_constant_type(&self, constant_index: u8) -> Option<Type> {
self.constants
.get(constant_index as usize)
.map(|value| value.r#type())
}
pub fn get_local_type(&self, local_index: u8) -> Option<Type> {
self.locals.get(local_index as usize)?.r#type.clone()
}
pub fn get_register_type(&self, register_index: u8) -> Option<Type> {
let local_type_option = self
.locals
.iter()
.find(|local| local.register_index == register_index)
.map(|local| local.r#type.clone());
if let Some(local_type) = local_type_option {
return local_type;
}
self.instructions
.iter()
.enumerate()
.find_map(|(index, (instruction, _))| {
if let Operation::LoadList = instruction.operation() {
if instruction.a() == register_index {
let mut length = (instruction.c() - instruction.b() + 1) as usize;
let mut item_type = Type::Any;
let distance_to_end = self.len() - index;
for (instruction, _) in self
.instructions()
.iter()
.rev()
.skip(distance_to_end)
.take(length)
{
if let Operation::Close = instruction.operation() {
length -= (instruction.c() - instruction.b()) as usize;
} else if let Type::Any = item_type {
item_type = instruction.yielded_type(self).unwrap_or(Type::Any);
}
}
return Some(Type::List {
item_type: Box::new(item_type),
length,
});
}
}
if instruction.yields_value() && instruction.a() == register_index {
instruction.yielded_type(self)
} else {
None
}
})
}
pub fn return_type(&self) -> Option<Type> {
let returns_value = self
.instructions()
.last()
.map(|(instruction, _)| {
debug_assert!(matches!(instruction.operation(), Operation::Return));
instruction.b_as_boolean()
})
.unwrap_or(false);
if returns_value {
self.instructions.iter().rev().find_map(|(instruction, _)| {
if instruction.yields_value() {
instruction.yielded_type(self)
} else {
None
}
})
} else {
None
}
}
pub fn disassembler(&self) -> Disassembler {
Disassembler::new(self)
}
}
impl Display for Chunk {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let disassembler = self.disassembler().styled(false);
write!(f, "{}", disassembler.disassemble())
}
}
impl Debug for Chunk {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let disassembly = self.disassembler().styled(false).disassemble();
if cfg!(debug_assertions) {
write!(f, "\n{}", disassembly)
} else {
write!(f, "{}", disassembly)
}
}
}
impl Eq for Chunk {}
impl PartialEq for Chunk {
fn eq(&self, other: &Self) -> bool {
self.instructions == other.instructions
&& self.constants == other.constants
&& self.locals == other.locals
}
}
/// A scoped variable.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Local {
/// The index of the identifier in the constants table.
pub identifier_index: u8,
/// The expected type of the local's value.
pub r#type: Option<Type>,
/// Whether the local is mutable.
pub is_mutable: bool,
/// Scope where the variable was declared.
pub scope: Scope,
/// Expected location of a local's value.
pub register_index: u8,
}
impl Local {
/// Creates a new Local instance.
pub fn new(
identifier_index: u8,
r#type: Option<Type>,
mutable: bool,
scope: Scope,
register_index: u8,
) -> Self {
Self {
identifier_index,
r#type,
is_mutable: mutable,
scope,
register_index,
}
}
}
/// Variable locality, as defined by its depth and block index.
///
/// 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.
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Scope {
/// Level of block nesting.
pub depth: u8,
/// Index of the block in the chunk.
pub block_index: u8,
}
impl Scope {
pub fn new(depth: u8, block_index: u8) -> Self {
Self { depth, block_index }
}
pub fn contains(&self, other: &Self) -> bool {
match self.depth.cmp(&other.depth) {
Ordering::Less => false,
Ordering::Greater => self.block_index >= other.block_index,
Ordering::Equal => self.block_index == other.block_index,
}
}
}
impl Display for Scope {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.depth, self.block_index)
}
}
/// Errors that can occur when using a [`Chunk`].
#[derive(Clone, Debug, PartialEq)]
pub enum ChunkError {
ConstantIndexOutOfBounds { index: usize },
InstructionIndexOutOfBounds { index: usize },
LocalIndexOutOfBounds { index: usize },
PoisonedChunk,
}
impl Display for ChunkError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
ChunkError::ConstantIndexOutOfBounds { index } => {
write!(f, "Constant index {} out of bounds", index)
}
ChunkError::InstructionIndexOutOfBounds { index } => {
write!(f, "Instruction index {} out of bounds", index)
}
ChunkError::LocalIndexOutOfBounds { index } => {
write!(f, "Local index {} out of bounds", index)
}
ChunkError::PoisonedChunk => write!(f, "Chunk is poisoned"),
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,366 +0,0 @@
//! Tool for disassembling chunks into a human-readable format.
//!
//! A disassembler can be created by calling [Chunk::disassembler][] or by instantiating one with
//! [Disassembler::new][].
//!
//! # Options
//!
//! The disassembler can be customized with the 'styled' option, which will apply ANSI color codes
//! to the output.
//!
//! # Output
//!
//! The output of [Disassembler::disassemble] is a string that can be printed to the console or
//! written to a file. Below is an example of the disassembly for a simple "Hello, world!" program.
//!
//! ```text
//! ┌──────────────────────────────────────────────────────────────────────────────┐
//! │ <file name omitted> │
//! │ │
//! │ write_line("Hello, world!") │
//! │ │
//! │ 3 instructions, 1 constants, 0 locals, returns none │
//! │ │
//! │ Instructions │
//! │ ------------ │
//! │ i BYTECODE OPERATION INFO TYPE POSITION │
//! │--- -------- ------------- -------------------- ---------------- ------------ │
//! │ 0 03 LOAD_CONSTANT R0 = C0 str (11, 26) │
//! │ 1 1390117 CALL_NATIVE write_line(R0) (0, 27) │
//! │ 2 18 RETURN (27, 27) │
//! │┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈│
//! │ Locals │
//! │ ------ │
//! │ i IDENTIFIER TYPE MUTABLE SCOPE REGISTER │
//! │ --- ---------- ---------------- ------- ------- -------- │
//! │┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈│
//! │ Constants │
//! │ --------- │
//! │ i VALUE │
//! │ --- --------------- │
//! │ 0 Hello, world! │
//! └──────────────────────────────────────────────────────────────────────────────┘
//! ```
use std::env::current_exe;
use colored::Colorize;
use crate::{Chunk, ConcreteValue, Local, Value};
const INSTRUCTION_HEADER: [&str; 4] = [
"Instructions",
"------------",
" i BYTECODE OPERATION INFO TYPE POSITION ",
"--- -------- ------------- -------------------- ---------------- ----------",
];
const CONSTANT_HEADER: [&str; 4] = [
"Constants",
"---------",
" i VALUE ",
"--- ---------------",
];
const LOCAL_HEADER: [&str; 4] = [
"Locals",
"------",
" i IDENTIFIER TYPE MUTABLE SCOPE REGISTER",
"--- ---------- ---------------- ------- ------- --------",
];
/// Builder that constructs a human-readable representation of a chunk.
///
/// See the [module-level documentation](index.html) for more information.
pub struct Disassembler<'a> {
output: String,
chunk: &'a Chunk,
source: Option<&'a str>,
// Options
styled: bool,
indent: usize,
}
impl<'a> Disassembler<'a> {
pub fn new(chunk: &'a Chunk) -> Self {
Self {
output: String::new(),
chunk,
source: None,
styled: false,
indent: 0,
}
}
/// The default width of the disassembly output. To correctly align the output, this should
/// return the width of the longest line that the disassembler is guaranteed to produce.
pub fn default_width() -> usize {
let longest_line = INSTRUCTION_HEADER[3];
longest_line.chars().count().max(80)
}
pub fn source(mut self, source: &'a str) -> Self {
self.source = Some(source);
self
}
pub fn styled(mut self, styled: bool) -> Self {
self.styled = styled;
self
}
pub fn indent(mut self, indent: usize) -> Self {
self.indent = indent;
self
}
fn push(
&mut self,
text: &str,
center: bool,
style_bold: bool,
style_dim: bool,
add_border: bool,
) {
let width = Disassembler::default_width();
let characters = text.chars().collect::<Vec<char>>();
let content_width = if add_border { width - 2 } else { width };
let (line_characters, remainder) = characters
.split_at_checked(content_width)
.unwrap_or((characters.as_slice(), &[]));
let (left_pad_length, right_pad_length) = {
let extra_space = content_width.saturating_sub(characters.len());
if center {
(extra_space / 2, extra_space / 2 + extra_space % 2)
} else {
(0, extra_space)
}
};
let content = if style_bold {
line_characters
.iter()
.collect::<String>()
.bold()
.to_string()
} else if style_dim {
line_characters
.iter()
.collect::<String>()
.dimmed()
.to_string()
} else {
line_characters.iter().collect::<String>()
};
let length_before_content = self.output.chars().count();
for _ in 0..self.indent {
self.output.push_str("");
}
if add_border {
self.output.push('│');
}
self.output.push_str(&" ".repeat(left_pad_length));
self.output.push_str(&content);
self.output.push_str(&" ".repeat(right_pad_length));
let length_after_content = self.output.chars().count();
let line_length = length_after_content - length_before_content;
if line_length < content_width - 1 {
self.output
.push_str(&" ".repeat(content_width - line_length));
}
if add_border {
self.output.push('│');
}
self.output.push('\n');
if !remainder.is_empty() {
self.push(
remainder.iter().collect::<String>().as_str(),
center,
style_bold,
style_dim,
add_border,
);
}
}
fn push_header(&mut self, header: &str) {
self.push(header, true, self.styled, false, true);
}
fn push_details(&mut self, details: &str) {
self.push(details, true, false, false, true);
}
fn push_border(&mut self, border: &str) {
self.push(border, false, false, false, false);
}
fn push_empty(&mut self) {
self.push("", false, false, false, true);
}
pub fn disassemble(mut self) -> String {
let width = Disassembler::default_width();
let top_border = "".to_string() + &"".repeat(width - 2) + "";
let section_border = "".to_string() + &"".repeat(width - 2) + "";
let bottom_border = "".to_string() + &"".repeat(width - 2) + "";
let name_display = self
.chunk
.name()
.map(|identifier| identifier.to_string())
.unwrap_or_else(|| {
current_exe()
.map(|path| path.to_string_lossy().to_string())
.unwrap_or("Chunk Disassembly".to_string())
});
self.push_border(&top_border);
self.push_header(&name_display);
if let Some(source) = self.source {
self.push_empty();
self.push_details(
&source
.replace(" ", "")
.replace("\n\n", " ")
.replace('\n', " "),
);
self.push_empty();
}
let info_line = format!(
"{} instructions, {} constants, {} locals, returns {}",
self.chunk.len(),
self.chunk.constants().len(),
self.chunk.locals().len(),
self.chunk
.return_type()
.map(|r#type| r#type.to_string())
.unwrap_or("none".to_string())
);
self.push(&info_line, true, false, true, true);
self.push_empty();
for line in INSTRUCTION_HEADER {
self.push_header(line);
}
for (index, (instruction, position)) in self.chunk.instructions().iter().enumerate() {
let bytecode = format!("{:02X}", u32::from(instruction));
let operation = instruction.operation().to_string();
let info = instruction.disassembly_info(self.chunk);
let type_display = instruction
.yielded_type(self.chunk)
.map(|r#type| {
let type_string = r#type.to_string();
if type_string.len() > 16 {
format!("{type_string:.13}...")
} else {
type_string
}
})
.unwrap_or(String::with_capacity(0));
let position = position.to_string();
let instruction_display = format!(
"{index:^3} {bytecode:>8} {operation:13} {info:^20} {type_display:^16} {position:10}"
);
self.push_details(&instruction_display);
}
self.push_border(&section_border);
for line in LOCAL_HEADER {
self.push_header(line);
}
for (
index,
Local {
identifier_index,
r#type,
scope,
register_index,
is_mutable: mutable,
},
) in self.chunk.locals().iter().enumerate()
{
let identifier_display = self
.chunk
.constants()
.get(*identifier_index as usize)
.map(|value| value.to_string())
.unwrap_or_else(|| "unknown".to_string());
let type_display = r#type
.as_ref()
.map(|r#type| {
let type_string = r#type.to_string();
if type_string.len() > 16 {
format!("{type_string:.13}...")
} else {
type_string
}
})
.unwrap_or("unknown".to_string());
let local_display = format!(
"{index:^3} {identifier_display:10} {type_display:16} {mutable:7} {scope:7} {register_index:8}"
);
self.push_details(&local_display);
}
self.push_border(&section_border);
for line in CONSTANT_HEADER {
self.push_header(line);
}
for (index, value) in self.chunk.constants().iter().enumerate() {
let value_display = {
let value_string = value.to_string();
if value_string.len() > 15 {
format!("{value_string:.12}...")
} else {
value_string
}
};
let constant_display = format!("{index:^3} {value_display:^15}");
self.push_details(&constant_display);
if let Value::Concrete(ConcreteValue::Function(function)) = value {
let function_disassembly = function
.chunk()
.disassembler()
.styled(self.styled)
.indent(self.indent + 1)
.disassemble();
self.output.push_str(&function_disassembly);
}
}
self.push_border(&bottom_border);
let _ = self.output.trim_end_matches('\n');
self.output
}
}

View File

@ -1,66 +0,0 @@
//! Top-level Dust errors with source code annotations.
use annotate_snippets::{Level, Renderer, Snippet};
use crate::{vm::VmError, CompileError, Span};
/// A top-level error that can occur during the execution of Dust code.
///
/// This error can display nicely formatted messages with source code annotations.
#[derive(Debug, PartialEq)]
pub enum DustError<'src> {
Compile {
error: CompileError,
source: &'src str,
},
Runtime {
error: VmError,
source: &'src str,
},
}
impl<'src> DustError<'src> {
pub fn report(&self) -> String {
let mut report = String::new();
let renderer = Renderer::styled();
match self {
DustError::Runtime { error, source } => {
let position = error.position();
let label = format!("{}: {}", VmError::title(), error.description());
let details = error
.details()
.unwrap_or_else(|| "While running this code".to_string());
let message = Level::Error.title(&label).snippet(
Snippet::source(source)
.fold(false)
.annotation(Level::Error.span(position.0..position.1).label(&details)),
);
report.push_str(&renderer.render(message).to_string());
}
DustError::Compile { error, source } => {
let position = error.position();
let label = format!("{}: {}", CompileError::title(), error.description());
let details = error
.details()
.unwrap_or_else(|| "While parsing this code".to_string());
let message = Level::Error.title(&label).snippet(
Snippet::source(source)
.fold(false)
.annotation(Level::Error.span(position.0..position.1).label(&details)),
);
report.push_str(&renderer.render(message).to_string());
}
}
report
}
}
pub trait AnnotatedError {
fn title() -> &'static str;
fn description(&self) -> &'static str;
fn details(&self) -> Option<String>;
fn position(&self) -> Span;
}

View File

@ -1,224 +0,0 @@
//! Formatting tools
use std::mem::replace;
use colored::{ColoredString, Colorize, CustomColor};
use crate::{CompileError, DustError, LexError, Lexer, Token};
pub fn format(source: &str, line_numbers: bool, colored: bool) -> Result<String, DustError> {
let lexer = Lexer::new(source);
let formatted = Formatter::new(lexer)
.line_numbers(line_numbers)
.colored(colored)
.format()
.map_err(|error| DustError::Compile {
error: CompileError::Lex(error),
source,
})?;
Ok(formatted)
}
#[derive(Debug)]
pub struct Formatter<'src> {
lexer: Lexer<'src>,
output_lines: Vec<(String, LineKind, usize)>,
next_line: String,
indent: usize,
current_token: Token<'src>,
previous_token: Token<'src>,
// Options
line_numbers: bool,
colored: bool,
}
impl<'src> Formatter<'src> {
pub fn new(mut lexer: Lexer<'src>) -> Self {
let (current_token, _) = lexer.next_token().unwrap();
Self {
lexer,
output_lines: Vec::new(),
next_line: String::new(),
indent: 0,
current_token,
previous_token: Token::Eof,
line_numbers: false,
colored: false,
}
}
pub fn line_numbers(mut self, line_numbers: bool) -> Self {
self.line_numbers = line_numbers;
self
}
pub fn colored(mut self, colored: bool) -> Self {
self.colored = colored;
self
}
pub fn format(&mut self) -> Result<String, LexError> {
let mut line_kind = LineKind::Empty;
self.advance()?;
while self.current_token != Token::Eof {
use Token::*;
if self.current_token.is_expression() && line_kind != LineKind::Assignment {
line_kind = LineKind::Expression;
}
match self.current_token {
Boolean(boolean) => {
self.push_colored(boolean.red());
}
Byte(byte) => {
self.push_colored(byte.green());
}
Character(character) => {
self.push_colored(
character
.to_string()
.custom_color(CustomColor::new(225, 150, 150)),
);
}
Float(float) => {
self.push_colored(float.yellow());
}
Identifier(identifier) => {
self.push_colored(identifier.blue());
self.next_line.push(' ');
}
Integer(integer) => {
self.push_colored(integer.cyan());
}
String(string) => {
self.push_colored(string.magenta());
}
LeftCurlyBrace => {
self.next_line.push_str(self.current_token.as_str());
self.commit_line(LineKind::OpenBlock);
self.indent += 1;
}
RightCurlyBrace => {
self.commit_line(LineKind::CloseBlock);
self.next_line.push_str(self.current_token.as_str());
self.indent -= 1;
}
Semicolon => {
if line_kind != LineKind::Assignment {
line_kind = LineKind::Statement;
}
self.next_line.push_str(self.current_token.as_str());
self.commit_line(line_kind);
}
Let => {
line_kind = LineKind::Assignment;
self.push_colored(self.current_token.as_str().bold());
self.next_line.push(' ');
}
Break | Loop | Return | While => {
line_kind = LineKind::Statement;
self.push_colored(self.current_token.as_str().bold());
self.next_line.push(' ');
}
token => {
self.next_line.push_str(token.as_str());
self.next_line.push(' ');
}
}
}
let mut previous_index = 0;
let mut current_index = 1;
while current_index < self.output_lines.len() {
let (_, previous, _) = &self.output_lines[previous_index];
let (_, current, _) = &self.output_lines[current_index];
match (previous, current) {
(LineKind::Empty, _)
| (_, LineKind::Empty)
| (LineKind::OpenBlock, _)
| (_, LineKind::CloseBlock) => {}
(left, right) if left == right => {}
_ => {
self.output_lines
.insert(current_index, ("".to_string(), LineKind::Empty, 0));
}
}
previous_index += 1;
current_index += 1;
}
let formatted = String::with_capacity(
self.output_lines
.iter()
.fold(0, |total, (line, _, _)| total + line.len()),
);
Ok(self.output_lines.iter().enumerate().fold(
formatted,
|acc, (index, (line, _, indent))| {
let index = if index == 0 {
format!("{:<3}| ", index + 1).dimmed()
} else {
format!("\n{:<3}| ", index + 1).dimmed()
};
let left_pad = " ".repeat(*indent);
format!("{}{}{}{}", acc, index, left_pad, line)
},
))
}
fn advance(&mut self) -> Result<(), LexError> {
if self.lexer.is_eof() {
return Ok(());
}
let (new_token, position) = self.lexer.next_token()?;
log::info!(
"Parsing {} at {}",
new_token.to_string().bold(),
position.to_string()
);
self.previous_token = replace(&mut self.current_token, new_token);
Ok(())
}
fn push_colored(&mut self, colored: ColoredString) {
self.next_line.push_str(&format!("{}", colored));
}
fn commit_line(&mut self, line_kind: LineKind) {
self.output_lines
.push((self.next_line.clone(), line_kind, self.indent));
self.next_line.clear();
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LineKind {
Empty,
Assignment,
Expression,
Statement,
OpenBlock,
CloseBlock,
}

View File

@ -1,902 +0,0 @@
//! An operation and its arguments for the Dust virtual machine.
//!
//! Each instruction is a 32-bit unsigned integer that is divided into five fields:
//! - Bits 0-6: The operation code.
//! - Bit 7: A flag indicating whether the B argument is a constant.
//! - Bit 8: A flag indicating whether the C argument is a constant.
//! - Bits 9-16: The A argument,
//! - Bits 17-24: The B argument.
//! - Bits 25-32: The C argument.
//!
//! Be careful when working with instructions directly. When modifying an instruction, be sure to
//! account for the fact that setting the A, B, or C arguments to 0 will have no effect. It is
//! usually best to remove instructions and insert new ones in their place instead of mutating them.
use serde::{Deserialize, Serialize};
use crate::{Chunk, NativeFunction, Operation, Type};
/// An operation and its arguments for the Dust virtual machine.
///
/// See the [module-level documentation](index.html) for more information.
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Instruction(u32);
impl Instruction {
pub fn with_operation(operation: Operation) -> Instruction {
Instruction(operation as u32)
}
pub fn r#move(to_register: u8, from_register: u8) -> Instruction {
let mut instruction = Instruction(Operation::Move as u32);
instruction.set_a(to_register);
instruction.set_b(from_register);
instruction
}
pub fn close(from_register: u8, to_register: u8) -> Instruction {
let mut instruction = Instruction(Operation::Close as u32);
instruction.set_b(from_register);
instruction.set_c(to_register);
instruction
}
pub fn load_boolean(to_register: u8, value: bool, skip: bool) -> Instruction {
let mut instruction = Instruction(Operation::LoadBoolean as u32);
instruction.set_a(to_register);
instruction.set_b_to_boolean(value);
instruction.set_c_to_boolean(skip);
instruction
}
pub fn load_constant(to_register: u8, constant_index: u8, skip: bool) -> Instruction {
let mut instruction = Instruction(Operation::LoadConstant as u32);
instruction.set_a(to_register);
instruction.set_b(constant_index);
instruction.set_c_to_boolean(skip);
instruction
}
pub fn load_list(to_register: u8, start_register: u8) -> Instruction {
let mut instruction = Instruction(Operation::LoadList as u32);
instruction.set_a(to_register);
instruction.set_b(start_register);
instruction
}
pub fn load_self(to_register: u8) -> Instruction {
let mut instruction = Instruction(Operation::LoadSelf as u32);
instruction.set_a(to_register);
instruction
}
pub fn define_local(to_register: u8, local_index: u8, is_mutable: bool) -> Instruction {
let mut instruction = Instruction(Operation::DefineLocal as u32);
instruction.set_a(to_register);
instruction.set_b(local_index);
instruction.set_c(if is_mutable { 1 } else { 0 });
instruction
}
pub fn get_local(to_register: u8, local_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::GetLocal as u32);
instruction.set_a(to_register);
instruction.set_b(local_index);
instruction
}
pub fn set_local(from_register: u8, local_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::SetLocal as u32);
instruction.set_a(from_register);
instruction.set_b(local_index);
instruction
}
pub fn add(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Add as u32);
instruction.set_a(to_register);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn subtract(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Subtract as u32);
instruction.set_a(to_register);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn multiply(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Multiply as u32);
instruction.set_a(to_register);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn divide(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Divide as u32);
instruction.set_a(to_register);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn modulo(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Modulo as u32);
instruction.set_a(to_register);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn test(to_register: u8, test_value: bool) -> Instruction {
let mut instruction = Instruction(Operation::Test as u32);
instruction.set_a(to_register);
instruction.set_c_to_boolean(test_value);
instruction
}
pub fn test_set(to_register: u8, argument_index: u8, test_value: bool) -> Instruction {
let mut instruction = Instruction(Operation::TestSet as u32);
instruction.set_a(to_register);
instruction.set_b(argument_index);
instruction.set_c_to_boolean(test_value);
instruction
}
pub fn equal(comparison_boolean: bool, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Equal as u32);
instruction.set_a_to_boolean(comparison_boolean);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn less(comparison_boolean: bool, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Less as u32);
instruction.set_a_to_boolean(comparison_boolean);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn less_equal(comparison_boolean: bool, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::LessEqual as u32);
instruction.set_a_to_boolean(comparison_boolean);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn negate(to_register: u8, from_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Negate as u32);
instruction.set_a(to_register);
instruction.set_b(from_index);
instruction
}
pub fn not(to_register: u8, from_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Not as u32);
instruction.set_a(to_register);
instruction.set_b(from_index);
instruction
}
pub fn jump(jump_offset: u8, is_positive: bool) -> Instruction {
let mut instruction = Instruction(Operation::Jump as u32);
instruction.set_b(jump_offset);
instruction.set_c_to_boolean(is_positive);
instruction
}
pub fn call(to_register: u8, function_register: u8, argument_count: u8) -> Instruction {
let mut instruction = Instruction(Operation::Call as u32);
instruction.set_a(to_register);
instruction.set_b(function_register);
instruction.set_c(argument_count);
instruction
}
pub fn call_native(
to_register: u8,
native_fn: NativeFunction,
argument_count: u8,
) -> Instruction {
let mut instruction = Instruction(Operation::CallNative as u32);
let native_fn_byte = native_fn as u8;
instruction.set_a(to_register);
instruction.set_b(native_fn_byte);
instruction.set_c(argument_count);
instruction
}
pub fn r#return(should_return_value: bool) -> Instruction {
let mut instruction = Instruction(Operation::Return as u32);
instruction.set_b_to_boolean(should_return_value);
instruction
}
pub fn operation(&self) -> Operation {
Operation::from((self.0 & 0b0000_0000_0011_1111) as u8)
}
pub fn set_operation(&mut self, operation: Operation) {
self.0 |= u8::from(operation) as u32;
}
pub fn data(&self) -> (Operation, u8, u8, u8, bool, bool) {
(
self.operation(),
self.a(),
self.b(),
self.c(),
self.b_is_constant(),
self.c_is_constant(),
)
}
pub fn a(&self) -> u8 {
(self.0 >> 24) as u8
}
pub fn b(&self) -> u8 {
(self.0 >> 16) as u8
}
pub fn c(&self) -> u8 {
(self.0 >> 8) as u8
}
pub fn a_as_boolean(&self) -> bool {
self.a() != 0
}
pub fn b_as_boolean(&self) -> bool {
self.b() != 0
}
pub fn c_as_boolean(&self) -> bool {
self.c() != 0
}
pub fn set_a_to_boolean(&mut self, boolean: bool) -> &mut Self {
self.set_a(if boolean { 1 } else { 0 });
self
}
pub fn set_b_to_boolean(&mut self, boolean: bool) -> &mut Self {
self.set_b(if boolean { 1 } else { 0 });
self
}
pub fn set_c_to_boolean(&mut self, boolean: bool) -> &mut Self {
self.set_c(if boolean { 1 } else { 0 });
self
}
pub fn set_a(&mut self, to_register: u8) {
self.0 |= (to_register as u32) << 24;
}
pub fn set_b(&mut self, argument: u8) {
self.0 |= (argument as u32) << 16;
}
pub fn set_c(&mut self, argument: u8) {
self.0 |= (argument as u32) << 8;
}
pub fn b_is_constant(&self) -> bool {
self.0 & 0b1000_0000 != 0
}
pub fn c_is_constant(&self) -> bool {
self.0 & 0b0100_0000 != 0
}
pub fn set_b_is_constant(&mut self) -> &mut Self {
self.0 |= 0b1000_0000;
self
}
pub fn set_c_is_constant(&mut self) -> &mut Self {
self.0 |= 0b0100_0000;
self
}
pub fn yields_value(&self) -> bool {
match self.operation() {
Operation::Add
| Operation::Call
| Operation::Divide
| Operation::GetLocal
| Operation::LoadBoolean
| Operation::LoadConstant
| Operation::LoadList
| Operation::LoadSelf
| Operation::Modulo
| Operation::Multiply
| Operation::Negate
| Operation::Not
| Operation::Subtract => true,
Operation::CallNative => {
let native_function = NativeFunction::from(self.b());
native_function.r#type().return_type.is_some()
}
_ => false,
}
}
pub fn yielded_type(&self, chunk: &Chunk) -> Option<Type> {
use Operation::*;
match self.operation() {
Add | Divide | Modulo | Multiply | Subtract => {
if self.b_is_constant() {
chunk.get_constant_type(self.b())
} else {
chunk.get_register_type(self.b())
}
}
LoadBoolean | Not => Some(Type::Boolean),
Negate => {
if self.b_is_constant() {
chunk.get_constant_type(self.b())
} else {
chunk.get_register_type(self.b())
}
}
LoadConstant => chunk.get_constant_type(self.b()),
LoadList => chunk.get_register_type(self.a()),
GetLocal => chunk.get_local_type(self.b()),
CallNative => {
let native_function = NativeFunction::from(self.b());
native_function.r#type().return_type.map(|boxed| *boxed)
}
_ => None,
}
}
pub fn disassembly_info(&self, chunk: &Chunk) -> String {
let format_arguments = || {
let first_argument = if self.b_is_constant() {
format!("C{}", self.b())
} else {
format!("R{}", self.b())
};
let second_argument = if self.c_is_constant() {
format!("C{}", self.c())
} else {
format!("R{}", self.c())
};
(first_argument, second_argument)
};
match self.operation() {
Operation::Move => format!("R{} = R{}", self.a(), self.b()),
Operation::Close => {
let from_register = self.b();
let to_register = self.c().saturating_sub(1);
format!("R{from_register}..=R{to_register}")
}
Operation::LoadBoolean => {
let to_register = self.a();
let boolean = self.b_as_boolean();
let jump = self.c_as_boolean();
if jump {
format!("R{to_register} = {boolean} && SKIP")
} else {
format!("R{to_register} = {boolean}")
}
}
Operation::LoadConstant => {
let register_index = self.a();
let constant_index = self.b();
let jump = self.c_as_boolean();
if jump {
format!("R{register_index} = C{constant_index} && SKIP")
} else {
format!("R{register_index} = C{constant_index}")
}
}
Operation::LoadList => {
let to_register = self.a();
let first_index = self.b();
let last_index = self.c();
format!("R{to_register} = [R{first_index}..=R{last_index}]",)
}
Operation::LoadSelf => {
let to_register = self.a();
let name = chunk
.name()
.map(|idenifier| idenifier.as_str())
.unwrap_or("self");
format!("R{to_register} = {name}")
}
Operation::DefineLocal => {
let to_register = self.a();
let local_index = self.b();
let identifier_display = match chunk.get_identifier(local_index) {
Some(identifier) => identifier.to_string(),
None => "???".to_string(),
};
let mutable_display = if self.c_as_boolean() { "mut" } else { "" };
format!("R{to_register} = L{local_index} {mutable_display} {identifier_display}")
}
Operation::GetLocal => {
let local_index = self.b();
format!("R{} = L{}", self.a(), local_index)
}
Operation::SetLocal => {
let local_index = self.b();
let identifier_display = match chunk.get_identifier(local_index) {
Some(identifier) => identifier.to_string(),
None => "???".to_string(),
};
format!("L{} = R{} {}", local_index, self.a(), identifier_display)
}
Operation::Add => {
let to_register = self.a();
let (first_argument, second_argument) = format_arguments();
format!("R{to_register} = {first_argument} + {second_argument}",)
}
Operation::Subtract => {
let to_register = self.a();
let (first_argument, second_argument) = format_arguments();
format!("R{to_register} = {first_argument} - {second_argument}",)
}
Operation::Multiply => {
let to_register = self.a();
let (first_argument, second_argument) = format_arguments();
format!("R{to_register} = {first_argument} * {second_argument}",)
}
Operation::Divide => {
let to_register = self.a();
let (first_argument, second_argument) = format_arguments();
format!("R{to_register} = {first_argument} / {second_argument}",)
}
Operation::Modulo => {
let to_register = self.a();
let (first_argument, second_argument) = format_arguments();
format!("R{to_register} = {first_argument} % {second_argument}",)
}
Operation::Test => {
let to_register = self.a();
let test_value = self.c_as_boolean();
format!("if R{to_register} != {test_value} {{ SKIP }}")
}
Operation::TestSet => {
let to_register = self.a();
let argument = format!("R{}", self.b());
let test_value = self.c_as_boolean();
let bang = if test_value { "" } else { "!" };
format!("if {bang}R{to_register} {{ R{to_register} = R{argument} }}",)
}
Operation::Equal => {
let comparison_symbol = if self.a_as_boolean() { "==" } else { "!=" };
let (first_argument, second_argument) = format_arguments();
format!("if {first_argument} {comparison_symbol} {second_argument} {{ SKIP }}")
}
Operation::Less => {
let comparison_symbol = if self.a_as_boolean() { "<" } else { ">=" };
let (first_argument, second_argument) = format_arguments();
format!("if {first_argument} {comparison_symbol} {second_argument} {{ SKIP }}")
}
Operation::LessEqual => {
let comparison_symbol = if self.a_as_boolean() { "<=" } else { ">" };
let (first_argument, second_argument) = format_arguments();
format!("if {first_argument} {comparison_symbol} {second_argument} {{ SKIP }}")
}
Operation::Negate => {
let to_register = self.a();
let argument = if self.b_is_constant() {
format!("C{}", self.b())
} else {
format!("R{}", self.b())
};
format!("R{to_register} = -{argument}")
}
Operation::Not => {
let to_register = self.a();
let argument = if self.b_is_constant() {
format!("C{}", self.b())
} else {
format!("R{}", self.b())
};
format!("R{to_register} = !{argument}")
}
Operation::Jump => {
let jump_distance = self.b();
let is_positive = self.c_as_boolean();
if is_positive {
format!("JUMP +{jump_distance}")
} else {
format!("JUMP -{jump_distance}")
}
}
Operation::Call => {
let to_register = self.a();
let function_register = self.b();
let argument_count = self.c();
let mut output = format!("R{to_register} = R{function_register}(");
if argument_count != 0 {
let first_argument = function_register + 1;
for (index, register) in
(first_argument..first_argument + argument_count).enumerate()
{
if index > 0 {
output.push_str(", ");
}
output.push_str(&format!("R{}", register));
}
}
output.push(')');
output
}
Operation::CallNative => {
let to_register = self.a();
let native_function = NativeFunction::from(self.b());
let argument_count = self.c();
let mut output = String::new();
let native_function_name = native_function.as_str();
if native_function.r#type().return_type.is_some() {
output.push_str(&format!("R{} = {}(", to_register, native_function_name));
} else {
output.push_str(&format!("{}(", native_function_name));
}
if argument_count != 0 {
let first_argument = to_register.saturating_sub(argument_count);
for (index, register) in (first_argument..to_register).enumerate() {
if index > 0 {
output.push_str(", ");
}
output.push_str(&format!("R{}", register));
}
}
output.push(')');
output
}
Operation::Return => {
let should_return_value = self.b_as_boolean();
if should_return_value {
"->".to_string()
} else {
"".to_string()
}
}
}
}
}
impl From<&Instruction> for u32 {
fn from(instruction: &Instruction) -> Self {
instruction.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn r#move() {
let mut instruction = Instruction::r#move(0, 1);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::Move);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
}
#[test]
fn close() {
let instruction = Instruction::close(1, 2);
assert_eq!(instruction.operation(), Operation::Close);
assert_eq!(instruction.b(), 1);
assert_eq!(instruction.c(), 2);
}
#[test]
fn load_boolean() {
let instruction = Instruction::load_boolean(4, true, true);
assert_eq!(instruction.operation(), Operation::LoadBoolean);
assert_eq!(instruction.a(), 4);
assert!(instruction.a_as_boolean());
assert!(instruction.c_as_boolean());
}
#[test]
fn load_constant() {
let mut instruction = Instruction::load_constant(0, 1, true);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::LoadConstant);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
assert!(instruction.c_as_boolean());
}
#[test]
fn load_list() {
let instruction = Instruction::load_list(0, 1);
assert_eq!(instruction.operation(), Operation::LoadList);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
}
#[test]
fn load_self() {
let instruction = Instruction::load_self(10);
assert_eq!(instruction.operation(), Operation::LoadSelf);
assert_eq!(instruction.a(), 10);
}
#[test]
fn declare_local() {
let mut instruction = Instruction::define_local(0, 1, true);
instruction.set_b_is_constant();
assert_eq!(instruction.operation(), Operation::DefineLocal);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert_eq!(instruction.c(), true as u8);
assert!(instruction.b_is_constant());
}
#[test]
fn add() {
let mut instruction = Instruction::add(1, 1, 0);
instruction.set_b_is_constant();
assert_eq!(instruction.operation(), Operation::Add);
assert_eq!(instruction.a(), 1);
assert_eq!(instruction.b(), 1);
assert_eq!(instruction.c(), 0);
assert!(instruction.b_is_constant());
}
#[test]
fn subtract() {
let mut instruction = Instruction::subtract(0, 1, 2);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::Subtract);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert_eq!(instruction.c(), 2);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
}
#[test]
fn multiply() {
let mut instruction = Instruction::multiply(0, 1, 2);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::Multiply);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert_eq!(instruction.c(), 2);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
}
#[test]
fn divide() {
let mut instruction = Instruction::divide(0, 1, 2);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::Divide);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert_eq!(instruction.c(), 2);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
}
#[test]
fn and() {
let instruction = Instruction::test(4, true);
assert_eq!(instruction.operation(), Operation::Test);
assert_eq!(instruction.a(), 4);
assert!(instruction.c_as_boolean());
}
#[test]
fn or() {
let instruction = Instruction::test_set(4, 1, true);
assert_eq!(instruction.operation(), Operation::TestSet);
assert_eq!(instruction.a(), 4);
assert_eq!(instruction.b(), 1);
assert!(instruction.c_as_boolean());
}
#[test]
fn equal() {
let mut instruction = Instruction::equal(true, 1, 2);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::Equal);
assert!(instruction.a_as_boolean());
assert_eq!(instruction.b(), 1);
assert_eq!(instruction.c(), 2);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
}
#[test]
fn negate() {
let mut instruction = Instruction::negate(0, 1);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::Negate);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
}
#[test]
fn not() {
let mut instruction = Instruction::not(0, 1);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::Not);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
}
#[test]
fn jump() {
let instruction = Instruction::jump(4, true);
assert_eq!(instruction.operation(), Operation::Jump);
assert_eq!(instruction.b(), 4);
assert!(instruction.c_as_boolean());
}
#[test]
fn call() {
let instruction = Instruction::call(1, 3, 4);
assert_eq!(instruction.operation(), Operation::Call);
assert_eq!(instruction.a(), 1);
assert_eq!(instruction.b(), 3);
assert_eq!(instruction.c(), 4);
}
#[test]
fn r#return() {
let instruction = Instruction::r#return(true);
assert_eq!(instruction.operation(), Operation::Return);
assert!(instruction.b_as_boolean());
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,44 +0,0 @@
//! The Dust programming language library.
pub mod chunk;
pub mod compiler;
pub mod disassembler;
pub mod dust_error;
pub mod formatter;
pub mod instruction;
pub mod lexer;
pub mod native_function;
pub mod operation;
pub mod optimizer;
pub mod token;
pub mod r#type;
pub mod value;
pub mod vm;
pub use crate::chunk::{Chunk, ChunkError, Local, Scope};
pub use crate::compiler::{compile, CompileError, Compiler};
pub use crate::disassembler::Disassembler;
pub use crate::dust_error::{AnnotatedError, DustError};
pub use crate::formatter::{format, Formatter};
pub use crate::instruction::Instruction;
pub use crate::lexer::{lex, LexError, Lexer};
pub use crate::native_function::{NativeFunction, NativeFunctionError};
pub use crate::operation::Operation;
pub use crate::optimizer::{optimize, Optimizer};
pub use crate::r#type::{EnumType, FunctionType, RangeableType, StructType, Type, TypeConflict};
pub use crate::token::{Token, TokenKind, TokenOwned};
pub use crate::value::{ConcreteValue, Function, Value, ValueError};
pub use crate::vm::{run, Vm, VmError};
use std::fmt::Display;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Span(pub usize, pub usize);
impl Display for Span {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({}, {})", self.0, self.1)
}
}

View File

@ -1,418 +0,0 @@
//! Built-in functions that implement extended functionality.
//!
//! Native functions are used either to implement features that are not possible to implement in
//! Dust itself or that are more efficient to implement in Rust.
use std::{
fmt::{self, Display, Formatter},
io::{self, stdin, stdout, Write},
string::{self},
};
use serde::{Deserialize, Serialize};
use crate::{
AnnotatedError, ConcreteValue, FunctionType, Instruction, Span, Type, Value, Vm, VmError,
};
macro_rules! define_native_function {
($(($name:ident, $byte:literal, $str:expr, $type:expr)),*) => {
/// A dust-native function.
///
/// See the [module-level documentation](index.html) for more information.
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum NativeFunction {
$(
$name = $byte as isize,
)*
}
impl NativeFunction {
pub fn as_str(&self) -> &'static str {
match self {
$(
NativeFunction::$name => $str,
)*
}
}
#[allow(clippy::should_implement_trait)]
pub fn from_str(string: &str) -> Option<Self> {
match string {
$(
$str => Some(NativeFunction::$name),
)*
_ => None,
}
}
pub fn r#type(&self) -> FunctionType {
match self {
$(
NativeFunction::$name => $type,
)*
}
}
pub fn returns_value(&self) -> bool {
match self {
$(
NativeFunction::$name => $type.return_type.is_some(),
)*
}
}
}
impl From<u8> for NativeFunction {
fn from(byte: u8) -> Self {
match byte {
$(
$byte => NativeFunction::$name,
)*
_ => {
if cfg!(test) {
panic!("Invalid native function byte: {}", byte)
} else {
NativeFunction::Panic
}
}
}
}
}
impl From<NativeFunction> for u8 {
fn from(native_function: NativeFunction) -> Self {
match native_function {
$(
NativeFunction::$name => $byte,
)*
}
}
}
};
}
define_native_function! {
// Assertion
(
Assert,
0_u8,
"assert",
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: None
}
),
// (AssertEqual, 1_u8, "assert_equal", false),
// (AssertNotEqual, 2_u8, "assert_not_equal", false),
(
Panic,
3_u8,
"panic",
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Some(Box::new(Type::Any))
}
),
// // Type conversion
// (Parse, 4_u8, "parse", true),
// (ToByte, 5_u8, "to_byte", true),
// (ToFloat, 6_u8, "to_float", true),
// (ToInteger, 7_u8, "to_integer", true),
(
ToString,
8_u8,
"to_string",
FunctionType {
type_parameters: None,
value_parameters: Some(vec![(0, Type::Any)]),
return_type: Some(Box::new(Type::String { length: None }))
}
),
// // List and string
// (All, 9_u8, "all", true),
// (Any, 10_u8, "any", true),
// (Append, 11_u8, "append", false),
// (Contains, 12_u8, "contains", true),
// (Dedup, 13_u8, "dedup", false),
// (EndsWith, 14_u8, "ends_with", true),
// (Find, 15_u8, "find", true),
// (Get, 16_u8, "get", true),
// (IndexOf, 17_u8, "index_of", true),
// (Length, 18_u8, "length", true),
// (Prepend, 19_u8, "prepend", false),
// (Replace, 20_u8, "replace", false),
// (Set, 21_u8, "set", false),
// (StartsWith, 22_u8, "starts_with", true),
// (Slice, 23_u8, "slice", true),
// (Sort, 24_u8, "sort", false),
// (Split, 25_u8, "split", true),
// // List
// (Flatten, 26_u8, "flatten", false),
// (Join, 27_u8, "join", true),
// (Map, 28_u8, "map", true),
// (Reduce, 29_u8, "reduce", true),
// (Remove, 30_u8, "remove", false),
// (Reverse, 31_u8, "reverse", false),
// (Unzip, 32_u8, "unzip", true),
// (Zip, 33_u8, "zip", true),
// // String
// (Bytes, 34_u8, "bytes", true),
// (CharAt, 35_u8, "char_at", true),
// (CharCodeAt, 36_u8, "char_code_at", true),
// (Chars, 37_u8, "chars", true),
// (Format, 38_u8, "format", true),
// (Repeat, 39_u8, "repeat", true),
// (SplitAt, 40_u8, "split_at", true),
// (SplitLines, 41_u8, "split_lines", true),
// (SplitWhitespace, 42_u8, "split_whitespace", true),
// (ToLowerCase, 43_u8, "to_lower_case", true),
// (ToUpperCase, 44_u8, "to_upper_case", true),
// (Trim, 45_u8, "trim", true),
// (TrimEnd, 46_u8, "trim_end", true),
// (TrimStart, 47_u8, "trim_start", true),
// // I/O
// // Read
// (Read, 48_u8, "read", true),
// (ReadFile, 49_u8, "read_file", true),
(
ReadLine,
50_u8,
"read_line",
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Some(Box::new(Type::String { length: None }))
}
),
// (ReadTo, 51_u8, "read_to", false),
// (ReadUntil, 52_u8, "read_until", true),
// // Write
// (AppendFile, 53_u8, "append_file", false),
// (PrependFile, 54_u8, "prepend_file", false),
(
Write,
55_u8,
"write",
FunctionType {
type_parameters: None,
value_parameters: Some(vec![(0, Type::String { length: None })]),
return_type: None
}
),
// (WriteFile, 56_u8, "write_file", false),
(
WriteLine,
57_u8,
"write_line",
FunctionType {
type_parameters: None,
value_parameters: Some(vec![(0, Type::String { length: None })]),
return_type: None
}
)
// // Random
// (Random, 58_u8, "random", true),
// (RandomInRange, 59_u8, "random_in_range", true)
}
impl NativeFunction {
pub fn call(
&self,
instruction: Instruction,
vm: &Vm,
position: Span,
) -> Result<Option<Value>, VmError> {
let to_register = instruction.a();
let argument_count = instruction.c();
let return_value = match self {
NativeFunction::Panic => {
let message = if argument_count == 0 {
None
} else {
let mut message = String::new();
for argument_index in 0..argument_count {
if argument_index != 0 {
message.push(' ');
}
let argument = vm.open_register(argument_index, position)?;
message.push_str(&argument.to_string());
}
Some(message)
};
return Err(VmError::NativeFunction(NativeFunctionError::Panic {
message,
position,
}));
}
// Type conversion
NativeFunction::ToString => {
let mut string = String::new();
for argument_index in 0..argument_count {
let argument = vm.open_register(argument_index, position)?;
string.push_str(&argument.to_string());
}
Some(Value::Concrete(ConcreteValue::String(string)))
}
// I/O
NativeFunction::ReadLine => {
let mut buffer = String::new();
stdin().read_line(&mut buffer).map_err(|io_error| {
VmError::NativeFunction(NativeFunctionError::Io {
error: io_error.kind(),
position,
})
})?;
buffer = buffer.trim_end_matches('\n').to_string();
Some(Value::Concrete(ConcreteValue::String(buffer)))
}
NativeFunction::Write => {
let to_register = instruction.a();
let mut stdout = stdout();
let map_err = |io_error: io::Error| {
VmError::NativeFunction(NativeFunctionError::Io {
error: io_error.kind(),
position,
})
};
let first_argument = to_register.saturating_sub(argument_count);
let last_argument = to_register.saturating_sub(1);
for argument_index in first_argument..=last_argument {
if argument_index != first_argument {
stdout.write(b" ").map_err(map_err)?;
}
let argument_string = vm.open_register(argument_index, position)?.to_string();
stdout
.write_all(argument_string.as_bytes())
.map_err(map_err)?;
}
None
}
NativeFunction::WriteLine => {
let mut stdout = stdout();
let map_err = |io_error: io::Error| {
VmError::NativeFunction(NativeFunctionError::Io {
error: io_error.kind(),
position,
})
};
let first_index = to_register.saturating_sub(argument_count);
let arguments = vm.open_nonempty_registers(first_index..to_register, position)?;
for (index, argument) in arguments.into_iter().enumerate() {
if index != 0 {
stdout.write(b" ").map_err(map_err)?;
}
if let Value::Concrete(ConcreteValue::String(string)) = argument {
let bytes = string.as_bytes();
stdout.write_all(bytes).map_err(map_err)?;
} else {
let bytes = argument.to_string().into_bytes();
stdout.write_all(&bytes).map_err(map_err)?;
}
}
stdout.write(b"\n").map_err(map_err)?;
None
}
_ => todo!(),
};
Ok(return_value)
}
}
impl Display for NativeFunction {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum NativeFunctionError {
ExpectedArgumentCount {
expected: usize,
found: usize,
position: Span,
},
Panic {
message: Option<String>,
position: Span,
},
Parse {
error: string::ParseError,
position: Span,
},
Io {
error: io::ErrorKind,
position: Span,
},
}
impl AnnotatedError for NativeFunctionError {
fn title() -> &'static str {
"Native Function Error"
}
fn description(&self) -> &'static str {
match self {
NativeFunctionError::ExpectedArgumentCount { .. } => {
"Expected a different number of arguments"
}
NativeFunctionError::Panic { .. } => "Explicit panic",
NativeFunctionError::Parse { .. } => "Failed to parse value",
NativeFunctionError::Io { .. } => "I/O error",
}
}
fn details(&self) -> Option<String> {
match self {
NativeFunctionError::ExpectedArgumentCount {
expected, found, ..
} => Some(format!("Expected {} arguments, found {}", expected, found)),
NativeFunctionError::Panic { message, .. } => message.clone(),
NativeFunctionError::Parse { error, .. } => Some(format!("{}", error)),
NativeFunctionError::Io { error, .. } => Some(format!("{}", error)),
}
}
fn position(&self) -> Span {
match self {
NativeFunctionError::ExpectedArgumentCount { position, .. } => *position,
NativeFunctionError::Panic { position, .. } => *position,
NativeFunctionError::Parse { position, .. } => *position,
NativeFunctionError::Io { position, .. } => *position,
}
}
}

View File

@ -1,110 +0,0 @@
//! Part of an [Instruction][crate::Instruction], which can be executed by the Dust virtual machine.
//!
//! !!! Warning !!!
//! The byte values of the operations matter. The seventh and eighth bits must be zero so that the
//! [Instruction][crate::Instruction] type can use them as flags.
use std::fmt::{self, Display, Formatter};
macro_rules! define_operation {
($(($name:ident, $byte:literal, $str:expr, $type:expr)),*) => {
/// Part of an [Instruction][crate::Instruction], which can be executed by the Dust virtual machine.)
///
/// See the [module-level documentation](index.html) for more information.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Operation {
$(
$name = $byte as isize,
)*
}
impl From<u8> for Operation {
fn from(byte: u8) -> Self {
match byte {
$(
$byte => Operation::$name,
)*
_ => {
if cfg!(test) {
panic!("Invalid operation byte: {}", byte)
} else {
Operation::Return
}
}
}
}
}
impl From<Operation> for u8 {
fn from(operation: Operation) -> Self {
match operation {
$(
Operation::$name => $byte,
)*
}
}
}
impl Display for Operation {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
$(
Operation::$name => write!(f, "{}", $str),
)*
}
}
}
}
}
define_operation! {
(Move, 0b0000_0000, "MOVE", None),
(Close, 0b000_0001, "CLOSE", None),
(LoadBoolean, 0b0000_0010, "LOAD_BOOLEAN", None),
(LoadConstant, 0b0000_0011, "LOAD_CONSTANT", None),
(LoadList, 0b0000_0100, "LOAD_LIST", None),
(LoadSelf, 0b0000_0101, "LOAD_SELF", None),
(DefineLocal, 0b0000_0110, "DEFINE_LOCAL", None),
(GetLocal, 0b0000_0111, "GET_LOCAL", None),
(SetLocal, 0b0000_1000, "SET_LOCAL", None),
(Add, 0b0000_1001, "ADD", None),
(Subtract, 0b0000_1010, "SUBTRACT", None),
(Multiply, 0b0000_1011, "MULTIPLY", None),
(Divide, 0b0000_1100, "DIVIDE", None),
(Modulo, 0b0000_1101, "MODULO", None),
(Test, 0b0000_1110, "TEST", None),
(TestSet, 0b0000_1111, "TEST_SET", None),
(Equal, 0b0001_0000, "EQUAL", None),
(Less, 0b0001_0001, "LESS", None),
(LessEqual, 0b0001_0010, "LESS_EQUAL", None),
(Negate, 0b0001_0011, "NEGATE", None),
(Not, 0b0001_0100, "NOT", None),
(Jump, 0b0001_0101, "JUMP", None),
(Call, 0b0001_0110, "CALL", None),
(CallNative, 0b0001_0111, "CALL_NATIVE", None),
(Return, 0b0001_1000, "RETURN", None)
}
impl Operation {
pub fn is_math(&self) -> bool {
matches!(
self,
Operation::Add
| Operation::Subtract
| Operation::Multiply
| Operation::Divide
| Operation::Modulo
)
}
pub fn is_comparison(&self) -> bool {
matches!(
self,
Operation::Equal | Operation::Less | Operation::LessEqual
)
}
pub fn is_test(&self) -> bool {
matches!(self, Operation::Test | Operation::TestSet)
}
}

View File

@ -1,96 +0,0 @@
//! Tools used by the compiler to optimize a chunk's bytecode.
use std::{iter::Map, slice::Iter};
use crate::{Instruction, Operation, Span};
type MapToOperation = fn(&(Instruction, Span)) -> Operation;
type OperationIter<'iter> = Map<Iter<'iter, (Instruction, Span)>, MapToOperation>;
/// Performs optimizations on a subset of instructions.
pub fn optimize(instructions: &mut [(Instruction, Span)]) -> usize {
Optimizer::new(instructions).optimize()
}
/// An instruction optimizer that mutably borrows instructions from a chunk.
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct Optimizer<'chunk> {
instructions: &'chunk mut [(Instruction, Span)],
}
impl<'chunk> Optimizer<'chunk> {
/// Creates a new optimizer with a mutable reference to some of a chunk's instructions.
pub fn new(instructions: &'chunk mut [(Instruction, Span)]) -> Self {
Self { instructions }
}
/// Potentially mutates the instructions to optimize them.
pub fn optimize(&mut self) -> usize {
let mut optimizations = 0;
if matches!(
self.get_operations(),
Some([
Operation::Equal | Operation::Less | Operation::LessEqual,
Operation::Jump,
Operation::LoadBoolean | Operation::LoadConstant,
Operation::LoadBoolean | Operation::LoadConstant,
])
) {
self.optimize_comparison();
optimizations += 1;
}
optimizations
}
/// Optimizes a comparison operation.
///
/// The instructions must be in the following order:
/// - `Operation::Equal | Operation::Less | Operation::LessEqual`
/// - `Operation::Jump`
/// - `Operation::LoadBoolean | Operation::LoadConstant`
/// - `Operation::LoadBoolean | Operation::LoadConstant`
fn optimize_comparison(&mut self) {
log::debug!("Optimizing comparison");
let first_loader_register = {
let first_loader = &mut self.instructions[2].0;
first_loader.set_c_to_boolean(true);
first_loader.a()
};
let second_loader = &mut self.instructions[3].0;
let mut second_loader_new = Instruction::with_operation(second_loader.operation());
second_loader_new.set_a(first_loader_register);
second_loader_new.set_b(second_loader.b());
second_loader_new.set_c(second_loader.c());
second_loader_new.set_b_to_boolean(second_loader.b_is_constant());
second_loader_new.set_c_to_boolean(second_loader.c_is_constant());
*second_loader = second_loader_new;
}
fn operations_iter(&self) -> OperationIter {
self.instructions
.iter()
.map(|(instruction, _)| instruction.operation())
}
fn get_operations<const COUNT: usize>(&self) -> Option<[Operation; COUNT]> {
if self.instructions.len() < COUNT {
return None;
}
let mut n_operations = [Operation::Return; COUNT];
for (nth, operation) in n_operations.iter_mut().zip(self.operations_iter()) {
*nth = operation;
}
Some(n_operations)
}
}

View File

@ -1,642 +0,0 @@
//! Token, TokenOwned and TokenKind types.
use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
macro_rules! define_tokens {
($($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)]
pub enum Token<'src> {
#[default]
Eof,
$(
$variant $(($data_type))?,
)*
}
#[derive(Debug, PartialEq, Clone)]
/// 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,
)*
}
};
}
define_tokens! {
// Hard-coded values
Boolean(&'src str),
Byte(&'src str),
Character(char),
Float(&'src str),
Identifier(&'src str),
Integer(&'src str),
String(&'src str),
// Keywords
Async,
Bool,
Break,
Else,
FloatKeyword,
Fn,
If,
Int,
Let,
Loop,
Map,
Mut,
Return,
Str,
Struct,
While,
// Symbols
ArrowThin,
BangEqual,
Bang,
Colon,
Comma,
Dot,
DoubleAmpersand,
DoubleDot,
DoubleEqual,
DoublePipe,
Equal,
Greater,
GreaterEqual,
LeftCurlyBrace,
LeftParenthesis,
LeftSquareBrace,
Less,
LessEqual,
Minus,
MinusEqual,
Percent,
PercentEqual,
Plus,
PlusEqual,
RightCurlyBrace,
RightParenthesis,
RightSquareBrace,
Semicolon,
Slash,
SlashEqual,
Star,
StarEqual,
}
impl<'src> Token<'src> {
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
match self {
Token::Eof => 0,
Token::Boolean(text) => text.len(),
Token::Byte(_) => 3,
Token::Character(_) => 3,
Token::Float(text) => text.len(),
Token::Identifier(text) => text.len(),
Token::Integer(text) => text.len(),
Token::String(text) => text.len() + 2,
Token::Async => 5,
Token::ArrowThin => 2,
Token::Bool => 4,
Token::Break => 5,
Token::Else => 4,
Token::FloatKeyword => 5,
Token::Fn => 2,
Token::If => 2,
Token::Int => 3,
Token::Let => 3,
Token::Loop => 4,
Token::Map => 3,
Token::Mut => 3,
Token::Str => 3,
Token::Struct => 6,
Token::While => 5,
Token::BangEqual => 2,
Token::Bang => 1,
Token::Colon => 1,
Token::Comma => 1,
Token::Dot => 1,
Token::DoubleAmpersand => 2,
Token::DoubleDot => 2,
Token::DoubleEqual => 2,
Token::DoublePipe => 2,
Token::Equal => 1,
Token::Greater => 1,
Token::GreaterEqual => 2,
Token::LeftCurlyBrace => 1,
Token::LeftParenthesis => 1,
Token::LeftSquareBrace => 1,
Token::Less => 1,
Token::LessEqual => 2,
Token::Minus => 1,
Token::MinusEqual => 2,
Token::Percent => 1,
Token::PercentEqual => 2,
Token::Plus => 1,
Token::PlusEqual => 2,
Token::Return => 6,
Token::RightCurlyBrace => 1,
Token::RightParenthesis => 1,
Token::RightSquareBrace => 1,
Token::Semicolon => 1,
Token::Slash => 1,
Token::SlashEqual => 2,
Token::Star => 1,
Token::StarEqual => 2,
}
}
pub fn as_str(&self) -> &str {
match self {
Token::Eof => "",
Token::Boolean(text) => text,
Token::Byte(text) => text,
Token::Character(_) => "character token",
Token::Float(text) => text,
Token::Identifier(text) => text,
Token::Integer(text) => text,
Token::String(text) => text,
Token::Async => "async",
Token::ArrowThin => "->",
Token::Bool => "bool",
Token::Break => "break",
Token::Else => "else",
Token::FloatKeyword => "float",
Token::Fn => "fn",
Token::If => "if",
Token::Int => "int",
Token::Let => "let",
Token::Loop => "loop",
Token::Map => "map",
Token::Mut => "mut",
Token::Str => "str",
Token::Struct => "struct",
Token::While => "while",
Token::BangEqual => "!=",
Token::Bang => "!",
Token::Colon => ":",
Token::Comma => ",",
Token::Dot => ".",
Token::DoubleAmpersand => "&&",
Token::DoubleDot => "..",
Token::DoubleEqual => "==",
Token::DoublePipe => "||",
Token::Equal => "=",
Token::Greater => ">",
Token::GreaterEqual => ">=",
Token::LeftCurlyBrace => "{",
Token::LeftParenthesis => "(",
Token::LeftSquareBrace => "[",
Token::Less => "<",
Token::LessEqual => "<=",
Token::Minus => "-",
Token::MinusEqual => "-=",
Token::Percent => "%",
Token::PercentEqual => "%=",
Token::Plus => "+",
Token::PlusEqual => "+=",
Token::Return => "return",
Token::RightCurlyBrace => "}",
Token::RightParenthesis => ")",
Token::RightSquareBrace => "]",
Token::Semicolon => ";",
Token::Slash => "/",
Token::SlashEqual => "/=",
Token::Star => "*",
Token::StarEqual => "*=",
}
}
pub fn to_owned(&self) -> TokenOwned {
match self {
Token::ArrowThin => TokenOwned::ArrowThin,
Token::Async => TokenOwned::Async,
Token::BangEqual => TokenOwned::BangEqual,
Token::Bang => TokenOwned::Bang,
Token::Bool => TokenOwned::Bool,
Token::Boolean(boolean) => TokenOwned::Boolean(boolean.to_string()),
Token::Break => TokenOwned::Break,
Token::Byte(byte) => TokenOwned::Byte(byte.to_string()),
Token::Character(character) => TokenOwned::Character(*character),
Token::Colon => TokenOwned::Colon,
Token::Comma => TokenOwned::Comma,
Token::Dot => TokenOwned::Dot,
Token::DoubleAmpersand => TokenOwned::DoubleAmpersand,
Token::DoubleDot => TokenOwned::DoubleDot,
Token::DoubleEqual => TokenOwned::DoubleEqual,
Token::DoublePipe => TokenOwned::DoublePipe,
Token::Else => TokenOwned::Else,
Token::Eof => TokenOwned::Eof,
Token::Equal => TokenOwned::Equal,
Token::Float(float) => TokenOwned::Float(float.to_string()),
Token::FloatKeyword => TokenOwned::FloatKeyword,
Token::Fn => TokenOwned::Fn,
Token::Greater => TokenOwned::Greater,
Token::GreaterEqual => TokenOwned::GreaterOrEqual,
Token::Identifier(text) => TokenOwned::Identifier(text.to_string()),
Token::If => TokenOwned::If,
Token::Int => TokenOwned::Int,
Token::Integer(integer) => TokenOwned::Integer(integer.to_string()),
Token::LeftCurlyBrace => TokenOwned::LeftCurlyBrace,
Token::LeftParenthesis => TokenOwned::LeftParenthesis,
Token::LeftSquareBrace => TokenOwned::LeftSquareBrace,
Token::Let => TokenOwned::Let,
Token::Less => TokenOwned::Less,
Token::LessEqual => TokenOwned::LessOrEqual,
Token::Loop => TokenOwned::Loop,
Token::Map => TokenOwned::Map,
Token::Minus => TokenOwned::Minus,
Token::MinusEqual => TokenOwned::MinusEqual,
Token::Mut => TokenOwned::Mut,
Token::Percent => TokenOwned::Percent,
Token::PercentEqual => TokenOwned::PercentEqual,
Token::Plus => TokenOwned::Plus,
Token::PlusEqual => TokenOwned::PlusEqual,
Token::Return => TokenOwned::Return,
Token::RightCurlyBrace => TokenOwned::RightCurlyBrace,
Token::RightParenthesis => TokenOwned::RightParenthesis,
Token::RightSquareBrace => TokenOwned::RightSquareBrace,
Token::Semicolon => TokenOwned::Semicolon,
Token::Star => TokenOwned::Star,
Token::StarEqual => TokenOwned::StarEqual,
Token::Slash => TokenOwned::Slash,
Token::SlashEqual => TokenOwned::SlashEqual,
Token::String(text) => TokenOwned::String(text.to_string()),
Token::Str => TokenOwned::Str,
Token::Struct => TokenOwned::Struct,
Token::While => TokenOwned::While,
}
}
pub fn kind(&self) -> TokenKind {
match self {
Token::ArrowThin => TokenKind::ArrowThin,
Token::Async => TokenKind::Async,
Token::BangEqual => TokenKind::BangEqual,
Token::Bang => TokenKind::Bang,
Token::Bool => TokenKind::Bool,
Token::Boolean(_) => TokenKind::Boolean,
Token::Break => TokenKind::Break,
Token::Byte(_) => TokenKind::Byte,
Token::Character(_) => TokenKind::Character,
Token::Colon => TokenKind::Colon,
Token::Comma => TokenKind::Comma,
Token::Dot => TokenKind::Dot,
Token::DoubleAmpersand => TokenKind::DoubleAmpersand,
Token::DoubleDot => TokenKind::DoubleDot,
Token::DoubleEqual => TokenKind::DoubleEqual,
Token::DoublePipe => TokenKind::DoublePipe,
Token::Else => TokenKind::Else,
Token::Eof => TokenKind::Eof,
Token::Equal => TokenKind::Equal,
Token::Float(_) => TokenKind::Float,
Token::FloatKeyword => TokenKind::FloatKeyword,
Token::Fn => TokenKind::Fn,
Token::Greater => TokenKind::Greater,
Token::GreaterEqual => TokenKind::GreaterEqual,
Token::Identifier(_) => TokenKind::Identifier,
Token::If => TokenKind::If,
Token::Int => TokenKind::Int,
Token::Integer(_) => TokenKind::Integer,
Token::LeftCurlyBrace => TokenKind::LeftCurlyBrace,
Token::LeftParenthesis => TokenKind::LeftParenthesis,
Token::LeftSquareBrace => TokenKind::LeftSquareBrace,
Token::Let => TokenKind::Let,
Token::Less => TokenKind::Less,
Token::LessEqual => TokenKind::LessEqual,
Token::Loop => TokenKind::Loop,
Token::Map => TokenKind::Map,
Token::Minus => TokenKind::Minus,
Token::MinusEqual => TokenKind::MinusEqual,
Token::Mut => TokenKind::Mut,
Token::Percent => TokenKind::Percent,
Token::PercentEqual => TokenKind::PercentEqual,
Token::Plus => TokenKind::Plus,
Token::PlusEqual => TokenKind::PlusEqual,
Token::Return => TokenKind::Return,
Token::RightCurlyBrace => TokenKind::RightCurlyBrace,
Token::RightParenthesis => TokenKind::RightParenthesis,
Token::RightSquareBrace => TokenKind::RightSquareBrace,
Token::Semicolon => TokenKind::Semicolon,
Token::Star => TokenKind::Star,
Token::StarEqual => TokenKind::StarEqual,
Token::Slash => TokenKind::Slash,
Token::SlashEqual => TokenKind::SlashEqual,
Token::Str => TokenKind::Str,
Token::String(_) => TokenKind::String,
Token::Struct => TokenKind::Struct,
Token::While => TokenKind::While,
}
}
/// Returns true if the token yields a value, begins an expression or is an expression operator.
pub fn is_expression(&self) -> bool {
matches!(
self,
Token::Boolean(_)
| Token::Byte(_)
| Token::Character(_)
| Token::Float(_)
| Token::Identifier(_)
| Token::Integer(_)
| Token::String(_)
| Token::Break
| Token::If
| Token::Return
| Token::Map
| Token::Loop
| Token::Struct
| Token::BangEqual
| Token::DoubleAmpersand
| Token::DoubleEqual
| Token::DoublePipe
| Token::Equal
| Token::Greater
| Token::GreaterEqual
| Token::LeftCurlyBrace
| Token::LeftParenthesis
| Token::LeftSquareBrace
| Token::Less
| Token::LessEqual
| Token::Minus
| Token::MinusEqual
| Token::Percent
| Token::PercentEqual
| Token::Plus
| Token::PlusEqual
| Token::Slash
| Token::SlashEqual
| Token::Star
| Token::StarEqual
)
}
}
impl<'src> Display for Token<'src> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Token::ArrowThin => write!(f, "->"),
Token::Async => write!(f, "async"),
Token::BangEqual => write!(f, "!="),
Token::Bang => write!(f, "!"),
Token::Bool => write!(f, "bool"),
Token::Boolean(value) => write!(f, "{value}"),
Token::Break => write!(f, "break"),
Token::Byte(value) => write!(f, "{value}"),
Token::Character(value) => write!(f, "{value}"),
Token::Colon => write!(f, ":"),
Token::Comma => write!(f, ","),
Token::Dot => write!(f, "."),
Token::DoubleAmpersand => write!(f, "&&"),
Token::DoubleDot => write!(f, ".."),
Token::DoubleEqual => write!(f, "=="),
Token::DoublePipe => write!(f, "||"),
Token::Else => write!(f, "else"),
Token::Eof => write!(f, "EOF"),
Token::Equal => write!(f, "="),
Token::Float(value) => write!(f, "{value}"),
Token::FloatKeyword => write!(f, "float"),
Token::Fn => write!(f, "fn"),
Token::Greater => write!(f, ">"),
Token::GreaterEqual => write!(f, ">="),
Token::Identifier(value) => write!(f, "{value}"),
Token::If => write!(f, "if"),
Token::Int => write!(f, "int"),
Token::Integer(value) => write!(f, "{value}"),
Token::LeftCurlyBrace => write!(f, "{{"),
Token::LeftParenthesis => write!(f, "("),
Token::LeftSquareBrace => write!(f, "["),
Token::Let => write!(f, "let"),
Token::Less => write!(f, "<"),
Token::LessEqual => write!(f, "<="),
Token::Loop => write!(f, "loop"),
Token::Map => write!(f, "map"),
Token::Minus => write!(f, "-"),
Token::MinusEqual => write!(f, "-="),
Token::Mut => write!(f, "mut"),
Token::Percent => write!(f, "%"),
Token::PercentEqual => write!(f, "%="),
Token::Plus => write!(f, "+"),
Token::PlusEqual => write!(f, "+="),
Token::Return => write!(f, "return"),
Token::RightCurlyBrace => write!(f, "}}"),
Token::RightParenthesis => write!(f, ")"),
Token::RightSquareBrace => write!(f, "]"),
Token::Semicolon => write!(f, ";"),
Token::Slash => write!(f, "/"),
Token::SlashEqual => write!(f, "/="),
Token::Star => write!(f, "*"),
Token::StarEqual => write!(f, "*="),
Token::Str => write!(f, "str"),
Token::String(value) => write!(f, "{value}"),
Token::Struct => write!(f, "struct"),
Token::While => write!(f, "while"),
}
}
}
/// Owned representation of a source token.
///
/// If a [Token] borrows from the source text, its TokenOwned omits the data.
#[derive(Debug, PartialEq, Clone)]
pub enum TokenOwned {
Eof,
Identifier(String),
// Hard-coded values
Boolean(String),
Byte(String),
Character(char),
Float(String),
Integer(String),
String(String),
// Keywords
Async,
Bool,
Break,
Else,
FloatKeyword,
Fn,
If,
Int,
Let,
Loop,
Map,
Mut,
Return,
Str,
While,
// Symbols
ArrowThin,
Bang,
BangEqual,
Colon,
Comma,
Dot,
DoubleAmpersand,
DoubleDot,
DoubleEqual,
DoublePipe,
Equal,
Greater,
GreaterOrEqual,
LeftCurlyBrace,
LeftParenthesis,
LeftSquareBrace,
Less,
LessOrEqual,
Minus,
MinusEqual,
Percent,
PercentEqual,
Plus,
PlusEqual,
RightCurlyBrace,
RightParenthesis,
RightSquareBrace,
Semicolon,
Star,
StarEqual,
Struct,
Slash,
SlashEqual,
}
impl Display for TokenOwned {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
TokenOwned::ArrowThin => Token::ArrowThin.fmt(f),
TokenOwned::Async => Token::Async.fmt(f),
TokenOwned::Bang => Token::Bang.fmt(f),
TokenOwned::BangEqual => Token::BangEqual.fmt(f),
TokenOwned::Bool => Token::Bool.fmt(f),
TokenOwned::Boolean(boolean) => Token::Boolean(boolean).fmt(f),
TokenOwned::Break => Token::Break.fmt(f),
TokenOwned::Byte(byte) => Token::Byte(byte).fmt(f),
TokenOwned::Character(character) => Token::Character(*character).fmt(f),
TokenOwned::Colon => Token::Colon.fmt(f),
TokenOwned::Comma => Token::Comma.fmt(f),
TokenOwned::Dot => Token::Dot.fmt(f),
TokenOwned::DoubleAmpersand => Token::DoubleAmpersand.fmt(f),
TokenOwned::DoubleDot => Token::DoubleDot.fmt(f),
TokenOwned::DoubleEqual => Token::DoubleEqual.fmt(f),
TokenOwned::DoublePipe => Token::DoublePipe.fmt(f),
TokenOwned::Else => Token::Else.fmt(f),
TokenOwned::Eof => Token::Eof.fmt(f),
TokenOwned::Equal => Token::Equal.fmt(f),
TokenOwned::Float(float) => Token::Float(float).fmt(f),
TokenOwned::FloatKeyword => Token::FloatKeyword.fmt(f),
TokenOwned::Fn => Token::Fn.fmt(f),
TokenOwned::Greater => Token::Greater.fmt(f),
TokenOwned::GreaterOrEqual => Token::GreaterEqual.fmt(f),
TokenOwned::Identifier(text) => Token::Identifier(text).fmt(f),
TokenOwned::If => Token::If.fmt(f),
TokenOwned::Int => Token::Int.fmt(f),
TokenOwned::Integer(integer) => Token::Integer(integer).fmt(f),
TokenOwned::LeftCurlyBrace => Token::LeftCurlyBrace.fmt(f),
TokenOwned::LeftParenthesis => Token::LeftParenthesis.fmt(f),
TokenOwned::LeftSquareBrace => Token::LeftSquareBrace.fmt(f),
TokenOwned::Let => Token::Let.fmt(f),
TokenOwned::Less => Token::Less.fmt(f),
TokenOwned::LessOrEqual => Token::LessEqual.fmt(f),
TokenOwned::Loop => Token::Loop.fmt(f),
TokenOwned::Map => Token::Map.fmt(f),
TokenOwned::Minus => Token::Minus.fmt(f),
TokenOwned::MinusEqual => Token::MinusEqual.fmt(f),
TokenOwned::Mut => Token::Mut.fmt(f),
TokenOwned::Percent => Token::Percent.fmt(f),
TokenOwned::PercentEqual => Token::PercentEqual.fmt(f),
TokenOwned::Plus => Token::Plus.fmt(f),
TokenOwned::PlusEqual => Token::PlusEqual.fmt(f),
TokenOwned::Return => Token::Return.fmt(f),
TokenOwned::RightCurlyBrace => Token::RightCurlyBrace.fmt(f),
TokenOwned::RightParenthesis => Token::RightParenthesis.fmt(f),
TokenOwned::RightSquareBrace => Token::RightSquareBrace.fmt(f),
TokenOwned::Semicolon => Token::Semicolon.fmt(f),
TokenOwned::Star => Token::Star.fmt(f),
TokenOwned::StarEqual => Token::StarEqual.fmt(f),
TokenOwned::Slash => Token::Slash.fmt(f),
TokenOwned::SlashEqual => Token::SlashEqual.fmt(f),
TokenOwned::Str => Token::Str.fmt(f),
TokenOwned::String(string) => Token::String(string).fmt(f),
TokenOwned::Struct => Token::Struct.fmt(f),
TokenOwned::While => Token::While.fmt(f),
}
}
}
impl Display for TokenKind {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
TokenKind::ArrowThin => Token::ArrowThin.fmt(f),
TokenKind::Async => Token::Async.fmt(f),
TokenKind::Bang => Token::Bang.fmt(f),
TokenKind::BangEqual => Token::BangEqual.fmt(f),
TokenKind::Bool => Token::Bool.fmt(f),
TokenKind::Boolean => write!(f, "boolean value"),
TokenKind::Break => Token::Break.fmt(f),
TokenKind::Byte => write!(f, "byte value"),
TokenKind::Character => write!(f, "character value"),
TokenKind::Colon => Token::Colon.fmt(f),
TokenKind::Comma => Token::Comma.fmt(f),
TokenKind::Dot => Token::Dot.fmt(f),
TokenKind::DoubleAmpersand => Token::DoubleAmpersand.fmt(f),
TokenKind::DoubleDot => Token::DoubleDot.fmt(f),
TokenKind::DoubleEqual => Token::DoubleEqual.fmt(f),
TokenKind::DoublePipe => Token::DoublePipe.fmt(f),
TokenKind::Else => Token::Else.fmt(f),
TokenKind::Eof => Token::Eof.fmt(f),
TokenKind::Equal => Token::Equal.fmt(f),
TokenKind::Float => write!(f, "float value"),
TokenKind::FloatKeyword => Token::FloatKeyword.fmt(f),
TokenKind::Fn => Token::Fn.fmt(f),
TokenKind::Greater => Token::Greater.fmt(f),
TokenKind::GreaterEqual => Token::GreaterEqual.fmt(f),
TokenKind::Identifier => write!(f, "identifier"),
TokenKind::If => Token::If.fmt(f),
TokenKind::Int => Token::Int.fmt(f),
TokenKind::Integer => write!(f, "integer value"),
TokenKind::LeftCurlyBrace => Token::LeftCurlyBrace.fmt(f),
TokenKind::LeftParenthesis => Token::LeftParenthesis.fmt(f),
TokenKind::LeftSquareBrace => Token::LeftSquareBrace.fmt(f),
TokenKind::Let => Token::Let.fmt(f),
TokenKind::Less => Token::Less.fmt(f),
TokenKind::LessEqual => Token::LessEqual.fmt(f),
TokenKind::Loop => Token::Loop.fmt(f),
TokenKind::Map => Token::Map.fmt(f),
TokenKind::Minus => Token::Minus.fmt(f),
TokenKind::MinusEqual => Token::MinusEqual.fmt(f),
TokenKind::Mut => Token::Mut.fmt(f),
TokenKind::Percent => Token::Percent.fmt(f),
TokenKind::PercentEqual => Token::PercentEqual.fmt(f),
TokenKind::Plus => Token::Plus.fmt(f),
TokenKind::PlusEqual => Token::PlusEqual.fmt(f),
TokenKind::Return => Token::Return.fmt(f),
TokenKind::RightCurlyBrace => Token::RightCurlyBrace.fmt(f),
TokenKind::RightParenthesis => Token::RightParenthesis.fmt(f),
TokenKind::RightSquareBrace => Token::RightSquareBrace.fmt(f),
TokenKind::Semicolon => Token::Semicolon.fmt(f),
TokenKind::Star => Token::Star.fmt(f),
TokenKind::StarEqual => Token::StarEqual.fmt(f),
TokenKind::Str => Token::Str.fmt(f),
TokenKind::Slash => Token::Slash.fmt(f),
TokenKind::SlashEqual => Token::SlashEqual.fmt(f),
TokenKind::String => write!(f, "string value"),
TokenKind::Struct => Token::Struct.fmt(f),
TokenKind::While => Token::While.fmt(f),
}
}
}

View File

@ -1,659 +0,0 @@
//! Value types and conflict handling.
use std::{
cmp::Ordering,
collections::HashMap,
fmt::{self, Display, Formatter},
};
use serde::{Deserialize, Serialize};
/// Description of a kind of value.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum Type {
Any,
Boolean,
Byte,
Character,
Enum(EnumType),
Float,
Function(FunctionType),
Generic {
identifier_index: u8,
concrete_type: Option<Box<Type>>,
},
Integer,
List {
item_type: Box<Type>,
length: usize,
},
ListEmpty,
ListOf {
item_type: Box<Type>,
},
Map {
pairs: HashMap<u8, Type>,
},
Number,
Range {
r#type: RangeableType,
},
String {
length: Option<usize>,
},
Struct(StructType),
Tuple {
fields: Option<Vec<Type>>,
},
}
impl Type {
/// 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 {
concrete_type: Some(concrete_type),
..
} = self
{
concrete_type.concrete_type()
} else {
self
}
}
/// 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::Byte, Type::Byte)
| (Type::Character, Type::Character)
| (Type::Float, Type::Float)
| (Type::Integer, Type::Integer)
| (Type::String { .. }, Type::String { .. }) => return Ok(()),
(
Type::Generic {
concrete_type: left,
..
},
Type::Generic {
concrete_type: right,
..
},
) => match (left, right) {
(Some(left), Some(right)) => {
if left.check(right).is_ok() {
return Ok(());
}
}
(None, None) => {
return Ok(());
}
_ => {}
},
(Type::Generic { concrete_type, .. }, other)
| (other, Type::Generic { concrete_type, .. }) => {
if let Some(concrete_type) = concrete_type {
if other == concrete_type.as_ref() {
return Ok(());
}
}
}
(Type::Struct(left_struct_type), Type::Struct(right_struct_type)) => {
if left_struct_type == right_struct_type {
return Ok(());
}
}
(
Type::List {
item_type: left_type,
length: left_length,
},
Type::List {
item_type: right_type,
length: right_length,
},
) => {
if left_length != right_length {
return Err(TypeConflict {
actual: other.clone(),
expected: self.clone(),
});
}
if left_type.check(right_type).is_err() {
return Err(TypeConflict {
actual: other.clone(),
expected: self.clone(),
});
}
return Ok(());
}
(
Type::ListOf {
item_type: left_type,
},
Type::ListOf {
item_type: right_type,
},
) => {
if left_type.check(right_type).is_err() {
return Err(TypeConflict {
actual: other.clone(),
expected: self.clone(),
});
}
}
(
Type::List {
item_type: list_item_type,
..
},
Type::ListOf {
item_type: list_of_item_type,
},
)
| (
Type::ListOf {
item_type: list_of_item_type,
},
Type::List {
item_type: list_item_type,
..
},
) => {
// TODO: This is a hack, remove it.
if let Type::Any = **list_of_item_type {
return Ok(());
}
if list_item_type.check(list_of_item_type).is_err() {
return Err(TypeConflict {
actual: other.clone(),
expected: self.clone(),
});
}
}
(
Type::Function(FunctionType {
type_parameters: left_type_parameters,
value_parameters: left_value_parameters,
return_type: left_return,
}),
Type::Function(FunctionType {
type_parameters: right_type_parameters,
value_parameters: right_value_parameters,
return_type: right_return,
}),
) => {
if left_return != right_return
|| left_type_parameters != right_type_parameters
|| left_value_parameters != right_value_parameters
{
return Err(TypeConflict {
actual: other.clone(),
expected: self.clone(),
});
}
return Ok(());
}
(Type::Range { r#type: left_type }, Type::Range { r#type: right_type }) => {
if left_type == right_type {
return Ok(());
}
}
(Type::Number, Type::Number | Type::Integer | Type::Float)
| (Type::Integer | Type::Float, Type::Number) => {
return Ok(());
}
_ => {}
}
Err(TypeConflict {
actual: other.clone(),
expected: self.clone(),
})
}
}
impl Display for Type {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Type::Any => write!(f, "any"),
Type::Boolean => write!(f, "bool"),
Type::Byte => write!(f, "byte"),
Type::Character => write!(f, "char"),
Type::Enum(EnumType { name, .. }) => write!(f, "{name}"),
Type::Float => write!(f, "float"),
Type::Function(function_type) => write!(f, "{function_type}"),
Type::Generic { concrete_type, .. } => {
match concrete_type.clone().map(|r#box| *r#box) {
Some(Type::Generic {
identifier_index: identifier,
..
}) => write!(f, "{identifier}"),
Some(concrete_type) => write!(f, "implied to be {concrete_type}"),
None => write!(f, "unknown"),
}
}
Type::Integer => write!(f, "int"),
Type::List { item_type, length } => write!(f, "[{item_type}; {length}]"),
Type::ListEmpty => write!(f, "[]"),
Type::ListOf { item_type } => write!(f, "[{item_type}]"),
Type::Map { pairs } => {
write!(f, "map ")?;
write!(f, "{{")?;
for (index, (key, value)) in pairs.iter().enumerate() {
write!(f, "{key}: {value}")?;
if index != pairs.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, "}}")
}
Type::Number => write!(f, "num"),
Type::Range { r#type } => write!(f, "{type} range"),
Type::String { .. } => write!(f, "str"),
Type::Struct(struct_type) => write!(f, "{struct_type}"),
Type::Tuple { fields } => {
if let Some(fields) = fields {
write!(f, "(")?;
for (index, r#type) in fields.iter().enumerate() {
write!(f, "{type}")?;
if index != fields.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, ")")
} else {
write!(f, "tuple")
}
}
}
}
}
impl PartialOrd for Type {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Type {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(Type::Any, Type::Any) => Ordering::Equal,
(Type::Any, _) => Ordering::Greater,
(Type::Boolean, Type::Boolean) => Ordering::Equal,
(Type::Boolean, _) => Ordering::Greater,
(Type::Byte, Type::Byte) => Ordering::Equal,
(Type::Byte, _) => Ordering::Greater,
(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::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::Generic { .. }, Type::Generic { .. }) => Ordering::Equal,
(Type::Generic { .. }, _) => Ordering::Greater,
(Type::Integer, Type::Integer) => Ordering::Equal,
(Type::Integer, _) => Ordering::Greater,
(
Type::List {
item_type: left_item_type,
length: left_length,
},
Type::List {
item_type: right_item_type,
length: right_length,
},
) => {
if left_length == right_length {
left_item_type.cmp(right_item_type)
} else {
left_length.cmp(right_length)
}
}
(Type::List { .. }, _) => Ordering::Greater,
(Type::ListEmpty, Type::ListEmpty) => Ordering::Equal,
(Type::ListEmpty, _) => Ordering::Greater,
(
Type::ListOf {
item_type: left_item_type,
},
Type::ListOf {
item_type: right_item_type,
},
) => left_item_type.cmp(right_item_type),
(Type::ListOf { .. }, _) => Ordering::Greater,
(Type::Map { pairs: left_pairs }, Type::Map { pairs: right_pairs }) => {
left_pairs.iter().cmp(right_pairs.iter())
}
(Type::Map { .. }, _) => Ordering::Greater,
(Type::Number, Type::Number) => Ordering::Equal,
(Type::Number, _) => Ordering::Greater,
(Type::Range { r#type: left_type }, Type::Range { r#type: right_type }) => {
left_type.cmp(right_type)
}
(Type::Range { .. }, _) => Ordering::Greater,
(Type::String { length: left }, Type::String { length: right }) => left.cmp(right),
(Type::String { .. }, _) => Ordering::Greater,
(Type::Struct(left_struct), Type::Struct(right_struct)) => {
left_struct.cmp(right_struct)
}
(Type::Struct(_), _) => Ordering::Greater,
(Type::Tuple { fields: left }, Type::Tuple { fields: right }) => left.cmp(right),
(Type::Tuple { .. }, _) => Ordering::Greater,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct FunctionType {
pub type_parameters: Option<Vec<u8>>,
pub value_parameters: Option<Vec<(u8, Type)>>,
pub return_type: Option<Box<Type>>,
}
impl Display for FunctionType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "fn ")?;
if let Some(type_parameters) = &self.type_parameters {
write!(f, "<")?;
for (index, type_parameter) in type_parameters.iter().enumerate() {
if index > 0 {
write!(f, ", ")?;
}
write!(f, "{type_parameter}")?;
}
write!(f, ">")?;
}
write!(f, "(")?;
if let Some(value_parameters) = &self.value_parameters {
for (index, (identifier, r#type)) in value_parameters.iter().enumerate() {
if index > 0 {
write!(f, ", ")?;
}
write!(f, "{identifier}: {type}")?;
}
}
write!(f, ")")?;
if let Some(return_type) = &self.return_type {
write!(f, " -> {return_type}")?;
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum StructType {
Unit { name: u8 },
Tuple { name: u8, fields: Vec<Type> },
Fields { name: u8, fields: HashMap<u8, Type> },
}
impl StructType {
pub fn name(&self) -> u8 {
match self {
StructType::Unit { name } => *name,
StructType::Tuple { name, .. } => *name,
StructType::Fields { name, .. } => *name,
}
}
}
impl Display for StructType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
StructType::Unit { name } => write!(f, "{name}"),
StructType::Tuple { name, fields } => {
write!(f, "{name}(")?;
for (index, field) in fields.iter().enumerate() {
write!(f, "{field}")?;
if index != fields.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, ")")
}
StructType::Fields { name, fields } => {
write!(f, "{name} {{")?;
for (index, (identifier, r#type)) in fields.iter().enumerate() {
write!(f, "{identifier}: {type}")?;
if index != fields.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, "}}")
}
}
}
}
impl PartialOrd for StructType {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for StructType {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(StructType::Unit { name: left_name }, StructType::Unit { name: right_name }) => {
left_name.cmp(right_name)
}
(StructType::Unit { .. }, _) => Ordering::Greater,
(
StructType::Tuple {
name: left_name,
fields: left_fields,
},
StructType::Tuple {
name: right_name,
fields: right_fields,
},
) => {
let name_cmp = left_name.cmp(right_name);
if name_cmp == Ordering::Equal {
left_fields.cmp(right_fields)
} else {
name_cmp
}
}
(StructType::Tuple { .. }, _) => Ordering::Greater,
(
StructType::Fields {
name: left_name,
fields: left_fields,
},
StructType::Fields {
name: right_name,
fields: right_fields,
},
) => {
let name_cmp = left_name.cmp(right_name);
if name_cmp == Ordering::Equal {
let len_cmp = left_fields.len().cmp(&right_fields.len());
if len_cmp == Ordering::Equal {
left_fields.iter().cmp(right_fields.iter())
} else {
len_cmp
}
} else {
name_cmp
}
}
(StructType::Fields { .. }, _) => Ordering::Greater,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct EnumType {
pub name: u8,
pub variants: Vec<StructType>,
}
impl Display for EnumType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let EnumType { name, variants } = self;
write!(f, "enum {name} {{ ")?;
for (index, variant) in variants.iter().enumerate() {
write!(f, "{variant}")?;
if index != self.variants.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, " }}")
}
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum RangeableType {
Byte,
Character,
Float,
Integer,
}
impl Display for RangeableType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
RangeableType::Byte => Type::Byte.fmt(f),
RangeableType::Character => Type::Character.fmt(f),
RangeableType::Float => Type::Float.fmt(f),
RangeableType::Integer => Type::Integer.fmt(f),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct TypeConflict {
pub expected: Type,
pub actual: Type,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_type_any() {
let foo = Type::Any;
let bar = Type::Any;
foo.check(&bar).unwrap();
}
#[test]
fn check_type_boolean() {
let foo = Type::Boolean;
let bar = Type::Boolean;
foo.check(&bar).unwrap();
}
#[test]
fn check_type_byte() {
let foo = Type::Byte;
let bar = Type::Byte;
foo.check(&bar).unwrap();
}
#[test]
fn check_type_character() {
let foo = Type::Character;
let bar = Type::Character;
foo.check(&bar).unwrap();
}
#[test]
fn errors() {
let foo = Type::Integer;
let bar = Type::String { length: None };
assert_eq!(
foo.check(&bar),
Err(TypeConflict {
actual: bar.clone(),
expected: foo.clone()
})
);
assert_eq!(
bar.check(&foo),
Err(TypeConflict {
actual: foo.clone(),
expected: bar.clone()
})
);
let types = [
Type::Boolean,
Type::Float,
Type::Integer,
Type::List {
item_type: Box::new(Type::Integer),
length: 42,
},
Type::Range {
r#type: RangeableType::Integer,
},
Type::String { length: None },
];
for left in types.clone() {
for right in types.clone() {
if left == right {
continue;
}
assert_eq!(
left.check(&right),
Err(TypeConflict {
actual: right.clone(),
expected: left.clone()
})
);
}
}
}
}

View File

@ -1,767 +0,0 @@
//! Dust value representation
//!
//! # Examples
//!
//! Each type of value has a corresponding method for instantiation:
//!
//! ```
//! # use dust_lang::Value;
//! let boolean = Value::boolean(true);
//! let float = Value::float(3.14);
//! let integer = Value::integer(42);
//! let string = Value::string("Hello, world!");
//! ```
//!
//! Values have a type, which can be retrieved using the `r#type` method:
//!
//! ```
//! # use dust_lang::*;
//! let value = Value::integer(42);
//!
//! assert_eq!(value.r#type(), Type::Integer);
//! ```
use std::{
cmp::Ordering,
fmt::{self, Debug, Display, Formatter},
ops::{Range, RangeInclusive},
};
use serde::{Deserialize, Serialize};
use crate::{Chunk, FunctionType, RangeableType, Type};
/// Dust value representation
///
/// See the [module-level documentation][self] for more.
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Value {
Concrete(ConcreteValue),
Abstract(AbstractValue),
}
impl Value {
pub fn boolean(value: bool) -> Self {
Value::Concrete(ConcreteValue::Boolean(value))
}
pub fn byte(value: u8) -> Self {
Value::Concrete(ConcreteValue::Byte(value))
}
pub fn character(value: char) -> Self {
Value::Concrete(ConcreteValue::Character(value))
}
pub fn float(value: f64) -> Self {
Value::Concrete(ConcreteValue::Float(value))
}
pub fn function(body: Chunk, r#type: FunctionType) -> Self {
Value::Concrete(ConcreteValue::Function(Function {
chunk: body,
r#type: Type::Function(r#type),
}))
}
pub fn integer<T: Into<i64>>(into_i64: T) -> Self {
Value::Concrete(ConcreteValue::Integer(into_i64.into()))
}
pub fn list<T: Into<Vec<Value>>>(items: T) -> Self {
Value::Concrete(ConcreteValue::List(items.into()))
}
pub fn abstract_list(start: u8, end: u8, item_type: Type) -> Self {
Value::Abstract(AbstractValue::List {
start,
end,
item_type,
})
}
pub fn string<T: ToString>(to_string: T) -> Self {
Value::Concrete(ConcreteValue::String(to_string.to_string()))
}
pub fn as_string(&self) -> Option<&String> {
if let Value::Concrete(ConcreteValue::String(string)) = self {
Some(string)
} else {
None
}
}
pub fn is_function(&self) -> bool {
matches!(self, Value::Concrete(ConcreteValue::Function(_)))
}
pub fn r#type(&self) -> Type {
match self {
Value::Concrete(data) => data.r#type(),
Value::Abstract(AbstractValue::List {
start,
end,
item_type,
}) => {
let length = (end - start + 1) as usize;
Type::List {
length,
item_type: Box::new(item_type.clone()),
}
}
}
}
pub fn add(&self, other: &Value) -> Result<Value, ValueError> {
use ConcreteValue::*;
use Value::*;
let sum = match (self, other) {
(Concrete(Byte(left)), Concrete(Byte(right))) => {
Value::byte(left.saturating_add(*right))
}
(Concrete(Float(left)), Concrete(Float(right))) => Value::float(left + right),
(Concrete(Integer(left)), Concrete(Integer(right))) => {
Value::integer(left.saturating_add(*right))
}
(Concrete(String(left)), Concrete(String(right))) => {
Value::string(format!("{}{}", left, right))
}
_ => return Err(ValueError::CannotAdd(self.clone(), other.clone())),
};
Ok(sum)
}
pub fn subtract(&self, other: &Value) -> Result<Value, ValueError> {
use ConcreteValue::*;
use Value::*;
let different = match (self, other) {
(Concrete(Byte(left)), Concrete(Byte(right))) => {
Value::byte(left.saturating_sub(*right))
}
(Concrete(Float(left)), Concrete(Float(right))) => Value::float(left - right),
(Concrete(Integer(left)), Concrete(Integer(right))) => {
Value::integer(left.saturating_sub(*right))
}
_ => return Err(ValueError::CannotSubtract(self.clone(), other.clone())),
};
Ok(different)
}
pub fn multiply(&self, other: &Value) -> Result<Value, ValueError> {
use ConcreteValue::*;
use Value::*;
let product = match (self, other) {
(Concrete(Byte(left)), Concrete(Byte(right))) => {
Value::byte(left.saturating_mul(*right))
}
(Concrete(Float(left)), Concrete(Float(right))) => Value::float(left * right),
(Concrete(Integer(left)), Concrete(Integer(right))) => {
Value::integer(left.saturating_mul(*right))
}
_ => return Err(ValueError::CannotAdd(self.clone(), other.clone())),
};
Ok(product)
}
pub fn divide(&self, other: &Value) -> Result<Value, ValueError> {
use ConcreteValue::*;
use Value::*;
let product = match (self, other) {
(Concrete(Byte(left)), Concrete(Byte(right))) => {
Value::byte(left.saturating_div(*right))
}
(Concrete(Float(left)), Concrete(Float(right))) => Value::float(left / right),
(Concrete(Integer(left)), Concrete(Integer(right))) => {
Value::integer(left.saturating_div(*right))
}
_ => return Err(ValueError::CannotDivide(self.clone(), other.clone())),
};
Ok(product)
}
pub fn modulo(&self, other: &Value) -> Result<Value, ValueError> {
use ConcreteValue::*;
use Value::*;
let product = match (self, other) {
(Concrete(Byte(left)), Concrete(Byte(right))) => Value::byte(left % right),
(Concrete(Float(left)), Concrete(Float(right))) => Value::float(left % right),
(Concrete(Integer(left)), Concrete(Integer(right))) => Value::integer(left % right),
_ => return Err(ValueError::CannotModulo(self.clone(), other.clone())),
};
Ok(product)
}
pub fn less_than(&self, other: &Value) -> Result<Value, ValueError> {
let (left, right) = match (self, other) {
(Value::Concrete(left), Value::Concrete(right)) => (left, right),
_ => return Err(ValueError::CannotCompare(self.clone(), other.clone())),
};
Ok(Value::boolean(left < right))
}
pub fn less_than_or_equal(&self, other: &Value) -> Result<Value, ValueError> {
let (left, right) = match (self, other) {
(Value::Concrete(left), Value::Concrete(right)) => (left, right),
_ => return Err(ValueError::CannotCompare(self.clone(), other.clone())),
};
Ok(Value::boolean(left <= right))
}
pub fn equal(&self, other: &Value) -> Result<Value, ValueError> {
let (left, right) = match (self, other) {
(Value::Concrete(left), Value::Concrete(right)) => (left, right),
_ => return Err(ValueError::CannotCompare(self.clone(), other.clone())),
};
Ok(Value::boolean(left == right))
}
pub fn negate(&self) -> Result<Value, ValueError> {
use ConcreteValue::*;
use Value::*;
let negated = match self {
Concrete(Integer(integer)) => Value::integer(-integer),
Concrete(Float(float)) => Value::float(-float),
_ => return Err(ValueError::CannotNot(self.clone())),
};
Ok(negated)
}
pub fn not(&self) -> Result<Value, ValueError> {
use ConcreteValue::*;
use Value::*;
let not = match self {
Concrete(Boolean(boolean)) => Value::boolean(!boolean),
Concrete(Byte(byte)) => Value::byte(!byte),
Concrete(Integer(integer)) => Value::integer(!integer),
_ => return Err(ValueError::CannotNot(self.clone())),
};
Ok(not)
}
}
impl From<bool> for Value {
fn from(value: bool) -> Self {
Value::boolean(value)
}
}
impl From<u8> for Value {
fn from(value: u8) -> Self {
Value::byte(value)
}
}
impl From<char> for Value {
fn from(value: char) -> Self {
Value::character(value)
}
}
impl From<f64> for Value {
fn from(value: f64) -> Self {
Value::float(value)
}
}
impl From<i32> for Value {
fn from(value: i32) -> Self {
Value::integer(value as i64)
}
}
impl From<i64> for Value {
fn from(value: i64) -> Self {
Value::integer(value)
}
}
impl From<String> for Value {
fn from(value: String) -> Self {
Value::string(value)
}
}
impl From<&str> for Value {
fn from(str: &str) -> Self {
Value::string(str)
}
}
impl Clone for Value {
fn clone(&self) -> Self {
log::trace!("Cloning value {self}");
match self {
Value::Abstract(object) => Value::Abstract(object.clone()),
Value::Concrete(concrete) => Value::Concrete(concrete.clone()),
}
}
}
impl Display for Value {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Value::Abstract(object) => write!(f, "{object}"),
Value::Concrete(concrete) => write!(f, "{concrete}"),
}
}
}
/// Value representation that can be resolved to a concrete value by the VM.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum AbstractValue {
List { start: u8, end: u8, item_type: Type },
}
impl Display for AbstractValue {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
AbstractValue::List { start, end, .. } => {
write!(f, "List [R{}..=R{}]", start, end)
}
}
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum ConcreteValue {
Boolean(bool),
Byte(u8),
Character(char),
Float(f64),
Function(Function),
Integer(i64),
List(Vec<Value>),
Range(RangeValue),
String(String),
}
impl ConcreteValue {
pub fn r#type(&self) -> Type {
match self {
ConcreteValue::Boolean(_) => Type::Boolean,
ConcreteValue::Byte(_) => Type::Byte,
ConcreteValue::Character(_) => Type::Character,
ConcreteValue::Float(_) => Type::Float,
ConcreteValue::Function(Function { r#type, .. }) => r#type.clone(),
ConcreteValue::Integer(_) => Type::Integer,
ConcreteValue::List(list) => Type::List {
item_type: list
.first()
.map(|value| Box::new(value.r#type()))
.unwrap_or_else(|| Box::new(Type::Any)),
length: list.len(),
},
ConcreteValue::Range(range) => range.r#type(),
ConcreteValue::String(string) => Type::String {
length: Some(string.len()),
},
}
}
pub fn is_rangeable(&self) -> bool {
matches!(
self,
ConcreteValue::Integer(_)
| ConcreteValue::Float(_)
| ConcreteValue::Character(_)
| ConcreteValue::Byte(_)
)
}
}
impl Display for ConcreteValue {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
ConcreteValue::Boolean(boolean) => write!(f, "{boolean}"),
ConcreteValue::Byte(byte) => write!(f, "0x{byte:02x}"),
ConcreteValue::Character(character) => write!(f, "{character}"),
ConcreteValue::Float(float) => {
write!(f, "{float}")?;
if float.fract() == 0.0 {
write!(f, ".0")?;
}
Ok(())
}
ConcreteValue::Function(Function { r#type, .. }) => {
write!(f, "{}", r#type)
}
ConcreteValue::Integer(integer) => write!(f, "{integer}"),
ConcreteValue::List(items) => {
write!(f, "[")?;
for (index, item) in items.iter().enumerate() {
if index > 0 {
write!(f, ", ")?;
}
write!(f, "{item}")?;
}
write!(f, "]")
}
ConcreteValue::Range(range_value) => {
write!(f, "{range_value}")
}
ConcreteValue::String(string) => write!(f, "{string}"),
}
}
}
impl Eq for ConcreteValue {}
impl PartialOrd for ConcreteValue {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ConcreteValue {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(ConcreteValue::Boolean(left), ConcreteValue::Boolean(right)) => left.cmp(right),
(ConcreteValue::Boolean(_), _) => Ordering::Greater,
(ConcreteValue::Byte(left), ConcreteValue::Byte(right)) => left.cmp(right),
(ConcreteValue::Byte(_), _) => Ordering::Greater,
(ConcreteValue::Character(left), ConcreteValue::Character(right)) => left.cmp(right),
(ConcreteValue::Character(_), _) => Ordering::Greater,
(ConcreteValue::Float(left), ConcreteValue::Float(right)) => {
if left.is_nan() && right.is_nan() {
Ordering::Equal
} else if left.is_nan() {
Ordering::Less
} else if right.is_nan() {
Ordering::Greater
} else {
left.partial_cmp(right).unwrap()
}
}
(ConcreteValue::Float(_), _) => Ordering::Greater,
(ConcreteValue::Function(left), ConcreteValue::Function(right)) => left.cmp(right),
(ConcreteValue::Function(_), _) => Ordering::Greater,
(ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => left.cmp(right),
(ConcreteValue::Integer(_), _) => Ordering::Greater,
(ConcreteValue::List(left), ConcreteValue::List(right)) => left.cmp(right),
(ConcreteValue::List(_), _) => Ordering::Greater,
(ConcreteValue::Range(left), ConcreteValue::Range(right)) => left.cmp(right),
(ConcreteValue::Range(_), _) => Ordering::Greater,
(ConcreteValue::String(left), ConcreteValue::String(right)) => left.cmp(right),
(ConcreteValue::String(_), _) => Ordering::Greater,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Function {
chunk: Chunk,
r#type: Type,
}
impl Function {
pub fn new(chunk: Chunk, r#type: Type) -> Self {
Self { chunk, r#type }
}
pub fn chunk(&self) -> &Chunk {
&self.chunk
}
pub fn chunk_mut(&mut self) -> &mut Chunk {
&mut self.chunk
}
pub fn take_chunk(self) -> Chunk {
self.chunk
}
pub fn r#type(&self) -> &Type {
&self.r#type
}
}
impl Display for Function {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.r#type)
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum RangeValue {
ByteRange(Range<u8>),
ByteRangeInclusive(RangeInclusive<u8>),
CharacterRange(Range<char>),
CharacterRangeInclusive(RangeInclusive<char>),
FloatRange(Range<f64>),
FloatRangeInclusive(RangeInclusive<f64>),
IntegerRange(Range<i64>),
IntegerRangeInclusive(RangeInclusive<i64>),
}
impl RangeValue {
pub fn r#type(&self) -> Type {
let inner_type = match self {
RangeValue::ByteRange(_) => RangeableType::Byte,
RangeValue::ByteRangeInclusive(_) => RangeableType::Byte,
RangeValue::CharacterRange(_) => RangeableType::Character,
RangeValue::CharacterRangeInclusive(_) => RangeableType::Character,
RangeValue::FloatRange(_) => RangeableType::Float,
RangeValue::FloatRangeInclusive(_) => RangeableType::Float,
RangeValue::IntegerRange(_) => RangeableType::Integer,
RangeValue::IntegerRangeInclusive(_) => RangeableType::Integer,
};
Type::Range { r#type: inner_type }
}
}
impl From<Range<u8>> for RangeValue {
fn from(range: Range<u8>) -> Self {
RangeValue::ByteRange(range)
}
}
impl From<RangeInclusive<u8>> for RangeValue {
fn from(range: RangeInclusive<u8>) -> Self {
RangeValue::ByteRangeInclusive(range)
}
}
impl From<Range<char>> for RangeValue {
fn from(range: Range<char>) -> Self {
RangeValue::CharacterRange(range)
}
}
impl From<RangeInclusive<char>> for RangeValue {
fn from(range: RangeInclusive<char>) -> Self {
RangeValue::CharacterRangeInclusive(range)
}
}
impl From<Range<f64>> for RangeValue {
fn from(range: Range<f64>) -> Self {
RangeValue::FloatRange(range)
}
}
impl From<RangeInclusive<f64>> for RangeValue {
fn from(range: RangeInclusive<f64>) -> Self {
RangeValue::FloatRangeInclusive(range)
}
}
impl From<Range<i32>> for RangeValue {
fn from(range: Range<i32>) -> Self {
RangeValue::IntegerRange(range.start as i64..range.end as i64)
}
}
impl From<RangeInclusive<i32>> for RangeValue {
fn from(range: RangeInclusive<i32>) -> Self {
RangeValue::IntegerRangeInclusive(*range.start() as i64..=*range.end() as i64)
}
}
impl From<Range<i64>> for RangeValue {
fn from(range: Range<i64>) -> Self {
RangeValue::IntegerRange(range)
}
}
impl From<RangeInclusive<i64>> for RangeValue {
fn from(range: RangeInclusive<i64>) -> Self {
RangeValue::IntegerRangeInclusive(range)
}
}
impl Display for RangeValue {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
RangeValue::ByteRange(range) => write!(f, "{}..{}", range.start, range.end),
RangeValue::ByteRangeInclusive(range) => {
write!(f, "{}..={}", range.start(), range.end())
}
RangeValue::CharacterRange(range) => write!(f, "{}..{}", range.start, range.end),
RangeValue::CharacterRangeInclusive(range) => {
write!(f, "{}..={}", range.start(), range.end())
}
RangeValue::FloatRange(range) => write!(f, "{}..{}", range.start, range.end),
RangeValue::FloatRangeInclusive(range) => {
write!(f, "{}..={}", range.start(), range.end())
}
RangeValue::IntegerRange(range) => write!(f, "{}..{}", range.start, range.end),
RangeValue::IntegerRangeInclusive(range) => {
write!(f, "{}..={}", range.start(), range.end())
}
}
}
}
impl Eq for RangeValue {}
impl PartialOrd for RangeValue {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for RangeValue {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(RangeValue::ByteRange(left), RangeValue::ByteRange(right)) => {
let start_cmp = left.start.cmp(&right.start);
if start_cmp != Ordering::Equal {
start_cmp
} else {
left.end.cmp(&right.end)
}
}
(RangeValue::ByteRange(_), _) => Ordering::Greater,
(RangeValue::ByteRangeInclusive(left), RangeValue::ByteRangeInclusive(right)) => {
let start_cmp = left.start().cmp(right.start());
if start_cmp != Ordering::Equal {
start_cmp
} else {
left.end().cmp(right.end())
}
}
(RangeValue::ByteRangeInclusive(_), _) => Ordering::Greater,
(RangeValue::CharacterRange(left), RangeValue::CharacterRange(right)) => {
let start_cmp = left.start.cmp(&right.start);
if start_cmp != Ordering::Equal {
start_cmp
} else {
left.end.cmp(&right.end)
}
}
(RangeValue::CharacterRange(_), _) => Ordering::Greater,
(
RangeValue::CharacterRangeInclusive(left),
RangeValue::CharacterRangeInclusive(right),
) => {
let start_cmp = left.start().cmp(right.start());
if start_cmp != Ordering::Equal {
start_cmp
} else {
left.end().cmp(right.end())
}
}
(RangeValue::CharacterRangeInclusive(_), _) => Ordering::Greater,
(RangeValue::FloatRange(left), RangeValue::FloatRange(right)) => {
let start_cmp = left.start.to_bits().cmp(&right.start.to_bits());
if start_cmp != Ordering::Equal {
start_cmp
} else {
left.end.to_bits().cmp(&right.end.to_bits())
}
}
(RangeValue::FloatRange(_), _) => Ordering::Greater,
(RangeValue::FloatRangeInclusive(left), RangeValue::FloatRangeInclusive(right)) => {
let start_cmp = left.start().to_bits().cmp(&right.start().to_bits());
if start_cmp != Ordering::Equal {
start_cmp
} else {
left.end().to_bits().cmp(&right.end().to_bits())
}
}
(RangeValue::FloatRangeInclusive(_), _) => Ordering::Greater,
(RangeValue::IntegerRange(left), RangeValue::IntegerRange(right)) => {
let start_cmp = left.start.cmp(&right.start);
if start_cmp != Ordering::Equal {
start_cmp
} else {
left.end.cmp(&right.end)
}
}
(RangeValue::IntegerRange(_), _) => Ordering::Greater,
(RangeValue::IntegerRangeInclusive(left), RangeValue::IntegerRangeInclusive(right)) => {
let start_cmp = left.start().cmp(right.start());
if start_cmp != Ordering::Equal {
start_cmp
} else {
left.end().cmp(right.end())
}
}
(RangeValue::IntegerRangeInclusive(_), _) => Ordering::Greater,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum ValueError {
CannotAdd(Value, Value),
CannotAnd(Value, Value),
CannotCompare(Value, Value),
CannotDivide(Value, Value),
CannotModulo(Value, Value),
CannotMultiply(Value, Value),
CannotNegate(Value),
CannotNot(Value),
CannotSubtract(Value, Value),
CannotOr(Value, Value),
}
impl Display for ValueError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
ValueError::CannotAdd(left, right) => {
write!(f, "Cannot add {left} and {right}")
}
ValueError::CannotAnd(left, right) => {
write!(f, "Cannot use logical AND operation on {left} and {right}")
}
ValueError::CannotCompare(left, right) => {
write!(f, "Cannot compare {left} and {right}")
}
ValueError::CannotDivide(left, right) => {
write!(f, "Cannot divide {left} by {right}")
}
ValueError::CannotModulo(left, right) => {
write!(f, "Cannot use modulo operation on {left} and {right}")
}
ValueError::CannotMultiply(left, right) => {
write!(f, "Cannot multiply {left} by {right}")
}
ValueError::CannotNegate(value) => {
write!(f, "Cannot negate {value}")
}
ValueError::CannotNot(value) => {
write!(f, "Cannot use logical NOT operation on {value}")
}
ValueError::CannotSubtract(left, right) => {
write!(f, "Cannot subtract {right} from {left}")
}
ValueError::CannotOr(left, right) => {
write!(f, "Cannot use logical OR operation on {left} and {right}")
}
}
}
}

View File

@ -1,765 +0,0 @@
//! Virtual machine and errors
use std::{
cmp::Ordering,
fmt::{self, Display, Formatter},
ops::Range,
};
use crate::{
compile, value::ConcreteValue, AnnotatedError, Chunk, ChunkError, DustError, FunctionType,
Instruction, Local, NativeFunction, NativeFunctionError, Operation, Span, Type, Value,
ValueError,
};
pub fn run(source: &str) -> Result<Option<Value>, DustError> {
let mut chunk = compile(source)?;
let mut vm = Vm::new(&mut chunk, None);
vm.run()
.map(|option| option.cloned())
.map_err(|error| DustError::Runtime { error, source })
}
pub fn run_and_display_output(source: &str) {
match run(source) {
Ok(Some(value)) => println!("{}", value),
Ok(None) => {}
Err(error) => eprintln!("{}", error.report()),
}
}
/// Dust virtual machine.
///
/// See the [module-level documentation](index.html) for more information.
#[derive(Debug, Eq, PartialEq)]
pub struct Vm<'chunk, 'parent> {
ip: usize,
chunk: &'chunk mut Chunk,
stack: Vec<Register>,
last_assigned_register: Option<u8>,
parent: Option<&'parent Vm<'chunk, 'parent>>,
}
impl<'chunk, 'parent> Vm<'chunk, 'parent> {
const STACK_LIMIT: usize = u16::MAX as usize;
pub fn new(chunk: &'chunk mut Chunk, parent: Option<&'parent Vm<'chunk, 'parent>>) -> Self {
Self {
ip: 0,
chunk,
stack: Vec::new(),
last_assigned_register: None,
parent,
}
}
pub fn run(&mut self) -> Result<Option<&Value>, VmError> {
// DRY helper to get constant or register values for binary operations
fn get_arguments<'a>(
vm: &'a mut Vm,
instruction: Instruction,
position: Span,
) -> Result<(&'a Value, &'a Value), VmError> {
let left = if instruction.b_is_constant() {
vm.get_constant(instruction.b(), position)?
} else {
vm.open_register(instruction.b(), position)?
};
let right = if instruction.c_is_constant() {
vm.get_constant(instruction.c(), position)?
} else {
vm.open_register(instruction.c(), position)?
};
Ok((left, right))
}
while let Ok((instruction, position)) = self.read(Span(0, 0)).copied() {
log::info!(
"{} | {} | {} | {}",
self.ip - 1,
position,
instruction.operation(),
instruction.disassembly_info(self.chunk)
);
match instruction.operation() {
Operation::Move => {
let to_register = instruction.a();
let from_register = instruction.b();
let from_register_has_value = self
.stack
.get(from_register as usize)
.is_some_and(|register| !matches!(register, Register::Empty));
if from_register_has_value {
self.set_register(
to_register,
Register::StackPointer(from_register),
position,
)?;
}
}
Operation::Close => {
let from_register = instruction.b();
let to_register = instruction.c();
if self.stack.len() < to_register as usize {
return Err(VmError::StackUnderflow { position });
}
for register_index in from_register..to_register {
self.stack[register_index as usize] = Register::Empty;
}
}
Operation::LoadBoolean => {
let to_register = instruction.a();
let boolean = instruction.b_as_boolean();
let jump = instruction.c_as_boolean();
let value = Value::boolean(boolean);
self.set_register(to_register, Register::Value(value), position)?;
if jump {
self.ip += 1;
}
}
Operation::LoadConstant => {
let to_register = instruction.a();
let from_constant = instruction.b();
let jump = instruction.c_as_boolean();
self.set_register(
to_register,
Register::ConstantPointer(from_constant),
position,
)?;
if jump {
self.ip += 1
}
}
Operation::LoadList => {
let to_register = instruction.a();
let start_register = instruction.b();
let item_type = (start_register..to_register)
.find_map(|register_index| {
if let Ok(value) = self.open_register(register_index, position) {
Some(value.r#type())
} else {
None
}
})
.unwrap_or(Type::Any);
let value = Value::abstract_list(start_register, to_register, item_type);
self.set_register(to_register, Register::Value(value), position)?;
}
Operation::LoadSelf => {
let to_register = instruction.a();
let value = Value::function(
self.chunk.clone(),
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: None,
},
);
self.set_register(to_register, Register::Value(value), position)?;
}
Operation::DefineLocal => {
let from_register = instruction.a();
let to_local = instruction.b();
self.define_local(to_local, from_register, position)?;
}
Operation::GetLocal => {
let to_register = instruction.a();
let local_index = instruction.b();
let local = self.get_local(local_index, position)?;
self.set_register(
to_register,
Register::StackPointer(local.register_index),
position,
)?;
}
Operation::SetLocal => {
let register = instruction.a();
let local_index = instruction.b();
self.define_local(local_index, register, position)?;
}
Operation::Add => {
let to_register = instruction.a();
let (left, right) = get_arguments(self, instruction, position)?;
let sum = left
.add(right)
.map_err(|error| VmError::Value { error, position })?;
self.set_register(to_register, Register::Value(sum), position)?;
}
Operation::Subtract => {
let to_register = instruction.a();
let (left, right) = get_arguments(self, instruction, position)?;
let difference = left
.subtract(right)
.map_err(|error| VmError::Value { error, position })?;
self.set_register(to_register, Register::Value(difference), position)?;
}
Operation::Multiply => {
let to_register = instruction.a();
let (left, right) = get_arguments(self, instruction, position)?;
let product = left
.multiply(right)
.map_err(|error| VmError::Value { error, position })?;
self.set_register(to_register, Register::Value(product), position)?;
}
Operation::Divide => {
let to_register = instruction.a();
let (left, right) = get_arguments(self, instruction, position)?;
let quotient = left
.divide(right)
.map_err(|error| VmError::Value { error, position })?;
self.set_register(to_register, Register::Value(quotient), position)?;
}
Operation::Modulo => {
let to_register = instruction.a();
let (left, right) = get_arguments(self, instruction, position)?;
let remainder = left
.modulo(right)
.map_err(|error| VmError::Value { error, position })?;
self.set_register(to_register, Register::Value(remainder), position)?;
}
Operation::Test => {
let register = instruction.a();
let test_value = instruction.c_as_boolean();
let value = self.open_register(register, position)?;
let boolean = if let Value::Concrete(ConcreteValue::Boolean(boolean)) = value {
*boolean
} else {
return Err(VmError::ExpectedBoolean {
found: value.clone(),
position,
});
};
if boolean != test_value {
self.ip += 1;
}
}
Operation::TestSet => todo!(),
Operation::Equal => {
debug_assert_eq!(
self.get_instruction(self.ip, position)?.0.operation(),
Operation::Jump
);
let (left, right) = get_arguments(self, instruction, position)?;
let equal_result = left
.equal(right)
.map_err(|error| VmError::Value { error, position })?;
let boolean =
if let Value::Concrete(ConcreteValue::Boolean(boolean)) = equal_result {
boolean
} else {
return Err(VmError::ExpectedBoolean {
found: equal_result.clone(),
position,
});
};
let compare_to = instruction.a_as_boolean();
if boolean == compare_to {
self.ip += 1;
} else {
let (jump, _) = self.get_instruction(self.ip, position)?;
let jump_distance = jump.a();
let is_positive = jump.b_as_boolean();
let new_ip = if is_positive {
self.ip + jump_distance as usize
} else {
self.ip - jump_distance as usize
};
self.ip = new_ip;
}
}
Operation::Less => {
debug_assert_eq!(
self.get_instruction(self.ip, position)?.0.operation(),
Operation::Jump
);
let (left, right) = get_arguments(self, instruction, position)?;
let less_result = left
.less_than(right)
.map_err(|error| VmError::Value { error, position })?;
let boolean =
if let Value::Concrete(ConcreteValue::Boolean(boolean)) = less_result {
boolean
} else {
return Err(VmError::ExpectedBoolean {
found: less_result.clone(),
position,
});
};
let compare_to = instruction.a_as_boolean();
if boolean == compare_to {
self.ip += 1;
} else {
let jump = self.get_instruction(self.ip, position)?.0;
let jump_distance = jump.a();
let is_positive = jump.b_as_boolean();
let new_ip = if is_positive {
self.ip + jump_distance as usize
} else {
self.ip - jump_distance as usize
};
self.ip = new_ip;
}
}
Operation::LessEqual => {
debug_assert_eq!(
self.get_instruction(self.ip, position)?.0.operation(),
Operation::Jump
);
let (left, right) = get_arguments(self, instruction, position)?;
let less_or_equal_result = left
.less_than_or_equal(right)
.map_err(|error| VmError::Value { error, position })?;
let boolean = if let Value::Concrete(ConcreteValue::Boolean(boolean)) =
less_or_equal_result
{
boolean
} else {
return Err(VmError::ExpectedBoolean {
found: less_or_equal_result.clone(),
position,
});
};
let compare_to = instruction.a_as_boolean();
if boolean == compare_to {
self.ip += 1;
} else {
let jump = self.get_instruction(self.ip, position)?.0;
let jump_distance = jump.a();
let is_positive = jump.b_as_boolean();
let new_ip = if is_positive {
self.ip + jump_distance as usize
} else {
self.ip - jump_distance as usize
};
self.ip = new_ip;
}
}
Operation::Negate => {
let value = if instruction.b_is_constant() {
self.get_constant(instruction.b(), position)?
} else {
self.open_register(instruction.b(), position)?
};
let negated = value
.negate()
.map_err(|error| VmError::Value { error, position })?;
self.set_register(instruction.a(), Register::Value(negated), position)?;
}
Operation::Not => {
let value = if instruction.b_is_constant() {
self.get_constant(instruction.b(), position)?
} else {
self.open_register(instruction.b(), position)?
};
let not = value
.not()
.map_err(|error| VmError::Value { error, position })?;
self.set_register(instruction.a(), Register::Value(not), position)?;
}
Operation::Jump => {
let jump_distance = instruction.b();
let is_positive = instruction.c_as_boolean();
let new_ip = if is_positive {
self.ip + jump_distance as usize
} else {
self.ip - jump_distance as usize - 1
};
self.ip = new_ip;
}
Operation::Call => {
let to_register = instruction.a();
let function_register = instruction.b();
let argument_count = instruction.c();
let value = self.open_register(function_register, position)?.clone();
let mut function =
if let Value::Concrete(ConcreteValue::Function(function)) = value {
function
} else {
return Err(VmError::ExpectedFunction {
found: value,
position,
});
};
let mut function_vm = Vm::new(function.chunk_mut(), Some(self));
let first_argument_index = function_register + 1;
for argument_index in
first_argument_index..first_argument_index + argument_count
{
let top_of_stack = function_vm.stack.len() as u8;
function_vm.set_register(
top_of_stack,
Register::ParentStackPointer(argument_index),
position,
)?
}
let return_value = function_vm.run()?.cloned();
if let Some(value) = return_value {
self.set_register(to_register, Register::Value(value), position)?;
}
}
Operation::CallNative => {
let native_function = NativeFunction::from(instruction.b());
let return_value = native_function.call(instruction, self, position)?;
if let Some(value) = return_value {
let to_register = instruction.a();
self.set_register(to_register, Register::Value(value), position)?;
}
}
Operation::Return => {
let should_return_value = instruction.b_as_boolean();
if !should_return_value {
return Ok(None);
}
let return_value = if let Some(register_index) = self.last_assigned_register {
self.open_register(register_index, position)?
} else {
return Err(VmError::StackUnderflow { position });
};
return Ok(Some(return_value));
}
}
}
Ok(None)
}
fn set_register(
&mut self,
to_register: u8,
register: Register,
position: Span,
) -> Result<(), VmError> {
self.last_assigned_register = Some(to_register);
let length = self.stack.len();
let to_register = to_register as usize;
if length == Self::STACK_LIMIT {
return Err(VmError::StackOverflow { position });
}
match to_register.cmp(&length) {
Ordering::Less => {
log::trace!("Change R{to_register} to {register}");
self.stack[to_register] = register;
Ok(())
}
Ordering::Equal => {
log::trace!("Set R{to_register} to {register}");
self.stack.push(register);
Ok(())
}
Ordering::Greater => {
let difference = to_register - length;
for index in 0..difference {
log::trace!("Set R{index} to {register}");
self.stack.push(Register::Empty);
}
log::trace!("Set R{to_register} to {register}");
self.stack.push(register);
Ok(())
}
}
}
fn get_constant(&self, index: u8, position: Span) -> Result<&Value, VmError> {
self.chunk
.get_constant(index)
.map_err(|error| VmError::Chunk { error, position })
}
pub fn open_register(&self, register_index: u8, position: Span) -> Result<&Value, VmError> {
let register_index = register_index as usize;
let register =
self.stack
.get(register_index)
.ok_or_else(|| VmError::RegisterIndexOutOfBounds {
index: register_index,
position,
})?;
match register {
Register::Value(value) => Ok(value),
Register::StackPointer(register_index) => self.open_register(*register_index, position),
Register::ConstantPointer(constant_index) => {
self.get_constant(*constant_index, position)
}
Register::ParentStackPointer(register_index) => {
let parent = self
.parent
.as_ref()
.ok_or(VmError::ExpectedParent { position })?;
parent.open_register(*register_index, position)
}
Register::ParentConstantPointer(constant_index) => {
let parent = self
.parent
.as_ref()
.ok_or(VmError::ExpectedParent { position })?;
parent.get_constant(*constant_index, position)
}
Register::Empty => Err(VmError::EmptyRegister {
index: register_index,
position,
}),
}
}
pub fn open_nonempty_registers(
&self,
register_index_range: Range<u8>,
position: Span,
) -> Result<Vec<&Value>, VmError> {
let mut values = Vec::with_capacity(register_index_range.len());
for register_index in register_index_range.clone() {
let register_index = register_index as usize;
let register = self.stack.get(register_index).ok_or_else(|| {
VmError::RegisterIndexOutOfBounds {
index: register_index,
position,
}
})?;
let value = match register {
Register::Value(value) => value,
Register::StackPointer(register_index) => {
self.open_register(*register_index, position)?
}
Register::ConstantPointer(constant_index) => {
self.get_constant(*constant_index, position)?
}
Register::ParentStackPointer(register_index) => {
let parent = self
.parent
.as_ref()
.ok_or(VmError::ExpectedParent { position })?;
parent.open_register(*register_index, position)?
}
Register::ParentConstantPointer(constant_index) => {
let parent = self
.parent
.as_ref()
.ok_or(VmError::ExpectedParent { position })?;
parent.get_constant(*constant_index, position)?
}
Register::Empty => continue,
};
values.push(value);
}
if values.is_empty() {
Err(VmError::EmptyRegisters {
indexes: register_index_range,
position,
})
} else {
Ok(values)
}
}
fn read(&mut self, position: Span) -> Result<&(Instruction, Span), VmError> {
self.chunk
.expect_not_poisoned()
.map_err(|error| VmError::Chunk { error, position })?;
let max_ip = self.chunk.len() - 1;
if self.ip > max_ip {
return self.get_instruction(max_ip, position);
} else {
self.ip += 1;
}
self.get_instruction(self.ip - 1, position)
}
fn define_local(
&mut self,
local_index: u8,
register_index: u8,
position: Span,
) -> Result<(), VmError> {
let local = self
.chunk
.get_local_mut(local_index)
.map_err(|error| VmError::Chunk { error, position })?;
log::debug!("Define local L{}", local_index);
local.register_index = register_index;
Ok(())
}
fn get_local(&self, local_index: u8, position: Span) -> Result<&Local, VmError> {
self.chunk
.get_local(local_index)
.map_err(|error| VmError::Chunk { error, position })
}
fn get_instruction(
&self,
index: usize,
position: Span,
) -> Result<&(Instruction, Span), VmError> {
self.chunk
.get_instruction(index)
.map_err(|error| VmError::Chunk { error, position })
}
}
#[derive(Debug, Eq, PartialEq)]
enum Register {
Empty,
Value(Value),
StackPointer(u8),
ConstantPointer(u8),
ParentStackPointer(u8),
ParentConstantPointer(u8),
}
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::StackPointer(index) => write!(f, "R{}", index),
Self::ConstantPointer(index) => write!(f, "C{}", index),
Self::ParentStackPointer(index) => write!(f, "PR{}", index),
Self::ParentConstantPointer(index) => write!(f, "PC{}", index),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum VmError {
// Stack errors
StackOverflow { position: Span },
StackUnderflow { position: Span },
// Register errors
EmptyRegister { index: usize, position: Span },
EmptyRegisters { indexes: Range<u8>, position: Span },
RegisterIndexOutOfBounds { index: usize, position: Span },
// Execution errors
ExpectedBoolean { found: Value, position: Span },
ExpectedFunction { found: Value, position: Span },
ExpectedParent { position: Span },
// Wrappers for foreign errors
Chunk { error: ChunkError, position: Span },
NativeFunction(NativeFunctionError),
Value { error: ValueError, position: Span },
}
impl AnnotatedError for VmError {
fn title() -> &'static str {
"Runtime Error"
}
fn description(&self) -> &'static str {
match self {
Self::Chunk { .. } => "Chunk error",
Self::EmptyRegister { .. } => "Empty register",
Self::EmptyRegisters { .. } => "Empty registers",
Self::ExpectedBoolean { .. } => "Expected boolean",
Self::ExpectedFunction { .. } => "Expected function",
Self::ExpectedParent { .. } => "Expected parent",
Self::NativeFunction(error) => error.description(),
Self::RegisterIndexOutOfBounds { .. } => "Register index out of bounds",
Self::StackOverflow { .. } => "Stack overflow",
Self::StackUnderflow { .. } => "Stack underflow",
Self::Value { .. } => "Value error",
}
}
fn details(&self) -> Option<String> {
match self {
Self::Chunk { error, .. } => Some(error.to_string()),
Self::EmptyRegister { index, .. } => Some(format!("Register R{index} is empty")),
Self::EmptyRegisters { indexes: range, .. } => Some(format!(
"Registers R{} to R{} are empty",
range.start, range.end
)),
Self::ExpectedFunction { found, .. } => Some(format!("{found} is not a function")),
Self::RegisterIndexOutOfBounds { index, .. } => {
Some(format!("Register {index} does not exist"))
}
Self::NativeFunction(error) => error.details(),
Self::Value { error, .. } => Some(error.to_string()),
_ => None,
}
}
fn position(&self) -> Span {
match self {
Self::Chunk { position, .. } => *position,
Self::EmptyRegister { position, .. } => *position,
Self::EmptyRegisters { position, .. } => *position,
Self::ExpectedBoolean { position, .. } => *position,
Self::ExpectedFunction { position, .. } => *position,
Self::ExpectedParent { position } => *position,
Self::NativeFunction(error) => error.position(),
Self::RegisterIndexOutOfBounds { position, .. } => *position,
Self::StackOverflow { position } => *position,
Self::StackUnderflow { position } => *position,
Self::Value { position, .. } => *position,
}
}
}

View File

@ -1,66 +0,0 @@
use dust_lang::*;
#[test]
fn constant() {
let source = "42";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(0, 2)),
(Instruction::r#return(true), Span(2, 2))
],
vec![Value::integer(42)],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(42))));
}
#[test]
fn empty() {
let source = "";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
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,
vec![
(
*Instruction::add(0, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(3, 4)
),
(
*Instruction::multiply(1, 0, 2).set_c_is_constant(),
Span(8, 9)
),
(Instruction::r#return(true), Span(11, 11)),
],
vec![Value::integer(1), Value::integer(2), Value::integer(3)],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(9))));
}

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,
vec![
(
*Instruction::equal(true, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 4)
),
(Instruction::jump(1, true), Span(2, 4)),
(Instruction::load_boolean(0, true, true), Span(2, 4)),
(Instruction::load_boolean(0, false, false), Span(2, 4)),
(Instruction::r#return(true), Span(6, 6)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
}
#[test]
fn greater() {
let source = "1 > 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::less_equal(false, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 3)
),
(Instruction::jump(1, true), Span(2, 3)),
(Instruction::load_boolean(0, true, true), Span(2, 3)),
(Instruction::load_boolean(0, false, false), Span(2, 3)),
(Instruction::r#return(true), Span(5, 5)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
}
#[test]
fn greater_than_or_equal() {
let source = "1 >= 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::less(false, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 4)
),
(Instruction::jump(1, true), Span(2, 4)),
(Instruction::load_boolean(0, true, true), Span(2, 4)),
(Instruction::load_boolean(0, false, false), Span(2, 4)),
(Instruction::r#return(true), Span(6, 6)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
}
#[test]
fn less_than() {
let source = "1 < 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::less(true, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 3)
),
(Instruction::jump(1, true), Span(2, 3)),
(Instruction::load_boolean(0, true, true), Span(2, 3)),
(Instruction::load_boolean(0, false, false), Span(2, 3)),
(Instruction::r#return(true), Span(5, 5)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
}
#[test]
fn less_than_or_equal() {
let source = "1 <= 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::less_equal(true, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 4)
),
(Instruction::jump(1, true), Span(2, 4)),
(Instruction::load_boolean(0, true, true), Span(2, 4)),
(Instruction::load_boolean(0, false, false), Span(2, 4)),
(Instruction::r#return(true), Span(6, 6)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
}
#[test]
fn not_equal() {
let source = "1 != 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(false, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 4)
),
(Instruction::jump(1, true), Span(2, 4)),
(Instruction::load_boolean(0, true, true), Span(2, 4)),
(Instruction::load_boolean(0, false, false), Span(2, 4)),
(Instruction::r#return(true), Span(6, 6)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
}

View File

@ -1,420 +0,0 @@
use dust_lang::*;
#[test]
fn equality_assignment_long() {
let source = "let a = if 4 == 4 { true } else { false }; a";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 0)
.set_b_is_constant()
.set_c_is_constant(),
Span(13, 15)
),
(Instruction::jump(1, true), Span(18, 19)),
(Instruction::load_boolean(0, true, true), Span(20, 24)),
(Instruction::load_boolean(0, false, false), Span(34, 39)),
(Instruction::define_local(0, 0, false), Span(4, 5)),
(Instruction::get_local(1, 0), Span(43, 44)),
(Instruction::r#return(true), Span(44, 44)),
],
vec![Value::integer(4), Value::string("a")],
vec![Local::new(
1,
None,
false,
Scope {
depth: 0,
block_index: 0
},
0
)]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
}
#[test]
fn equality_assignment_short() {
let source = "let a = 4 == 4 a";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 0)
.set_b_is_constant()
.set_c_is_constant(),
Span(10, 12)
),
(Instruction::jump(1, true), Span(10, 12)),
(Instruction::load_boolean(0, true, true), Span(10, 12)),
(Instruction::load_boolean(0, false, false), Span(10, 12)),
(Instruction::define_local(0, 0, false), Span(4, 5)),
(Instruction::get_local(1, 0), Span(15, 16)),
(Instruction::r#return(true), Span(16, 16)),
],
vec![Value::integer(4), Value::string("a")],
vec![Local::new(1, None, false, Scope::default(), 0)]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
}
#[test]
fn if_else_assigment_false() {
let source = r#"
let a = if 4 == 3 {
1; 2; 3; 4;
panic()
} else {
1; 2; 3; 4;
42
};
a"#;
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(22, 24)
),
(Instruction::jump(6, true), Span(27, 28)),
(Instruction::load_constant(0, 2, false), Span(41, 42)),
(Instruction::load_constant(1, 3, false), Span(44, 45)),
(Instruction::load_constant(2, 1, false), Span(47, 48)),
(Instruction::load_constant(3, 0, false), Span(50, 51)),
(
Instruction::call_native(4, NativeFunction::Panic, 0),
Span(65, 72)
),
(Instruction::jump(5, true), Span(138, 139)),
(Instruction::load_constant(5, 2, false), Span(102, 103)),
(Instruction::load_constant(6, 3, false), Span(105, 106)),
(Instruction::load_constant(7, 1, false), Span(108, 109)),
(Instruction::load_constant(8, 0, false), Span(111, 112)),
(Instruction::load_constant(9, 4, false), Span(126, 128)),
(Instruction::r#move(9, 4), Span(138, 139)),
(Instruction::define_local(9, 0, false), Span(13, 14)),
(Instruction::get_local(10, 0), Span(148, 149)),
(Instruction::r#return(true), Span(149, 149)),
],
vec![
Value::integer(4),
Value::integer(3),
Value::integer(1),
Value::integer(2),
Value::integer(42),
Value::string("a")
],
vec![Local::new(5, None, false, Scope::default(), 0)]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(42))));
}
#[test]
fn if_else_assigment_true() {
let source = r#"
let a = if 4 == 4 {
1; 2; 3; 4;
42
} else {
1; 2; 3; 4;
panic()
};
a"#;
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 0)
.set_b_is_constant()
.set_c_is_constant(),
Span(22, 24)
),
(Instruction::jump(6, true), Span(27, 28)),
(Instruction::load_constant(0, 1, false), Span(41, 42)),
(Instruction::load_constant(1, 2, false), Span(44, 45)),
(Instruction::load_constant(2, 3, false), Span(47, 48)),
(Instruction::load_constant(3, 0, false), Span(50, 51)),
(Instruction::load_constant(4, 4, false), Span(65, 67)),
(Instruction::jump(5, true), Span(138, 139)),
(Instruction::load_constant(5, 1, false), Span(97, 98)),
(Instruction::load_constant(6, 2, false), Span(100, 101)),
(Instruction::load_constant(7, 3, false), Span(103, 104)),
(Instruction::load_constant(8, 0, false), Span(106, 107)),
(
Instruction::call_native(9, NativeFunction::Panic, 0),
Span(121, 128)
),
(Instruction::r#move(9, 4), Span(138, 139)),
(Instruction::define_local(9, 0, false), Span(13, 14)),
(Instruction::get_local(10, 0), Span(148, 149)),
(Instruction::r#return(true), Span(149, 149)),
],
vec![
Value::integer(4),
Value::integer(1),
Value::integer(2),
Value::integer(3),
Value::integer(42),
Value::string("a")
],
vec![Local::new(5, None, false, Scope::default(), 0)]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(42))));
}
#[test]
fn if_else_complex() {
let source = "
if 1 == 1 {
1; 2; 3; 4;
} else {
1; 2; 3; 4;
}";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 0)
.set_b_is_constant()
.set_c_is_constant(),
Span(14, 16)
),
(Instruction::jump(5, true), Span(19, 20)),
(Instruction::load_constant(0, 0, false), Span(33, 34)),
(Instruction::load_constant(1, 1, false), Span(36, 37)),
(Instruction::load_constant(2, 2, false), Span(39, 40)),
(Instruction::load_constant(3, 3, false), Span(42, 43)),
(Instruction::jump(4, true), Span(95, 95)),
(Instruction::load_constant(4, 0, false), Span(74, 75)),
(Instruction::load_constant(5, 1, false), Span(77, 78)),
(Instruction::load_constant(6, 2, false), Span(80, 81)),
(Instruction::load_constant(7, 3, false), Span(83, 84)),
(Instruction::r#move(7, 3), Span(95, 95)),
(Instruction::r#return(false), Span(95, 95)),
],
vec![
Value::integer(1),
Value::integer(2),
Value::integer(3),
Value::integer(4),
],
vec![]
))
);
assert_eq!(run(source), Ok(None));
}
// #[test]
// fn if_else_nested() {
// let source = r#"
// if 0 == 1 {
// if 0 == 2 {
// 1;
// } else {
// 2;
// }
// } else {
// if 0 == 3 {
// 3;
// } else {
// 4;
// }
// }"#;
// assert_eq!(
// parse(source),
// Ok(Chunk::with_data(
// None,
// vec![
// (
// *Instruction::equal(true, 0, 1)
// .set_b_is_constant()
// .set_c_is_constant(),
// Span(14, 16)
// ),
// (Instruction::jump(7, true), Span(14, 16)),
// (
// *Instruction::equal(true, 0, 2)
// .set_b_is_constant()
// .set_c_is_constant(),
// Span(38, 41)
// ),
// (Instruction::jump(3, true), Span(38, 41)),
// (Instruction::load_constant(0, 1, false), Span(61, 62)),
// (Instruction::jump(1, true1), Span(95, 95)),
// (
// *Instruction::equal(true, 0, 3)
// .set_b_is_constant()
// .set_c_is_constant(),
// Span(77, 79)
// ),
// (Instruction::jump(3, true), Span(77, 79)),
// (Instruction::load_constant(0, 2, false), Span(94, 95)),
// (Instruction::jump(1, true1), Span(95, 95)),
// (Instruction::load_constant(0, 3, false), Span(114, 115)),
// (Instruction::jump(1, true1), Span(95, 95)),
// (Instruction::load_constant(0, 4, false), Span(134, 135)),
// (Instruction::r#return(true), Span(146, 146)),
// ],
// vec![
// Value::integer(0),
// Value::integer(1),
// Value::integer(0),
// Value::integer(2),
// Value::integer(1),
// Value::integer(0),
// Value::integer(3),
// Value::integer(3),
// Value::integer(4)
// ],
// vec![]
// ))
// );
// assert_eq!(run(source), Ok(Some(Value::integer(4))));
// }
#[test]
fn if_else_false() {
let source = "if 1 == 2 { panic() } else { 42 }";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(5, 7)
),
(Instruction::jump(1, true), Span(10, 11)),
(
Instruction::call_native(0, NativeFunction::Panic, 0),
Span(12, 19)
),
(Instruction::load_constant(1, 2, true), Span(29, 31)),
(Instruction::r#move(1, 0), Span(33, 33)),
(Instruction::r#return(true), Span(33, 33)),
],
vec![Value::integer(1), Value::integer(2), Value::integer(42)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(42))));
}
#[test]
fn if_else_true() {
let source = "if 1 == 1 { 42 } else { panic() }";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 0)
.set_b_is_constant()
.set_c_is_constant(),
Span(5, 7)
),
(Instruction::jump(1, true), Span(10, 11)),
(Instruction::load_constant(0, 1, true), Span(12, 14)),
(
Instruction::call_native(1, NativeFunction::Panic, 0),
Span(24, 31)
),
(Instruction::r#move(1, 0), Span(33, 33)),
(Instruction::r#return(true), Span(33, 33))
],
vec![Value::integer(1), Value::integer(42)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(42))));
}
#[test]
fn if_false() {
let source = "if 1 == 2 { 2 }";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(5, 7)
),
(Instruction::jump(1, true), Span(10, 11)),
(Instruction::load_constant(0, 1, false), Span(12, 13)),
(Instruction::r#return(false), Span(15, 15))
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(None));
}
#[test]
fn if_true() {
let source = "if 1 == 1 { 2 }";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 0)
.set_b_is_constant()
.set_c_is_constant(),
Span(5, 7)
),
(Instruction::jump(1, true), Span(10, 11)),
(Instruction::load_constant(0, 1, false), Span(12, 13)),
(Instruction::r#return(false), Span(15, 15))
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(None));
}

View File

@ -1,126 +0,0 @@
use dust_lang::*;
#[test]
fn function() {
let source = "fn(a: int, b: int) -> int { a + b }";
assert_eq!(
run(source),
Ok(Some(Value::function(
Chunk::with_data(
None,
vec![
(Instruction::add(2, 0, 1), Span(30, 31)),
(Instruction::r#return(true), Span(35, 35)),
],
vec![Value::string("a"), Value::string("b"),],
vec![
Local::new(0, Some(Type::Integer), false, Scope::default(), 0),
Local::new(1, Some(Type::Integer), false, Scope::default(), 1)
]
),
FunctionType {
type_parameters: None,
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
return_type: Some(Box::new(Type::Integer)),
}
)))
);
}
#[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,
vec![
(Instruction::load_constant(0, 0, false), Span(0, 36)),
(Instruction::load_constant(1, 1, false), Span(36, 37)),
(Instruction::load_constant(2, 2, false), Span(39, 40)),
(Instruction::call(3, 0, 2), Span(35, 41)),
(Instruction::r#return(true), Span(41, 41)),
],
vec![
Value::function(
Chunk::with_data(
None,
vec![
(Instruction::add(2, 0, 1), Span(30, 31)),
(Instruction::r#return(true), Span(35, 36)),
],
vec![Value::string("a"), Value::string("b"),],
vec![
Local::new(0, Some(Type::Integer), false, Scope::default(), 0),
Local::new(1, Some(Type::Integer), false, Scope::default(), 1)
]
),
FunctionType {
type_parameters: None,
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
return_type: Some(Box::new(Type::Integer)),
}
),
Value::integer(1),
Value::integer(2)
],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::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,
vec![
(Instruction::load_constant(0, 1, false), Span(0, 40)),
(Instruction::define_local(0, 0, false), Span(3, 6)),
(Instruction::r#return(false), Span(40, 40))
],
vec![
Value::string("add"),
Value::function(
Chunk::with_data(
None,
vec![
(Instruction::add(2, 0, 1), Span(35, 36)),
(Instruction::r#return(true), Span(40, 40)),
],
vec![Value::string("a"), Value::string("b")],
vec![
Local::new(0, Some(Type::Integer), false, Scope::default(), 0),
Local::new(1, Some(Type::Integer), false, Scope::default(), 1)
]
),
FunctionType {
type_parameters: None,
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
return_type: Some(Box::new(Type::Integer)),
},
)
],
vec![Local::new(
0,
Some(Type::Function(FunctionType {
type_parameters: None,
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
return_type: Some(Box::new(Type::Integer)),
})),
false,
Scope::default(),
0
),],
)),
);
assert_eq!(run(source), Ok(None));
}

View File

@ -1,127 +0,0 @@
use dust_lang::*;
#[test]
fn empty_list() {
let source = "[]";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_list(0, 0), Span(0, 2)),
(Instruction::r#return(true), Span(2, 2)),
],
vec![],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::abstract_list(0, 0, Type::Any))));
}
#[test]
fn list() {
let source = "[1, 2, 3]";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
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![Value::integer(1), Value::integer(2), Value::integer(3)],
vec![]
)),
);
assert_eq!(
run(source),
Ok(Some(Value::abstract_list(0, 3, Type::Integer)))
);
}
#[test]
fn list_with_complex_expression() {
let source = "[1, 2 + 3 - 4 * 5]";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(1, 2)),
(
*Instruction::add(1, 1, 2)
.set_b_is_constant()
.set_c_is_constant(),
Span(6, 7)
),
(
*Instruction::multiply(2, 3, 4)
.set_b_is_constant()
.set_c_is_constant(),
Span(14, 15)
),
(Instruction::subtract(3, 1, 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![
Value::integer(1),
Value::integer(2),
Value::integer(3),
Value::integer(4),
Value::integer(5)
],
vec![]
)),
);
assert_eq!(
run(source),
Ok(Some(Value::abstract_list(0, 4, Type::Integer)))
);
}
#[test]
fn list_with_simple_expression() {
let source = "[1, 2 + 3, 4]";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(1, 2)),
(
*Instruction::add(1, 1, 2)
.set_b_is_constant()
.set_c_is_constant(),
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![
Value::integer(1),
Value::integer(2),
Value::integer(3),
Value::integer(4),
],
vec![]
)),
);
assert_eq!(
run(source),
Ok(Some(Value::abstract_list(0, 3, Type::Integer)))
);
}

View File

@ -1,77 +0,0 @@
use dust_lang::*;
#[test]
fn and() {
let source = "true && false";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_boolean(0, true, false), Span(0, 4)),
(Instruction::test(0, false), Span(5, 7)),
(Instruction::jump(1, true), Span(5, 7)),
(Instruction::load_boolean(1, false, false), Span(8, 13)),
(Instruction::r#return(true), Span(13, 13)),
],
vec![],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
}
#[test]
fn or() {
let source = "true || false";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_boolean(0, true, false), Span(0, 4)),
(Instruction::test(0, true), Span(5, 7)),
(Instruction::jump(1, true), Span(5, 7)),
(Instruction::load_boolean(1, false, false), Span(8, 13)),
(Instruction::r#return(true), Span(13, 13)),
],
vec![],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
}
#[test]
fn variable_and() {
let source = "let a = true; let b = false; a && b";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_boolean(0, true, false), Span(8, 12)),
(Instruction::define_local(0, 0, false), Span(4, 5)),
(Instruction::load_boolean(1, false, false), Span(22, 27)),
(Instruction::define_local(1, 1, false), Span(18, 19)),
(Instruction::get_local(2, 0), Span(29, 30)),
(Instruction::test(2, false), Span(31, 33)),
(Instruction::jump(1, true), Span(31, 33)),
(Instruction::get_local(3, 1), Span(34, 35)),
(Instruction::r#return(true), Span(35, 35)),
],
vec![Value::string("a"), Value::string("b"),],
vec![
Local::new(0, None, false, Scope::default(), 0),
Local::new(1, None, false, Scope::default(), 1),
]
))
);
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
}

View File

@ -1,35 +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,
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(
*Instruction::less(true, 0, 2).set_c_is_constant(),
Span(23, 24)
),
(Instruction::jump(2, true), Span(41, 42)),
(*Instruction::add(0, 0, 3).set_c_is_constant(), Span(39, 40)),
(Instruction::jump(3, false), Span(41, 42)),
(Instruction::get_local(1, 0), Span(41, 42)),
(Instruction::r#return(true), Span(42, 42)),
],
vec![
Value::integer(0),
Value::string("x"),
Value::integer(5),
Value::integer(1),
],
vec![Local::new(1, None, true, Scope::default(), 0),]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(5))));
}

View File

@ -1,324 +0,0 @@
use dust_lang::*;
#[test]
fn add() {
let source = "1 + 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::add(0, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 3)
),
(Instruction::r#return(true), Span(5, 5))
],
vec![Value::integer(1), Value::integer(2)],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(3))));
}
#[test]
fn add_assign() {
let source = "let mut a = 1; a += 2; a";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(*Instruction::add(0, 0, 2).set_c_is_constant(), Span(17, 19)),
(Instruction::get_local(1, 0), Span(23, 24)),
(Instruction::r#return(true), Span(24, 24))
],
vec![Value::integer(1), Value::string("a"), Value::integer(2)],
vec![Local::new(1, None, true, Scope::default(), 0)]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(3))));
}
#[test]
fn add_assign_expects_mutable_variable() {
let source = "1 += 2";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::ExpectedMutableVariable {
found: Token::Integer("1").to_owned(),
position: Span(0, 1)
},
source
})
);
}
// #[test]
// fn add_expects_integer_float_or_string() {
// let source = "true + false";
// assert_eq!(
// parse(source),
// Err(DustError::Parse {
// error: ParseError::ExpectedIntegerFloatOrString {
// found: Token::True,
// position: Span(0, 3)
// },
// source
// })
// );
// }
#[test]
fn divide() {
let source = "2 / 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::divide(0, 0, 0)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 3)
),
(Instruction::r#return(true), Span(5, 5))
],
vec![Value::integer(2)],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(1))));
}
#[test]
fn divide_assign() {
let source = "let mut a = 2; a /= 2; a";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(
*Instruction::divide(0, 0, 0).set_c_is_constant(),
Span(17, 19)
),
(Instruction::get_local(1, 0), Span(23, 24)),
(Instruction::r#return(true), Span(24, 24))
],
vec![Value::integer(2), Value::string("a")],
vec![Local::new(1, None, true, Scope::default(), 0)]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(1))));
}
#[test]
fn divide_assign_expects_mutable_variable() {
let source = "1 -= 2";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::ExpectedMutableVariable {
found: Token::Integer("1").to_owned(),
position: Span(0, 1)
},
source
})
);
}
#[test]
fn math_operator_precedence() {
let source = "1 + 2 - 3 * 4 / 5";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::add(0, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 3)
),
(
*Instruction::multiply(1, 2, 3)
.set_b_is_constant()
.set_c_is_constant(),
Span(10, 11)
),
(
*Instruction::divide(2, 1, 4).set_c_is_constant(),
Span(14, 15)
),
(Instruction::subtract(3, 0, 2), Span(6, 7)),
(Instruction::r#return(true), Span(17, 17)),
],
vec![
Value::integer(1),
Value::integer(2),
Value::integer(3),
Value::integer(4),
Value::integer(5),
],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(1))));
}
#[test]
fn multiply() {
let source = "1 * 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::multiply(0, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 3)
),
(Instruction::r#return(true), Span(5, 5)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(2))));
}
#[test]
fn multiply_assign() {
let source = "let mut a = 2; a *= 3 a";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(
*Instruction::multiply(0, 0, 2).set_c_is_constant(),
Span(17, 19)
),
(Instruction::get_local(1, 0), Span(22, 23)),
(Instruction::r#return(true), Span(23, 23))
],
vec![Value::integer(2), Value::string("a"), Value::integer(3)],
vec![Local::new(1, None, true, Scope::default(), 0),]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(6))));
}
#[test]
fn multiply_assign_expects_mutable_variable() {
let source = "1 *= 2";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::ExpectedMutableVariable {
found: Token::Integer("1").to_owned(),
position: Span(0, 1)
},
source
})
);
}
#[test]
fn subtract() {
let source = "1 - 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::subtract(0, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 3)
),
(Instruction::r#return(true), Span(5, 5)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(-1))));
}
#[test]
fn subtract_assign() {
let source = "let mut x = 42; x -= 2; x";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(12, 14)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(
*Instruction::subtract(0, 0, 2).set_c_is_constant(),
Span(18, 20)
),
(Instruction::get_local(1, 0), Span(24, 25)),
(Instruction::r#return(true), Span(25, 25)),
],
vec![Value::integer(42), Value::string("x"), Value::integer(2)],
vec![Local::new(1, None, true, Scope::default(), 0)]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(40))));
}
#[test]
fn subtract_assign_expects_mutable_variable() {
let source = "1 -= 2";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::ExpectedMutableVariable {
found: Token::Integer("1").to_owned(),
position: Span(0, 1)
},
source
})
);
}

View File

@ -1,59 +0,0 @@
use dust_lang::*;
#[test]
fn panic() {
let source = "panic(\"Goodbye world!\", 42)";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(6, 22)),
(Instruction::load_constant(1, 1, false), Span(24, 26)),
(
Instruction::call_native(2, NativeFunction::Panic, 2),
Span(0, 27)
),
(Instruction::r#return(true), Span(27, 27))
],
vec![Value::string("Goodbye world!"), Value::integer(42)],
vec![]
)),
);
assert_eq!(
run(source),
Err(DustError::Runtime {
error: VmError::NativeFunction(NativeFunctionError::Panic {
message: Some("Goodbye world! 42".to_string()),
position: Span(0, 27)
}),
source
})
)
}
#[test]
fn to_string() {
let source = "to_string(42)";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(10, 12)),
(
Instruction::call_native(1, NativeFunction::ToString, 1),
Span(0, 13)
),
(Instruction::r#return(true), Span(13, 13))
],
vec![Value::integer(42)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::string("42"))))
}

View File

@ -1,244 +0,0 @@
use dust_lang::*;
#[test]
fn allow_access_to_parent_scope() {
let source = r#"
let x = 1;
{
x
}
"#;
assert_eq!(run(source), Ok(Some(Value::integer(1))));
}
#[test]
fn block_scope() {
let source = "
let a = 0;
{
let b = 42;
{
let c = 1;
}
let d = 2;
}
let e = 1;
";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(17, 18)),
(Instruction::define_local(0, 0, false), Span(13, 14)),
(Instruction::load_constant(1, 2, false), Span(50, 52)),
(Instruction::define_local(1, 1, false), Span(46, 47)),
(Instruction::load_constant(2, 4, false), Span(92, 93)),
(Instruction::define_local(2, 2, false), Span(88, 89)),
(Instruction::load_constant(3, 6, false), Span(129, 130)),
(Instruction::define_local(3, 3, false), Span(125, 126)),
(Instruction::load_constant(4, 4, false), Span(158, 159)),
(Instruction::define_local(4, 4, false), Span(154, 155)),
(Instruction::r#return(false), Span(165, 165))
],
vec![
Value::integer(0),
Value::string("a"),
Value::integer(42),
Value::string("b"),
Value::integer(1),
Value::string("c"),
Value::integer(2),
Value::string("d"),
Value::string("e"),
],
vec![
Local::new(1, None, false, Scope::new(0, 0), 0),
Local::new(3, None, false, Scope::new(1, 1), 1),
Local::new(5, None, false, Scope::new(2, 2), 2),
Local::new(7, None, false, Scope::new(1, 1), 3),
Local::new(8, None, false, Scope::new(0, 0), 4),
]
)),
);
assert_eq!(run(source), Ok(None));
}
#[test]
fn multiple_block_scopes() {
let source = "
let a = 0;
{
let b = 42;
{
let c = 1;
}
let d = 2;
}
let q = 42;
{
let b = 42;
{
let c = 1;
}
let d = 2;
}
let e = 1;
";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(17, 18)),
(Instruction::define_local(0, 0, false), Span(13, 14)),
(Instruction::load_constant(1, 2, false), Span(50, 52)),
(Instruction::define_local(1, 1, false), Span(46, 47)),
(Instruction::load_constant(2, 4, false), Span(92, 93)),
(Instruction::define_local(2, 2, false), Span(88, 89)),
(Instruction::load_constant(3, 6, false), Span(129, 130)),
(Instruction::define_local(3, 3, false), Span(125, 126)),
(Instruction::load_constant(4, 2, false), Span(158, 160)),
(Instruction::define_local(4, 4, false), Span(154, 155)),
(Instruction::load_constant(5, 2, false), Span(192, 194)),
(Instruction::define_local(5, 5, false), Span(188, 189)),
(Instruction::load_constant(6, 4, false), Span(234, 235)),
(Instruction::define_local(6, 6, false), Span(230, 231)),
(Instruction::load_constant(7, 6, false), Span(271, 272)),
(Instruction::define_local(7, 7, false), Span(267, 268)),
(Instruction::load_constant(8, 4, false), Span(300, 301)),
(Instruction::define_local(8, 8, false), Span(296, 297)),
(Instruction::r#return(false), Span(307, 307))
],
vec![
Value::integer(0),
Value::string("a"),
Value::integer(42),
Value::string("b"),
Value::integer(1),
Value::string("c"),
Value::integer(2),
Value::string("d"),
Value::string("q"),
Value::string("e"),
],
vec![
Local::new(1, None, false, Scope::new(0, 0), 0),
Local::new(3, None, false, Scope::new(1, 1), 1),
Local::new(5, None, false, Scope::new(2, 2), 2),
Local::new(7, None, false, Scope::new(1, 1), 3),
Local::new(8, None, false, Scope::new(0, 0), 4),
Local::new(3, None, false, Scope::new(1, 3), 5),
Local::new(5, None, false, Scope::new(2, 4), 6),
Local::new(7, None, false, Scope::new(1, 3), 7),
Local::new(9, None, false, Scope::new(0, 0), 8),
]
)),
);
assert_eq!(run(source), Ok(None));
}
#[test]
fn disallow_access_to_child_scope() {
let source = r#"
{
let x = 1;
}
x
"#;
assert_eq!(
run(source),
Err(DustError::Compile {
error: CompileError::VariableOutOfScope {
identifier: "x".to_string(),
position: Span(52, 53),
variable_scope: Scope::new(1, 1),
access_scope: Scope::new(0, 0),
},
source
})
);
}
#[test]
fn disallow_access_to_child_scope_nested() {
let source = r#"
{
{
let x = 1;
}
x
}
"#;
assert_eq!(
run(source),
Err(DustError::Compile {
error: CompileError::VariableOutOfScope {
identifier: "x".to_string(),
position: Span(78, 79),
variable_scope: Scope::new(2, 2),
access_scope: Scope::new(1, 1),
},
source
})
);
}
#[test]
fn disallow_access_to_sibling_scope() {
let source = r#"
{
let x = 1;
}
{
x
}
"#;
assert_eq!(
run(source),
Err(DustError::Compile {
error: CompileError::VariableOutOfScope {
identifier: "x".to_string(),
variable_scope: Scope::new(1, 1),
access_scope: Scope::new(1, 2),
position: Span(66, 67),
},
source
})
);
}
#[test]
fn disallow_access_to_sibling_scope_nested() {
let source = r#"
{
{
let x = 1;
}
{
x
}
}
"#;
assert_eq!(
run(source),
Err(DustError::Compile {
error: CompileError::VariableOutOfScope {
identifier: "x".to_string(),
variable_scope: Scope::new(2, 2),
access_scope: Scope::new(2, 3),
position: Span(96, 97),
},
source
})
);
}

View File

@ -1,42 +0,0 @@
use dust_lang::*;
#[test]
fn negate() {
let source = "-(42)";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(*Instruction::negate(0, 0).set_b_is_constant(), Span(0, 1)),
(Instruction::r#return(true), Span(5, 5)),
],
vec![Value::integer(42)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(-42))));
}
#[test]
fn not() {
let source = "!true";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_boolean(0, true, false), Span(1, 5)),
(Instruction::not(1, 0), Span(0, 1)),
(Instruction::r#return(true), Span(5, 5)),
],
vec![],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
}

View File

@ -1,63 +0,0 @@
use dust_lang::*;
#[test]
fn define_local() {
let source = "let x = 42;";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(8, 10)),
(Instruction::define_local(0, 0, false), Span(4, 5)),
(Instruction::r#return(false), Span(11, 11))
],
vec![Value::integer(42), Value::string("x")],
vec![Local::new(1, None, false, Scope::default(), 0)]
)),
);
assert_eq!(run(source), Ok(None));
}
#[test]
fn let_statement_expects_identifier() {
let source = "let 1 = 2";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::ExpectedToken {
expected: TokenKind::Identifier,
found: Token::Integer("1").to_owned(),
position: Span(4, 5)
},
source
})
);
}
#[test]
fn set_local() {
let source = "let mut x = 41; x = 42; x";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(12, 14)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(Instruction::load_constant(1, 2, false), Span(20, 22)),
(Instruction::set_local(1, 0), Span(16, 17)),
(Instruction::get_local(2, 0), Span(24, 25)),
(Instruction::r#return(true), Span(25, 25)),
],
vec![Value::integer(41), Value::string("x"), Value::integer(42)],
vec![Local::new(1, None, true, Scope::default(), 0)]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(42))));
}

View File

@ -1,15 +0,0 @@
[package]
name = "dust-shell"
version = "0.1.0"
authors.workspace = true
edition.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
[dependencies]
clap = { version = "4.5.14", features = ["derive"] }
colored = "2.1.0"
dust-lang = { path = "../dust-lang" }
env_logger = "0.11.5"
log = "0.4.22"

View File

@ -1,138 +0,0 @@
use std::{fs::read_to_string, io::Write};
use clap::Parser;
use colored::Colorize;
use dust_lang::{compile, format, run};
use log::{Level, LevelFilter};
#[derive(Parser)]
struct Cli {
/// Source code sent via command line
#[arg(short, long)]
command: Option<String>,
/// Whether to output formatted source code
#[arg(short, long)]
format: bool,
/// Whether to output line numbers in formatted source code
#[arg(long)]
format_line_numbers: Option<bool>,
/// Whether to output colors in formatted source code
#[arg(long)]
format_colored: Option<bool>,
/// Whether to output the disassembled chunk
#[arg(short, long)]
parse: bool,
/// Whether to style the disassembled chunk
#[arg(long)]
style_disassembly: Option<bool>,
/// Log level
#[arg(short, long)]
log: Option<LevelFilter>,
/// Path to a source code file
path: Option<String>,
}
fn main() {
let args = Cli::parse();
let mut logger = env_logger::builder();
logger.format(|buf, record| {
let level_display = match record.level() {
Level::Info => "INFO".bold().white(),
Level::Debug => "DEBUG".bold().blue(),
Level::Warn => "WARN".bold().yellow(),
Level::Error => "ERROR".bold().red(),
Level::Trace => "TRACE".bold().purple(),
};
let module = record
.module_path()
.map(|path| path.split("::").last().unwrap_or(path))
.unwrap_or("unknown")
.dimmed();
let display = format!("{level_display:5} {module:^6} {args}", args = record.args());
writeln!(buf, "{display}")
});
if let Some(level) = args.log {
logger.filter_level(level).init();
} else {
logger.parse_env("DUST_LOG").init();
}
let source = if let Some(path) = &args.path {
&read_to_string(path).expect("Failed to read file")
} else if let Some(command) = &args.command {
command
} else {
eprintln!("No input provided");
return;
};
if args.format {
let line_numbers = args.format_line_numbers.unwrap_or(true);
let colored = args.format_colored.unwrap_or(true);
log::info!("Formatting source");
match format(source, line_numbers, colored) {
Ok(formatted) => println!("{}", formatted),
Err(error) => {
eprintln!("{}", error.report());
}
}
}
if args.parse {
let styled = args.style_disassembly.unwrap_or(true);
log::info!("Parsing source");
match compile(source) {
Ok(chunk) => {
let disassembly = chunk
.disassembler()
.source(source)
.styled(styled)
.disassemble();
println!("{}", disassembly);
}
Err(error) => {
eprintln!("{}", error.report());
}
}
}
if args.format || args.parse {
return;
}
match run(source) {
Ok(Some(value)) => println!("{}", value),
Ok(None) => {}
Err(error) => {
eprintln!("{}", error.report());
}
}
}
#[cfg(test)]
mod tests {
use clap::CommandFactory;
use super::*;
#[test]
fn verify_cli() {
Cli::command().debug_assert();
}
}

View File

@ -1,5 +0,0 @@
var i = 0;
while (i < 10000) {
i++;
}

View File

@ -1,6 +0,0 @@
[
{ "name": "Sammy", "type": "shark", "clams": 5 },
{ "name": "Bubbles", "type": "orca", "clams": 3 },
{ "name": "Splish", "type": "dolphin", "clams": 2 },
{ "name": "Splash", "type": "dolphin", "clams": 2 }
]

View File

@ -1,11 +0,0 @@
function fib(n) {
if (n <= 0) {
return 0;
} else if (n === 1) {
return 1;
} else {
return fib(n - 1) + fib(n - 2);
}
}
console.log(fib(25));

View File

@ -0,0 +1,6 @@
const fs = require('node:fs');
const data = fs.readFileSync("examples/assets/jq_data.json");
const items = JSON.parse(data);
const output = items.map((item) => item.commit.committer.name);
console.log(output)

19
examples/async.ds Normal file
View File

@ -0,0 +1,19 @@
(output "This will print first.")
create_random_numbers = |count| => {
numbers = [];
while (length numbers) < count {
numbers += (random_integer)
}
(output "Made " + count + " numbers.")
}
async {
(create_random_numbers 1000)
(create_random_numbers 100)
(create_random_numbers 10)
}
(output "This will print last.")

View File

@ -1,21 +0,0 @@
count_slowly = fn (
multiplier: int,
) {
i = 0
while i < 10 {
sleep_time = i * multiplier;
thread.sleep(sleep_time)
thread.write_line(i as str)
i += 1
}
}
async {
count_slowly(50)
count_slowly(100)
count_slowly(200)
count_slowly(250)
}

View File

@ -0,0 +1,13 @@
raw_data = async {
{
cast = (download "https://api.sampleapis.com/futurama/cast")
characters = (download "https://api.sampleapis.com/futurama/characters")
episodes = (download "https://api.sampleapis.com/futurama/episodes")
}
}
cast_len = (length (from_json data:cast))
characters_len = (length (from_json data:characters))
episodes_len = (length (from_json data:episodes))
(output [cast_len, characters_len, episodes_len])

57
examples/clue_solver.ds Normal file
View File

@ -0,0 +1,57 @@
all_cards = {
rooms = ['Library' 'Kitchen' 'Conservatory']
suspects = ['White' 'Green' 'Scarlett']
weapons = ['Rope' 'Lead_Pipe' 'Knife']
}
is_ready_to_solve = |cards| => {
((length cards:suspects) == 1)
&& ((length cards:rooms) == 1)
&& ((length cards:weapons) == 1)
}
take_turn = |opponent_card, current_room, cards| => {
(remove_card opponent_card cards)
(make_guess current_room cards)
cards
}
remove_card = |opponent_card cards| => {
remove card from cards:rooms {
card == opponent_card
}
remove card from cards:weapons {
card == opponent_card
}
remove card from cards:suspects {
card == opponent_card
}
}
make_guess = |current_room cards| => {
if (is_ready_to_solve cards) {
(output 'It was '
+ cards:suspects:0
+ ' in the '
+ cards:rooms:0
+ ' with the '
+ cards:weapons:0
+ '!')
} else {
(output 'I accuse '
+ (random cards:suspects)
+ ' in the '
+ current_room
+ ' with the '
+ (random cards:weapons)
+ '!')
}
}
(take_turn 'Rope' 'Kitchen'
(take_turn 'Library' 'Kitchen'
(take_turn 'Conservatory' 'Kitchen'
(take_turn 'White' 'Kitchen'
(take_turn 'Green' 'Kitchen'
(take_turn 'Knife' 'Kitchen' all_cards))))))

View File

@ -1,5 +0,0 @@
let mut i = 0;
while i < 10000 {
i += 1;
}

8
examples/fetch.ds Normal file
View File

@ -0,0 +1,8 @@
raw_data = (download "https://api.sampleapis.com/futurama/cast")
cast_data = (from_json raw_data)
names = transform cast_member in cast_data {
cast_member.name
}
(assert_equal "Billy West", names.0)

View File

@ -1,8 +1,9 @@
fn fib (n: int) -> int {
if n <= 0 { return 0 }
if n == 1 { return 1 }
fib(n - 1) + fib(n - 2)
fib = |i| => {
if i <= 1 {
1
} else {
(fib i - 1) + (fib i - 2)
}
}
write_line(fib(25))
(fib 5)

3
examples/filter_loop.ds Normal file
View File

@ -0,0 +1,3 @@
filter item in (from_json (read 'examples/assets/jq_data.json')) {
(length item.commit.committer.name) <= 10
}

7
examples/find_loop.ds Normal file
View File

@ -0,0 +1,7 @@
list = [1 2 1 3]
found = find i in list {
i == 3
}
(assert_equal 3 found)

View File

@ -1,20 +1,18 @@
let mut count = 1
count = 1
while count <= 15 {
let divides_by_3 = count % 3 == 0
let divides_by_5 = count % 5 == 0
divides_by_3 = count % 3 == 0
divides_by_5 = count % 5 == 0
let output = if divides_by_3 && divides_by_5 {
"fizzbuzz"
} else if divides_by_3 {
"fizz"
if divides_by_3 && divides_by_5 {
(output 'fizzbuzz')
} else if divides_by_3 {
(output 'fizz')
} else if divides_by_5 {
"buzz"
} else {
to_string(count)
(output 'buzz')
} else {
(output count)
}
write_line(output)
count += 1
}

6
examples/for_loop.ds Normal file
View File

@ -0,0 +1,6 @@
list = [1 2 3]
for i in list {
i += 1
(output i)
}

View File

@ -1,19 +0,0 @@
write_line("Guess the number.")
let secret_number = random(0..100);
loop {
write_line("Input your guess.")
let input = io.read_line();
let guess = int.parse(input);
if guess < secret_number {
io.write_line("Too low!")
} else if guess > secret_number {
io.write_line("Too high!")
} else {
io.write_line("You win!")
break
}
}

View File

@ -1,6 +1 @@
write_line("Hello, world!")
write_line("Enter your name...")
let name = read_line()
write_line("Hello " + name + "!")
(output 'Hello, world!')

8
examples/jq_data.ds Normal file
View File

@ -0,0 +1,8 @@
data = (from_json (read 'examples/assets/jq_data.json'))
transform commit_data in data {
{
message = commit_data.commit.message
name = commit_data.commit.committer.name
}
}

View File

@ -1,4 +0,0 @@
input = fs.read_file('examples/assets/data.json')
data = json.parse(input)
length(data)

7
examples/list.ds Normal file
View File

@ -0,0 +1,7 @@
numbers = [1, 2, 3]
x = numbers:0
y = numbers:1
z = numbers:2
(assert_equal x + y, z)

11
examples/map.ds Normal file
View File

@ -0,0 +1,11 @@
dictionary = {
dust = "awesome"
answer = 42
}
(output
'Dust is '
+ dictionary:dust
+ '! The answer is '
+ dictionary:answer
)

16
examples/random.ds Normal file
View File

@ -0,0 +1,16 @@
stuff = [
(random_integer)
(random_integer)
(random_integer)
(random_float)
(random_float)
(random_float)
(random_boolean)
(random_boolean)
(random_boolean)
"foobar_1"
"foobar_2"
"foobar_3"
]
(random stuff)

8
examples/remove_loop.ds Normal file
View File

@ -0,0 +1,8 @@
list = [1 2 1 3]
removed = remove i from list {
i == 3
}
(assert_equal 3 removed)
(assert_equal [1 2 1] list)

19
examples/sea_creatures.ds Normal file
View File

@ -0,0 +1,19 @@
raw_data = (read 'examples/assets/seaCreatures.json')
sea_creatures = (from_json raw_data)
data = {
creatures = []
total_clams = 0
dolphin_clams = 0
}
for creature in sea_creatures {
data:creatures += creature:name
data:total_clams += creature:clams
if creature:type == 'dolphin' {
data:dolphin_clams += creature:clams
}
}
data

15
examples/select.ds Normal file
View File

@ -0,0 +1,15 @@
my_table = table |text number bool| [
["a", 1, true]
["b", 2, true]
["a", 3, true]
]
test_table = table |text bool| [
["a", true]
["b", true]
["a", true]
]
test_select = select |text bool| from my_table;
(assert_equal test_select, test_table)

43
examples/table.ds Normal file
View File

@ -0,0 +1,43 @@
my_table = table |text number bool| [
["a", 1, true]
["b", 2, true]
["a", 3, true]
]
test_table = table |text bool| [
["a", true]
["b", true]
["a", true]
]
test_select = select |text bool| from my_table;
(assert_equal test_select, test_table)
test_table = table |number bool| [
[1, true]
[3, true]
]
test_select_where = select |number, bool| from my_table {
text == "a"
}
(assert_equal test_select_where, test_table)
test_table = table |text number bool| [
["a", 1, true]
["b", 2, true]
["a", 3, true]
["c", 4, true]
["d", 5, true]
["e", 6, true]
]
insert into my_table [
["c", 4, true]
["d", 5, true]
["e", 6, true]
]
(assert_equal test_table, my_table)

View File

@ -0,0 +1,5 @@
data = (from_json (read "examples/assets/jq_data.json"))
transform item in data {
item:commit:committer:name
}

View File

@ -1,20 +0,0 @@
// This function returns its argument.
foo = fn <T>(x: T) -> T { x }
// Use turbofish to supply type information.
bar = foo::<str>("hi")
// Use type annotation
baz: str = foo("hi")
// The `json.parse` function takes a string and returns the specified type
// Use turbofish
x = json.parse::<int>("1")
// Use type annotation
x: int = json.parse("1")
x: int = {
json.parse("1")
}

10
examples/variables.ds Normal file
View File

@ -0,0 +1,10 @@
x = 1
y = "hello dust!"
z = 42.0
list = [3, 2, x]
big_list = [x, y, z, list]
foo = {
x = "bar"
y = 42
z = 0
}

6
examples/while_loop.ds Normal file
View File

@ -0,0 +1,6 @@
i = 0
while i < 10 {
(output i)
i += 1
}

12
examples/yield.ds Normal file
View File

@ -0,0 +1,12 @@
1 -> (output)
add_one = |list| => {
transform number in list {
number + 1
}
}
foo = [1, 2, 3] -> (add_one)
(assert_equal [2 3 4] foo)

View File

@ -0,0 +1,80 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{AbstractTree, Error, Identifier, Map, Result, Statement, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Assignment {
identifier: Identifier,
operator: AssignmentOperator,
statement: Statement,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum AssignmentOperator {
Equal,
PlusEqual,
MinusEqual,
}
impl AbstractTree for Assignment {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
Error::expect_syntax_node(source, "assignment", node)?;
let identifier_node = node.child(0).unwrap();
let identifier = Identifier::from_syntax_node(source, identifier_node)?;
let operator_node = node.child(1).unwrap().child(0).unwrap();
let operator = match operator_node.kind() {
"=" => AssignmentOperator::Equal,
"+=" => AssignmentOperator::PlusEqual,
"-=" => AssignmentOperator::MinusEqual,
_ => {
return Err(Error::UnexpectedSyntaxNode {
expected: "=, += or -=",
actual: operator_node.kind(),
location: operator_node.start_position(),
relevant_source: source[operator_node.byte_range()].to_string(),
})
}
};
let statement_node = node.child(2).unwrap();
let statement = Statement::from_syntax_node(source, statement_node)?;
Ok(Assignment {
identifier,
operator,
statement,
})
}
fn run(&self, source: &str, context: &mut Map) -> Result<Value> {
let key = self.identifier.inner().clone();
let value = self.statement.run(source, context)?;
let new_value = match self.operator {
AssignmentOperator::PlusEqual => {
if let Some(mut previous_value) = context.variables()?.get(&key).cloned() {
previous_value += value;
previous_value
} else {
Value::Empty
}
}
AssignmentOperator::MinusEqual => {
if let Some(mut previous_value) = context.variables()?.get(&key).cloned() {
previous_value -= value;
previous_value
} else {
Value::Empty
}
}
AssignmentOperator::Equal => value,
};
context.variables_mut()?.insert(key, new_value);
Ok(Value::Empty)
}
}

View File

@ -0,0 +1,72 @@
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{AbstractTree, Map, Result, Statement, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Block {
is_async: bool,
statements: Vec<Statement>,
}
impl AbstractTree for Block {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
debug_assert_eq!("block", node.kind());
let first_child = node.child(0).unwrap();
let is_async = first_child.kind() == "async";
let statement_count = if is_async {
node.child_count() - 3
} else {
node.child_count() - 2
};
let mut statements = Vec::with_capacity(statement_count);
for index in 1..statement_count + 1 {
let child_node = node.child(index).unwrap();
if child_node.is_named() {
let statement = Statement::from_syntax_node(source, child_node)?;
statements.push(statement);
}
}
Ok(Block {
is_async,
statements,
})
}
fn run(&self, source: &str, context: &mut Map) -> Result<Value> {
if self.is_async {
let statements = &self.statements;
statements
.into_par_iter()
.enumerate()
.find_map_first(|(index, statement)| {
let mut context = context.clone();
let result = statement.run(source, &mut context);
if result.is_err() {
Some(result)
} else if index == statements.len() - 1 {
Some(result)
} else {
None
}
})
.unwrap_or(Ok(Value::Empty))
} else {
let mut prev_result = None;
for statement in &self.statements {
prev_result = Some(statement.run(source, context)?);
}
Ok(prev_result.unwrap_or(Value::Empty))
}
}
}

View File

@ -0,0 +1,642 @@
use std::{
env::current_dir,
fs::{copy, metadata, read_dir, read_to_string, remove_file, write, File},
io::Write,
path::PathBuf,
process::Command,
};
use rand::{random, thread_rng, Rng};
use reqwest::blocking::get;
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{AbstractTree, Error, Expression, List, Map, Result, Table, Value, ValueType};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum BuiltInFunction {
// General
Assert(Vec<Expression>),
AssertEqual(Vec<Expression>),
Download(Expression),
Context,
Help(Option<Expression>),
Length(Expression),
Output(Vec<Expression>),
OutputError(Vec<Expression>),
Type(Expression),
Workdir,
// Filesystem
Append(Vec<Expression>),
Metadata(Expression),
Move(Vec<Expression>),
Read(Expression),
Remove(Expression),
Write(Vec<Expression>),
// Format conversion
FromJson(Expression),
ToJson(Expression),
ToString(Expression),
ToFloat(Expression),
// Command
Bash(Vec<Expression>),
Fish(Vec<Expression>),
Raw(Vec<Expression>),
Sh(Vec<Expression>),
Zsh(Vec<Expression>),
// Random
Random(Vec<Expression>),
RandomBoolean,
RandomInteger,
RandomFloat,
// Table
Columns(Expression),
Rows(Expression),
// List
Reverse(Expression, Option<(Expression, Expression)>),
}
impl AbstractTree for BuiltInFunction {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
debug_assert_eq!("built_in_function", node.kind());
fn parse_expressions(source: &str, node: Node) -> Result<Vec<Expression>> {
let mut expressions = Vec::new();
for index in 1..node.child_count() {
let child_node = node.child(index).unwrap();
if child_node.kind() == "expression" {
let expression = Expression::from_syntax_node(source, child_node)?;
expressions.push(expression);
}
}
Ok(expressions)
}
let tool_node = node.child(0).unwrap();
let tool = match tool_node.kind() {
"assert" => {
let expressions = parse_expressions(source, node)?;
BuiltInFunction::Assert(expressions)
}
"assert_equal" => {
let expressions = parse_expressions(source, node)?;
BuiltInFunction::AssertEqual(expressions)
}
"context" => BuiltInFunction::Context,
"download" => {
let expression_node = node.child(1).unwrap();
let expression = Expression::from_syntax_node(source, expression_node)?;
BuiltInFunction::Download(expression)
}
"help" => {
let child_node = node.child(1).unwrap();
let expression = if child_node.is_named() {
Some(Expression::from_syntax_node(source, child_node)?)
} else {
None
};
BuiltInFunction::Help(expression)
}
"length" => {
let expression_node = node.child(1).unwrap();
let expression = Expression::from_syntax_node(source, expression_node)?;
BuiltInFunction::Length(expression)
}
"output" => {
let expressions = parse_expressions(source, node)?;
BuiltInFunction::Output(expressions)
}
"output_error" => {
let expressions = parse_expressions(source, node)?;
BuiltInFunction::OutputError(expressions)
}
"type" => {
let expression_node = node.child(1).unwrap();
let expression = Expression::from_syntax_node(source, expression_node)?;
BuiltInFunction::Type(expression)
}
"workdir" => BuiltInFunction::Workdir,
"append" => {
let expressions = parse_expressions(source, node)?;
Error::expect_tool_argument_amount("append", 2, expressions.len())?;
BuiltInFunction::Append(expressions)
}
"metadata" => {
let expression_node = node.child(1).unwrap();
let expression = Expression::from_syntax_node(source, expression_node)?;
BuiltInFunction::Metadata(expression)
}
"move" => {
let expressions = parse_expressions(source, node)?;
Error::expect_tool_argument_amount("move", 2, expressions.len())?;
BuiltInFunction::Move(expressions)
}
"read" => {
let expression_node = node.child(1).unwrap();
let expression = Expression::from_syntax_node(source, expression_node)?;
BuiltInFunction::Read(expression)
}
"remove" => {
let expression_node = node.child(1).unwrap();
let expression = Expression::from_syntax_node(source, expression_node)?;
BuiltInFunction::Remove(expression)
}
"write" => {
let expressions = parse_expressions(source, node)?;
Error::expect_tool_argument_amount("write", 2, expressions.len())?;
BuiltInFunction::Write(expressions)
}
"from_json" => {
let expression_node = node.child(1).unwrap();
let expression = Expression::from_syntax_node(source, expression_node)?;
BuiltInFunction::FromJson(expression)
}
"to_json" => {
let expression_node = node.child(1).unwrap();
let expression = Expression::from_syntax_node(source, expression_node)?;
BuiltInFunction::ToJson(expression)
}
"to_string" => {
let expression_node = node.child(1).unwrap();
let expression = Expression::from_syntax_node(source, expression_node)?;
BuiltInFunction::ToString(expression)
}
"to_float" => {
let expression_node = node.child(1).unwrap();
let expression = Expression::from_syntax_node(source, expression_node)?;
BuiltInFunction::ToFloat(expression)
}
"bash" => {
let expressions = parse_expressions(source, node)?;
BuiltInFunction::Bash(expressions)
}
"fish" => {
let expressions = parse_expressions(source, node)?;
BuiltInFunction::Fish(expressions)
}
"raw" => {
let expressions = parse_expressions(source, node)?;
BuiltInFunction::Raw(expressions)
}
"sh" => {
let expressions = parse_expressions(source, node)?;
BuiltInFunction::Sh(expressions)
}
"zsh" => {
let expressions = parse_expressions(source, node)?;
BuiltInFunction::Zsh(expressions)
}
"random" => {
let expressions = parse_expressions(source, node)?;
BuiltInFunction::Random(expressions)
}
"random_boolean" => BuiltInFunction::RandomBoolean,
"random_float" => BuiltInFunction::RandomFloat,
"random_integer" => BuiltInFunction::RandomInteger,
"columns" => {
let expression_node = node.child(2).unwrap();
let expression = Expression::from_syntax_node(source, expression_node)?;
BuiltInFunction::Columns(expression)
}
"rows" => {
let expression_node = node.child(2).unwrap();
let expression = Expression::from_syntax_node(source, expression_node)?;
BuiltInFunction::Rows(expression)
}
"reverse" => {
let list_node = node.child(2).unwrap();
let list_expression = Expression::from_syntax_node(source, list_node)?;
let slice_range_nodes =
if let (Some(start_node), Some(end_node)) = (node.child(3), node.child(4)) {
let start = Expression::from_syntax_node(source, start_node)?;
let end = Expression::from_syntax_node(source, end_node)?;
Some((start, end))
} else {
None
};
BuiltInFunction::Reverse(list_expression, slice_range_nodes)
}
_ => {
return Err(Error::UnexpectedSyntaxNode {
expected: "built-in function",
actual: tool_node.kind(),
location: tool_node.start_position(),
relevant_source: source[tool_node.byte_range()].to_string(),
})
}
};
Ok(tool)
}
fn run(&self, source: &str, context: &mut Map) -> Result<Value> {
match self {
BuiltInFunction::Assert(expressions) => {
for expression in expressions {
let value = expression.run(source, context)?;
if value.as_boolean()? {
continue;
} else {
return Err(Error::AssertFailed);
}
}
Ok(Value::Empty)
}
BuiltInFunction::AssertEqual(expressions) => {
let mut prev_value = None;
for expression in expressions {
let value = expression.run(source, context)?;
if let Some(prev_value) = &prev_value {
if &value == prev_value {
continue;
} else {
return Err(Error::AssertEqualFailed {
expected: prev_value.clone(),
actual: value,
});
}
}
prev_value = Some(value);
}
Ok(Value::Empty)
}
BuiltInFunction::Context => Ok(Value::Map(context.clone())),
BuiltInFunction::Download(expression) => {
let value = expression.run(source, context)?;
let url = value.as_string()?;
let data = get(url)?.text()?;
Ok(Value::String(data))
}
BuiltInFunction::Length(expression) => {
let value = expression.run(source, context)?;
let length = match value {
Value::List(list) => list.items().len(),
Value::Map(map) => map.variables()?.len(),
Value::Table(table) => table.len(),
Value::String(string) => string.chars().count(),
Value::Function(_) => todo!(),
Value::Float(_) => todo!(),
Value::Integer(_) => todo!(),
Value::Boolean(_) => todo!(),
Value::Empty => todo!(),
};
Ok(Value::Integer(length as i64))
}
BuiltInFunction::Help(_expression) => {
let mut help_table =
Table::new(vec!["tool".to_string(), "description".to_string()]);
help_table.insert(vec![
Value::String("help".to_string()),
Value::String("Get info on tools.".to_string()),
])?;
Ok(Value::Table(help_table))
}
BuiltInFunction::Output(expressions) => {
for expression in expressions {
let value = expression.run(source, context)?;
println!("{value}");
}
Ok(Value::Empty)
}
BuiltInFunction::OutputError(expressions) => {
for expression in expressions {
let value = expression.run(source, context)?;
eprintln!("{value}");
}
Ok(Value::Empty)
}
BuiltInFunction::Type(expression) => {
let run_expression = expression.run(source, context);
let value_type = if let Ok(value) = run_expression {
value.value_type()
} else if let Err(Error::VariableIdentifierNotFound(_)) = run_expression {
ValueType::Empty
} else {
return run_expression;
};
Ok(Value::String(value_type.to_string()))
}
BuiltInFunction::Workdir => {
let workdir = current_dir()?.to_string_lossy().to_string();
Ok(Value::String(workdir))
}
BuiltInFunction::Append(expressions) => {
let path_value = expressions[0].run(source, context)?;
let path = path_value.as_string()?;
let data = expressions[1].run(source, context)?.to_string();
let mut file = File::options().append(true).open(path)?;
file.write(data.as_bytes())?;
Ok(Value::Empty)
}
BuiltInFunction::Metadata(expression) => {
let path_value = expression.run(source, context)?;
let path = path_value.as_string()?;
let metadata = metadata(path)?;
let file_type = if metadata.is_dir() {
"dir".to_string()
} else if metadata.is_file() {
"file".to_string()
} else if metadata.is_symlink() {
"link".to_string()
} else {
"unknown".to_string()
};
let size = metadata.len() as i64;
let created = metadata.created()?.elapsed()?.as_secs() as i64;
let modified = metadata.modified()?.elapsed()?.as_secs() as i64;
let accessed = metadata.accessed()?.elapsed()?.as_secs() as i64;
let metadata_output = Map::new();
{
let mut metadata_variables = metadata_output.variables_mut()?;
metadata_variables.insert("type".to_string(), Value::String(file_type));
metadata_variables.insert("size".to_string(), Value::Integer(size));
metadata_variables.insert("created".to_string(), Value::Integer(created));
metadata_variables.insert("modified".to_string(), Value::Integer(modified));
metadata_variables.insert("accessed".to_string(), Value::Integer(accessed));
}
Ok(Value::Map(metadata_output))
}
BuiltInFunction::Move(expressions) => {
let from_value = expressions[0].run(source, context)?;
let from = from_value.as_string()?;
let to_value = expressions[1].run(source, context)?;
let to = to_value.as_string()?;
copy(from, to)?;
remove_file(from)?;
Ok(Value::Empty)
}
BuiltInFunction::Read(expression) => {
let path_value = expression.run(source, context)?;
let path = PathBuf::from(path_value.as_string()?);
let content = if path.is_dir() {
let dir = read_dir(&path)?;
let mut contents = Vec::new();
for file in dir {
let file = file?;
let file_path = file.path().to_string_lossy().to_string();
contents.push(Value::String(file_path));
}
Value::List(List::with_items(contents))
} else {
Value::String(read_to_string(path)?)
};
Ok(content)
}
BuiltInFunction::Remove(expression) => {
let path_value = expression.run(source, context)?;
let path = PathBuf::from(path_value.as_string()?);
remove_file(path)?;
Ok(Value::Empty)
}
BuiltInFunction::Write(expressions) => {
let path_value = expressions[0].run(source, context)?;
let path = path_value.as_string()?;
let data_value = expressions[1].run(source, context)?;
let data = data_value.as_string()?;
write(path, data)?;
Ok(Value::Empty)
}
BuiltInFunction::FromJson(expression) => {
let json_value = expression.run(source, context)?;
let json = json_value.as_string()?;
let value = serde_json::from_str(json)?;
Ok(value)
}
BuiltInFunction::ToJson(expression) => {
let value = expression.run(source, context)?;
let json = serde_json::to_string(&value)?;
Ok(Value::String(json))
}
BuiltInFunction::ToString(expression) => {
let value = expression.run(source, context)?;
let string = value.to_string();
Ok(Value::String(string))
}
BuiltInFunction::ToFloat(expression) => {
let value = expression.run(source, context)?;
let float = match value {
Value::String(string) => string.parse()?,
Value::Float(float) => float,
Value::Integer(integer) => integer as f64,
_ => return Err(Error::ExpectedNumberOrString { actual: value }),
};
Ok(Value::Float(float))
}
BuiltInFunction::Bash(expressions) => {
let mut command = Command::new("bash");
for expression in expressions {
let value = expression.run(source, context)?;
let command_input = value.as_string()?;
command.arg(command_input);
}
let output = command.spawn()?.wait_with_output()?.stdout;
Ok(Value::String(String::from_utf8(output)?))
}
BuiltInFunction::Fish(expressions) => {
let mut command = Command::new("fish");
for expression in expressions {
let value = expression.run(source, context)?;
let command_input = value.as_string()?;
command.arg(command_input);
}
let output = command.spawn()?.wait_with_output()?.stdout;
Ok(Value::String(String::from_utf8(output)?))
}
BuiltInFunction::Raw(expressions) => {
let raw_command = expressions[0].run(source, context)?;
let mut command = Command::new(raw_command.as_string()?);
for expression in &expressions[1..] {
let value = expression.run(source, context)?;
let command_input = value.as_string()?;
command.arg(command_input);
}
let output = command.spawn()?.wait_with_output()?.stdout;
Ok(Value::String(String::from_utf8(output)?))
}
BuiltInFunction::Sh(expressions) => {
let mut command = Command::new("sh");
for expression in expressions {
let value = expression.run(source, context)?;
let command_input = value.as_string()?;
command.arg(command_input);
}
let output = command.spawn()?.wait_with_output()?.stdout;
Ok(Value::String(String::from_utf8(output)?))
}
BuiltInFunction::Zsh(expressions) => {
let mut command = Command::new("zsh");
for expression in expressions {
let value = expression.run(source, context)?;
let command_input = value.as_string()?;
command.arg(command_input);
}
let output = command.spawn()?.wait_with_output()?.stdout;
Ok(Value::String(String::from_utf8(output)?))
}
BuiltInFunction::Random(expressions) => {
if expressions.len() == 1 {
let value = expressions[0].run(source, context)?;
let list = value.as_list()?.items();
if list.len() == 1 {
return Ok(list.first().cloned().unwrap());
}
let range = 0..list.len();
let random_index = thread_rng().gen_range(range);
let random_value = list.get(random_index).ok_or(Error::ExpectedList {
actual: value.clone(),
})?;
return Ok(random_value.clone());
}
let range = 0..expressions.len();
let random_index = thread_rng().gen_range(range);
let random_expression = expressions.get(random_index).unwrap();
let value = random_expression.run(source, context)?;
Ok(value)
}
BuiltInFunction::RandomBoolean => Ok(Value::Boolean(random())),
BuiltInFunction::RandomFloat => Ok(Value::Float(random())),
BuiltInFunction::RandomInteger => Ok(Value::Integer(random())),
BuiltInFunction::Columns(expression) => {
let column_names = expression
.run(source, context)?
.as_table()?
.headers()
.iter()
.cloned()
.map(Value::String)
.collect();
Ok(Value::List(List::with_items(column_names)))
}
BuiltInFunction::Rows(expression) => {
let rows = expression
.run(source, context)?
.as_table()?
.rows()
.iter()
.cloned()
.map(|row| Value::List(List::with_items(row)))
.collect();
Ok(Value::List(List::with_items(rows)))
}
BuiltInFunction::Reverse(list_expression, slice_range) => {
let expression_run = list_expression.run(source, context)?;
let list = expression_run.as_list()?;
if let Some((start, end)) = slice_range {
let start = start.run(source, context)?.as_integer()? as usize;
let end = end.run(source, context)?.as_integer()? as usize;
list.items_mut()[start..end].reverse()
} else {
list.items_mut().reverse()
};
Ok(Value::List(list.clone()))
}
}
}
}

View File

@ -0,0 +1,69 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{
value_node::ValueNode, AbstractTree, BuiltInFunction, Error, Identifier, Index, Map, Result,
Value, Yield,
};
use super::{function_call::FunctionCall, logic::Logic, math::Math};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum Expression {
Value(ValueNode),
Identifier(Identifier),
Index(Box<Index>),
Math(Box<Math>),
Logic(Box<Logic>),
FunctionCall(FunctionCall),
Tool(Box<BuiltInFunction>),
Yield(Yield),
}
impl AbstractTree for Expression {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
Error::expect_syntax_node(source, "expression", node)?;
let child = if node.child(0).unwrap().is_named() {
node.child(0).unwrap()
} else {
node.child(1).unwrap()
};
let expression = match child.kind() {
"value" => Expression::Value(ValueNode::from_syntax_node(source, child)?),
"identifier" => Expression::Identifier(Identifier::from_syntax_node(source, child)?),
"index" => Expression::Index(Box::new(Index::from_syntax_node(source, child)?)),
"math" => Expression::Math(Box::new(Math::from_syntax_node(source, child)?)),
"logic" => Expression::Logic(Box::new(Logic::from_syntax_node(source, child)?)),
"function_call" => {
Expression::FunctionCall(FunctionCall::from_syntax_node(source, child)?)
}
"tool" => Expression::Tool(Box::new(BuiltInFunction::from_syntax_node(source, child)?)),
"yield" => Expression::Yield(Yield::from_syntax_node(source, child)?),
_ => {
return Err(Error::UnexpectedSyntaxNode {
expected: "value, identifier, index, math, logic, function_call or yield",
actual: child.kind(),
location: child.start_position(),
relevant_source: source[child.byte_range()].to_string(),
})
}
};
Ok(expression)
}
fn run(&self, source: &str, context: &mut Map) -> Result<Value> {
match self {
Expression::Value(value_node) => value_node.run(source, context),
Expression::Identifier(identifier) => identifier.run(source, context),
Expression::Math(math) => math.run(source, context),
Expression::Logic(logic) => logic.run(source, context),
Expression::FunctionCall(function_call) => function_call.run(source, context),
Expression::Tool(tool) => tool.run(source, context),
Expression::Index(index) => index.run(source, context),
Expression::Yield(r#yield) => r#yield.run(source, context),
}
}
}

View File

@ -0,0 +1,77 @@
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{AbstractTree, Block, Error, Expression, Identifier, List, Map, Result, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Filter {
count: Option<Expression>,
item_id: Identifier,
collection: Expression,
predicate: Block,
}
impl AbstractTree for Filter {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
let count = match node.child_by_field_name("count") {
Some(node) => Some(Expression::from_syntax_node(source, node)?),
None => None,
};
let item_id_node = node.child_by_field_name("item_id").unwrap();
let item_id = Identifier::from_syntax_node(source, item_id_node)?;
let collection_node = node.child_by_field_name("collection").unwrap();
let collection = Expression::from_syntax_node(source, collection_node)?;
let predicate_node = node.child_by_field_name("predicate").unwrap();
let predicate = Block::from_syntax_node(source, predicate_node)?;
Ok(Filter {
count,
item_id,
collection,
predicate,
})
}
fn run(&self, source: &str, context: &mut Map) -> Result<Value> {
let value = self.collection.run(source, context)?;
let values = value.as_list()?.items();
let key = self.item_id.inner();
let new_values = List::new();
let count = match &self.count {
Some(expression) => Some(expression.run(source, context)?.as_integer()? as usize),
None => None,
};
let loop_context = Map::clone_from(context)?;
values.par_iter().try_for_each(|value| {
if let Some(max) = count {
if new_values.items().len() == max {
return Ok(());
}
}
let mut iter_context = loop_context.clone();
iter_context
.variables_mut()?
.insert(key.clone(), value.clone());
let should_include = self
.predicate
.run(source, &mut iter_context)?
.as_boolean()?;
if should_include {
new_values.items_mut().push(value.clone());
}
Ok::<(), Error>(())
})?;
Ok(Value::List(new_values))
}
}

70
src/abstract_tree/find.rs Normal file
View File

@ -0,0 +1,70 @@
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{AbstractTree, Block, Error, Expression, Identifier, Map, Result, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Find {
identifier: Identifier,
expression: Expression,
item: Block,
}
impl AbstractTree for Find {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
let identifier_node = node.child(1).unwrap();
let identifier = Identifier::from_syntax_node(source, identifier_node)?;
let expression_node = node.child(3).unwrap();
let expression = Expression::from_syntax_node(source, expression_node)?;
let item_node = node.child(4).unwrap();
let item = Block::from_syntax_node(source, item_node)?;
Ok(Find {
identifier,
expression,
item,
})
}
fn run(&self, source: &str, context: &mut Map) -> Result<Value> {
let expression_run = self.expression.run(source, context)?;
let list = expression_run.as_list()?.items();
let key = self.identifier.inner();
let find_result = list.par_iter().find_map_first(|value| {
let loop_context = Map::clone_from(context).unwrap();
loop_context
.variables_mut()
.unwrap()
.insert(key.clone(), (*value).clone());
let run_result = self.item.run(source, &mut loop_context.clone());
if let Ok(run_result_value) = run_result {
if let Ok(should_return) = run_result_value.as_boolean() {
if should_return {
Some(Ok(value.clone()))
} else {
None
}
} else {
Some(Err(Error::ExpectedBoolean {
actual: value.clone(),
}))
}
} else {
Some(run_result)
}
});
if let Some(result) = find_result {
result
} else {
Ok(Value::Empty)
}
}
}

78
src/abstract_tree/for.rs Normal file
View File

@ -0,0 +1,78 @@
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{AbstractTree, Block, Error, Expression, Identifier, Map, Result, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct For {
is_async: bool,
item_id: Identifier,
collection: Expression,
block: Block,
}
impl AbstractTree for For {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
let for_node = node.child(0).unwrap();
let is_async = match for_node.kind() {
"for" => false,
"async for" => true,
_ => {
return Err(Error::UnexpectedSyntaxNode {
expected: "for or async for",
actual: for_node.kind(),
location: for_node.start_position(),
relevant_source: source[for_node.byte_range()].to_string(),
})
}
};
let identifier_node = node.child(1).unwrap();
let identifier = Identifier::from_syntax_node(source, identifier_node)?;
let expression_node = node.child(3).unwrap();
let expression = Expression::from_syntax_node(source, expression_node)?;
let item_node = node.child(4).unwrap();
let item = Block::from_syntax_node(source, item_node)?;
Ok(For {
is_async,
item_id: identifier,
collection: expression,
block: item,
})
}
fn run(&self, source: &str, context: &mut Map) -> Result<Value> {
let expression_run = self.collection.run(source, context)?;
let values = expression_run.as_list()?.items();
let key = self.item_id.inner();
if self.is_async {
values.par_iter().try_for_each(|value| {
let mut iter_context = Map::clone_from(context)?;
iter_context
.variables_mut()?
.insert(key.clone(), value.clone());
self.block.run(source, &mut iter_context).map(|_value| ())
})?;
} else {
let loop_context = Map::clone_from(context)?;
for value in values.iter() {
loop_context
.variables_mut()?
.insert(key.clone(), value.clone());
self.block.run(source, &mut loop_context.clone())?;
}
}
Ok(Value::Empty)
}
}

View File

@ -0,0 +1,77 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{AbstractTree, BuiltInFunction, Error, Map, Result, Value};
use super::{expression::Expression, identifier::Identifier};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum FunctionCall {
BuiltIn(Box<BuiltInFunction>),
ContextDefined {
name: Identifier,
arguments: Vec<Expression>,
},
}
impl AbstractTree for FunctionCall {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
debug_assert_eq!("function_call", node.kind());
let function_node = node.child(1).unwrap();
let mut arguments = Vec::new();
for index in 2..node.child_count() - 1 {
let node = node.child(index).unwrap();
if node.is_named() {
let expression = Expression::from_syntax_node(source, node)?;
arguments.push(expression);
}
}
let function_call = if function_node.kind() == "built_in_function" {
let function = BuiltInFunction::from_syntax_node(source, function_node)?;
FunctionCall::BuiltIn(Box::new(function))
} else {
let identifier = Identifier::from_syntax_node(source, function_node)?;
FunctionCall::ContextDefined {
name: identifier,
arguments,
}
};
Ok(function_call)
}
fn run(&self, source: &str, context: &mut Map) -> Result<Value> {
let (name, arguments) = match self {
FunctionCall::BuiltIn(function) => return function.run(source, context),
FunctionCall::ContextDefined { name, arguments } => (name, arguments),
};
let definition = if let Some(value) = context.variables()?.get(name.inner()) {
value.as_function().cloned()?
} else {
return Err(Error::FunctionIdentifierNotFound(name.clone()));
};
let mut function_context = Map::clone_from(context)?;
if let Some(parameters) = definition.identifiers() {
let parameter_expression_pairs = parameters.iter().zip(arguments.iter());
let mut variables = function_context.variables_mut()?;
for (identifier, expression) in parameter_expression_pairs {
let key = identifier.clone().take_inner();
let value = expression.run(source, context)?;
variables.insert(key, value);
}
}
definition.body().run(source, &mut function_context)
}
}

View File

@ -0,0 +1,39 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{AbstractTree, Error, Map, Result, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Identifier(String);
impl Identifier {
pub fn new(inner: String) -> Self {
Identifier(inner)
}
pub fn take_inner(self) -> String {
self.0
}
pub fn inner(&self) -> &String {
&self.0
}
}
impl AbstractTree for Identifier {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
Error::expect_syntax_node(source, "identifier", node)?;
let identifier = &source[node.byte_range()];
Ok(Identifier(identifier.to_string()))
}
fn run(&self, _source: &str, context: &mut Map) -> Result<Value> {
if let Some(value) = context.variables()?.get(&self.0) {
Ok(value.clone())
} else {
Err(Error::VariableIdentifierNotFound(self.inner().clone()))
}
}
}

View File

@ -0,0 +1,79 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{AbstractTree, Error, Identifier, Map, Result, Statement, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct IdentifierAssignment {
identifier: Identifier,
operator: AssignmentOperator,
statement: Statement,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum AssignmentOperator {
Equal,
PlusEqual,
MinusEqual,
}
impl AbstractTree for IdentifierAssignment {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
let identifier_node = node.child(0).unwrap();
let identifier = Identifier::from_syntax_node(source, identifier_node)?;
let operator_node = node.child(1).unwrap().child(0).unwrap();
let operator = match operator_node.kind() {
"=" => AssignmentOperator::Equal,
"+=" => AssignmentOperator::PlusEqual,
"-=" => AssignmentOperator::MinusEqual,
_ => {
return Err(Error::UnexpectedSyntaxNode {
expected: "=, += or -=",
actual: operator_node.kind(),
location: operator_node.start_position(),
relevant_source: source[operator_node.byte_range()].to_string(),
})
}
};
let statement_node = node.child(2).unwrap();
let statement = Statement::from_syntax_node(source, statement_node)?;
Ok(IdentifierAssignment {
identifier,
operator,
statement,
})
}
fn run(&self, source: &str, context: &mut Map) -> Result<Value> {
let key = self.identifier.inner().clone();
let value = self.statement.run(source, context)?;
let mut context = context.variables_mut();
let new_value = match self.operator {
AssignmentOperator::PlusEqual => {
if let Some(mut previous_value) = context.get(&key).cloned() {
previous_value += value;
previous_value
} else {
Value::Empty
}
}
AssignmentOperator::MinusEqual => {
if let Some(mut previous_value) = context.get(&key).cloned() {
previous_value -= value;
previous_value
} else {
Value::Empty
}
}
AssignmentOperator::Equal => value,
};
context.insert(key, new_value);
Ok(Value::Empty)
}
}

View File

@ -0,0 +1,83 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{AbstractTree, Block, Expression, Map, Result, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct IfElse {
if_expression: Expression,
if_block: Block,
else_if_expressions: Vec<Expression>,
else_if_blocks: Vec<Block>,
else_block: Option<Block>,
}
impl AbstractTree for IfElse {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
let if_expression_node = node.child(0).unwrap().child(1).unwrap();
let if_expression = Expression::from_syntax_node(source, if_expression_node)?;
let if_block_node = node.child(0).unwrap().child(2).unwrap();
let if_block = Block::from_syntax_node(source, if_block_node)?;
let child_count = node.child_count();
let mut else_if_expressions = Vec::new();
let mut else_if_blocks = Vec::new();
let mut else_block = None;
for index in 1..child_count {
let child = node.child(index).unwrap();
if child.kind() == "else_if" {
let expression_node = child.child(1).unwrap();
let expression = Expression::from_syntax_node(source, expression_node)?;
else_if_expressions.push(expression);
let block_node = child.child(2).unwrap();
let block = Block::from_syntax_node(source, block_node)?;
else_if_blocks.push(block);
}
if child.kind() == "else" {
let else_node = child.child(1).unwrap();
else_block = Some(Block::from_syntax_node(source, else_node)?);
}
}
Ok(IfElse {
if_expression,
if_block,
else_if_expressions,
else_if_blocks,
else_block,
})
}
fn run(&self, source: &str, context: &mut Map) -> Result<Value> {
let if_boolean = self.if_expression.run(source, context)?.as_boolean()?;
if if_boolean {
self.if_block.run(source, context)
} else {
let expressions = &self.else_if_expressions;
for (index, expression) in expressions.iter().enumerate() {
let if_boolean = expression.run(source, context)?.as_boolean()?;
if if_boolean {
let block = self.else_if_blocks.get(index).unwrap();
return block.run(source, context);
}
}
if let Some(block) = &self.else_block {
block.run(source, context)
} else {
Ok(Value::Empty)
}
}
}
}

103
src/abstract_tree/index.rs Normal file
View File

@ -0,0 +1,103 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{AbstractTree, Error, Expression, List, Map, Result, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Index {
pub collection: Expression,
pub index: Expression,
pub index_end: Option<Expression>,
}
impl AbstractTree for Index {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
let collection_node = node.child(0).unwrap();
let collection = Expression::from_syntax_node(source, collection_node)?;
let index_node = node.child(2).unwrap();
let index = Expression::from_syntax_node(source, index_node)?;
let index_end_node = node.child(4);
let index_end = if let Some(index_end_node) = index_end_node {
Some(Expression::from_syntax_node(source, index_end_node)?)
} else {
None
};
Ok(Index {
collection,
index,
index_end,
})
}
fn run(&self, source: &str, context: &mut Map) -> Result<Value> {
let collection = self.collection.run(source, context)?;
match collection {
Value::List(list) => {
let index = self.index.run(source, context)?.as_integer()? as usize;
let item = if let Some(index_end) = &self.index_end {
let index_end = index_end.run(source, context)?.as_integer()? as usize;
let sublist = list.items()[index..=index_end].to_vec();
Value::List(List::with_items(sublist))
} else {
list.items().get(index).cloned().unwrap_or_default()
};
Ok(item)
}
Value::Map(map) => {
let value = if let Expression::Identifier(identifier) = &self.index {
let key = identifier.inner();
map.variables()?.get(key).cloned().unwrap_or(Value::Empty)
} else {
let value = self.index.run(source, context)?;
let key = value.as_string()?;
map.variables()?.get(key).cloned().unwrap_or(Value::Empty)
};
Ok(value)
}
Value::String(string) => {
let index = self.index.run(source, context)?.as_integer()? as usize;
let item = string.chars().nth(index).unwrap_or_default();
Ok(Value::String(item.to_string()))
}
_ => Err(Error::ExpectedCollection { actual: collection }),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::evaluate;
#[test]
fn evaluate_list_index() {
let test = evaluate("x = [1 [2] 3] x:1:0").unwrap();
assert_eq!(Value::Integer(2), test);
}
#[test]
fn evaluate_map_index() {
let test = evaluate("x = {y = {z = 2}} x:y:z").unwrap();
assert_eq!(Value::Integer(2), test);
}
#[test]
fn evaluate_complex_index() {
let test = evaluate("x = [1 2 3]; y = || => {0}; x:((y));").unwrap();
assert_eq!(Value::Integer(1), test);
}
}

View File

@ -0,0 +1,93 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{AbstractTree, Error, Index, Map, Result, Statement, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct IndexAssignment {
index: Index,
operator: AssignmentOperator,
statement: Statement,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum AssignmentOperator {
Equal,
PlusEqual,
MinusEqual,
}
impl AbstractTree for IndexAssignment {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
Error::expect_syntax_node(source, "index_assignment", node)?;
let index_node = node.child(0).unwrap();
let index = Index::from_syntax_node(source, index_node)?;
let operator_node = node.child(1).unwrap().child(0).unwrap();
let operator = match operator_node.kind() {
"=" => AssignmentOperator::Equal,
"+=" => AssignmentOperator::PlusEqual,
"-=" => AssignmentOperator::MinusEqual,
_ => {
return Err(Error::UnexpectedSyntaxNode {
expected: "=, += or -=",
actual: operator_node.kind(),
location: operator_node.start_position(),
relevant_source: source[operator_node.byte_range()].to_string(),
})
}
};
let statement_node = node.child(2).unwrap();
let statement = Statement::from_syntax_node(source, statement_node)?;
Ok(IndexAssignment {
index,
operator,
statement,
})
}
fn run(&self, source: &str, context: &mut Map) -> Result<Value> {
let index_collection = self.index.collection.run(source, context)?;
let index_context = index_collection.as_map().unwrap_or(&context);
let index_key = if let crate::Expression::Identifier(identifier) = &self.index.index {
identifier.inner()
} else {
return Err(Error::VariableIdentifierNotFound(
self.index.index.run(source, context)?.to_string(),
));
};
let value = self.statement.run(source, &mut context.clone())?;
let new_value = match self.operator {
AssignmentOperator::PlusEqual => {
if let Some(mut previous_value) = index_context.variables()?.get(index_key).cloned()
{
previous_value += value;
previous_value
} else {
Value::Empty
}
}
AssignmentOperator::MinusEqual => {
if let Some(mut previous_value) = index_context.variables()?.get(index_key).cloned()
{
previous_value -= value;
previous_value
} else {
Value::Empty
}
}
AssignmentOperator::Equal => value,
};
index_context
.variables_mut()?
.insert(index_key.clone(), new_value);
Ok(Value::Empty)
}
}

View File

@ -0,0 +1,44 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{AbstractTree, Expression, Identifier, Map, Result, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Insert {
identifier: Identifier,
expression: Expression,
}
impl AbstractTree for Insert {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
let identifier_node = node.child(2).unwrap();
let identifier = Identifier::from_syntax_node(source, identifier_node)?;
let expression_node = node.child(3).unwrap();
let expression = Expression::from_syntax_node(source, expression_node)?;
Ok(Insert {
identifier,
expression,
})
}
fn run(&self, source: &str, context: &mut Map) -> Result<Value> {
let table_name = self.identifier.inner().clone();
let mut table = self.identifier.run(source, context)?.as_table()?.clone();
let new_rows = self.expression.run(source, context)?.into_inner_list()?;
let values = new_rows.items();
table.reserve(values.len());
for row in values.iter() {
let row_values = row.clone().into_inner_list()?;
table.insert(row_values.items().clone())?;
}
context
.variables_mut()?
.insert(table_name, Value::Table(table));
Ok(Value::Empty)
}
}

View File

@ -0,0 +1,88 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{AbstractTree, Error, Expression, Map, Result, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Logic {
left: Expression,
operator: LogicOperator,
right: Expression,
}
impl AbstractTree for Logic {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
let left_node = node.child(0).unwrap();
let left = Expression::from_syntax_node(source, left_node)?;
let operator_node = node.child(1).unwrap().child(0).unwrap();
let operator = match operator_node.kind() {
"==" => LogicOperator::Equal,
"!=" => LogicOperator::NotEqual,
"&&" => LogicOperator::And,
"||" => LogicOperator::Or,
">" => LogicOperator::Greater,
"<" => LogicOperator::Less,
">=" => LogicOperator::GreaterOrEqual,
"<=" => LogicOperator::LessOrEqaul,
_ => {
return Err(Error::UnexpectedSyntaxNode {
expected: "==, !=, &&, ||, >, <, >= or <=",
actual: operator_node.kind(),
location: operator_node.start_position(),
relevant_source: source[operator_node.byte_range()].to_string(),
})
}
};
let right_node = node.child(2).unwrap();
let right = Expression::from_syntax_node(source, right_node)?;
Ok(Logic {
left,
operator,
right,
})
}
fn run(&self, source: &str, context: &mut Map) -> Result<Value> {
let left = self.left.run(source, context)?;
let right = self.right.run(source, context)?;
let result = match self.operator {
LogicOperator::Equal => {
if let (Ok(left_num), Ok(right_num)) = (left.as_number(), right.as_number()) {
left_num == right_num
} else {
left == right
}
}
LogicOperator::NotEqual => {
if let (Ok(left_num), Ok(right_num)) = (left.as_number(), right.as_number()) {
left_num != right_num
} else {
left != right
}
}
LogicOperator::And => left.as_boolean()? && right.as_boolean()?,
LogicOperator::Or => left.as_boolean()? || right.as_boolean()?,
LogicOperator::Greater => left > right,
LogicOperator::Less => left < right,
LogicOperator::GreaterOrEqual => left >= right,
LogicOperator::LessOrEqaul => left <= right,
};
Ok(Value::Boolean(result))
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum LogicOperator {
Equal,
NotEqual,
And,
Or,
Greater,
Less,
GreaterOrEqual,
LessOrEqaul,
}

View File

@ -0,0 +1,22 @@
//! Pattern matching.
//!
//! Note that this module is called "match" but is escaped as "r#match" because
//! "match" is a keyword in Rust.
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{AbstractTree, Map, Result, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Match {}
impl AbstractTree for Match {
fn from_syntax_node(_source: &str, _node: Node) -> Result<Self> {
todo!()
}
fn run(&self, _source: &str, _context: &mut Map) -> Result<Value> {
todo!()
}
}

67
src/abstract_tree/math.rs Normal file
View File

@ -0,0 +1,67 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{AbstractTree, Error, Expression, Map, Result, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Math {
left: Expression,
operator: MathOperator,
right: Expression,
}
impl AbstractTree for Math {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
let left_node = node.child(0).unwrap();
let left = Expression::from_syntax_node(source, left_node)?;
let operator_node = node.child(1).unwrap().child(0).unwrap();
let operator = match operator_node.kind() {
"+" => MathOperator::Add,
"-" => MathOperator::Subtract,
"*" => MathOperator::Multiply,
"/" => MathOperator::Divide,
"%" => MathOperator::Modulo,
_ => {
return Err(Error::UnexpectedSyntaxNode {
expected: "+, -, *, / or %",
actual: operator_node.kind(),
location: operator_node.start_position(),
relevant_source: source[operator_node.byte_range()].to_string(),
})
}
};
let right_node = node.child(2).unwrap();
let right = Expression::from_syntax_node(source, right_node)?;
Ok(Math {
left,
operator,
right,
})
}
fn run(&self, source: &str, context: &mut Map) -> Result<Value> {
let left = self.left.run(source, context)?;
let right = self.right.run(source, context)?;
let value = match self.operator {
MathOperator::Add => left + right,
MathOperator::Subtract => left - right,
MathOperator::Multiply => left * right,
MathOperator::Divide => left / right,
MathOperator::Modulo => left % right,
}?;
Ok(value)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum MathOperator {
Add,
Subtract,
Multiply,
Divide,
Modulo,
}

60
src/abstract_tree/mod.rs Normal file
View File

@ -0,0 +1,60 @@
//! Abstract, executable representations of corresponding items found in Dust
//! source code. The types that implement [AbstractTree] are inteded to be
//! created by an [Evaluator].
//!
//! When adding new lanugage features, first extend the grammar to recognize new
//! syntax nodes. Then add a new AbstractTree type using the existing types as
//! examples.
pub mod assignment;
pub mod block;
pub mod built_in_function;
pub mod expression;
pub mod filter;
pub mod find;
pub mod r#for;
pub mod function_call;
pub mod identifier;
pub mod if_else;
pub mod index;
pub mod index_assignment;
pub mod insert;
pub mod logic;
pub mod r#match;
pub mod math;
pub mod remove;
pub mod select;
pub mod statement;
pub mod transform;
pub mod value_node;
pub mod r#while;
pub mod r#yield;
pub use {
assignment::*, block::*, built_in_function::*, expression::*, filter::*, find::*,
function_call::*, identifier::*, if_else::*, index::*, index_assignment::*, insert::*,
logic::*, math::*, r#for::*, r#match::*, r#while::*, r#yield::*, remove::*, select::*,
statement::*, transform::*, value_node::*,
};
use tree_sitter::Node;
use crate::{Map, Result, Value};
/// This trait is implemented by the Evaluator's internal types to form an
/// executable tree that resolves to a single value.
pub trait AbstractTree: Sized {
/// Interpret the syntax tree at the given node and return the abstraction.
///
/// This function is used to convert nodes in the Tree Sitter concrete
/// syntax tree into executable nodes in an abstract tree. This function is
/// where the tree should be traversed by accessing sibling and child nodes.
/// Each node in the CST should be traversed only once.
///
/// If necessary, the source code can be accessed directly by getting the
/// node's byte range.
fn from_syntax_node(source: &str, node: Node) -> Result<Self>;
/// Execute dust code by traversing the tree
fn run(&self, source: &str, context: &mut Map) -> Result<Value>;
}

View File

@ -0,0 +1,75 @@
use std::sync::RwLock;
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{AbstractTree, Block, Error, Expression, Identifier, Map, Result, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Remove {
item_id: Identifier,
collection: Expression,
predicate: Block,
}
impl AbstractTree for Remove {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
let identifier_node = node.child(1).unwrap();
let item_id = Identifier::from_syntax_node(source, identifier_node)?;
let expression_node = node.child(3).unwrap();
let collection = Expression::from_syntax_node(source, expression_node)?;
let block_node = node.child(4).unwrap();
let predicate = Block::from_syntax_node(source, block_node)?;
Ok(Remove {
item_id,
collection,
predicate,
})
}
fn run(&self, source: &str, context: &mut Map) -> Result<Value> {
let value = self.collection.run(source, context)?;
let values = value.as_list()?;
let key = self.item_id.inner();
let should_remove_index = RwLock::new(None);
values
.items()
.par_iter()
.enumerate()
.try_for_each(|(index, value)| {
if should_remove_index.read()?.is_some() {
return Ok(());
}
let iter_context = Map::clone_from(context)?;
iter_context
.variables_mut()?
.insert(key.clone(), value.clone());
let should_remove = self
.predicate
.run(source, &mut iter_context.clone())?
.as_boolean()?;
if should_remove {
let _ = should_remove_index.write()?.insert(index);
}
Ok::<(), Error>(())
})?;
let index = should_remove_index.read()?;
if let Some(index) = *index {
Ok(values.items_mut().remove(index))
} else {
Ok(Value::Empty)
}
}
}

108
src/abstract_tree/select.rs Normal file
View File

@ -0,0 +1,108 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{AbstractTree, Block, Expression, Identifier, Map, Result, Table, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Select {
identifiers: Vec<Identifier>,
expression: Expression,
predicate: Option<Block>,
}
impl AbstractTree for Select {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
let child_count = node.child_count();
let mut identifiers = Vec::new();
let identifier_list = node.child(1).unwrap();
for index in 1..identifier_list.child_count() - 1 {
let node = identifier_list.child(index).unwrap();
if node.is_named() {
let identifier = Identifier::from_syntax_node(source, node)?;
identifiers.push(identifier);
}
}
let expression_node = node.child(3).unwrap();
let expression = Expression::from_syntax_node(source, expression_node)?;
let final_node = node.child(child_count - 1).unwrap();
let predicate = if final_node.kind() == "block" {
Some(Block::from_syntax_node(source, final_node)?)
} else {
None
};
Ok(Select {
identifiers,
expression,
predicate,
})
}
fn run(&self, source: &str, context: &mut Map) -> Result<Value> {
let value = self.expression.run(source, context)?;
let old_table = value.as_table()?;
let column_names = if !self.identifiers.is_empty() {
self.identifiers
.iter()
.cloned()
.map(|identifier| identifier.take_inner())
.collect()
} else {
old_table.headers().clone()
};
let mut new_table = Table::new(column_names.to_vec());
for row in old_table.rows() {
let mut new_row = Vec::new();
let row_context = Map::new();
for (i, value) in row.iter().enumerate() {
let column_name = old_table.headers().get(i).unwrap();
row_context
.variables_mut()?
.insert(column_name.clone(), value.clone());
let new_table_column_index =
new_table
.headers()
.iter()
.enumerate()
.find_map(|(index, new_column_name)| {
if new_column_name == column_name {
Some(index)
} else {
None
}
});
if let Some(index) = new_table_column_index {
while new_row.len() < index + 1 {
new_row.push(Value::Empty);
}
new_row[index] = value.clone();
}
}
if let Some(where_clause) = &self.predicate {
let should_include = where_clause
.run(source, &mut row_context.clone())?
.as_boolean()?;
if should_include {
new_table.insert(new_row)?;
}
} else {
new_table.insert(new_row)?;
}
}
Ok(Value::Table(new_table))
}
}

View File

@ -0,0 +1,107 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{
AbstractTree, Assignment, Block, Error, Expression, Filter, Find, For, IfElse, IndexAssignment,
Insert, Map, Match, Remove, Result, Select, Transform, Value, While,
};
/// Abstract representation of a statement.
///
/// A statement may evaluate to an Empty value when run. If a Statement is an
/// Expression, it will always return a non-empty value when run.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum Statement {
Assignment(Box<Assignment>),
Expression(Expression),
IfElse(Box<IfElse>),
Match(Match),
While(Box<While>),
Block(Box<Block>),
For(Box<For>),
Transform(Box<Transform>),
Filter(Box<Filter>),
Find(Box<Find>),
Remove(Box<Remove>),
Select(Box<Select>),
Insert(Box<Insert>),
IndexAssignment(Box<IndexAssignment>),
}
impl AbstractTree for Statement {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
Error::expect_syntax_node(source, "statement", node)?;
let child = node.child(0).unwrap();
match child.kind() {
"assignment" => Ok(Statement::Assignment(Box::new(
Assignment::from_syntax_node(source, child)?,
))),
"expression" => Ok(Self::Expression(Expression::from_syntax_node(
source, child,
)?)),
"if_else" => Ok(Statement::IfElse(Box::new(IfElse::from_syntax_node(
source, child,
)?))),
"tool" => Ok(Statement::IfElse(Box::new(IfElse::from_syntax_node(
source, child,
)?))),
"while" => Ok(Statement::While(Box::new(While::from_syntax_node(
source, child,
)?))),
"block" => Ok(Statement::Block(Box::new(Block::from_syntax_node(
source, child,
)?))),
"for" => Ok(Statement::For(Box::new(For::from_syntax_node(
source, child,
)?))),
"transform" => Ok(Statement::Transform(Box::new(Transform::from_syntax_node(
source, child,
)?))),
"filter" => Ok(Statement::Filter(Box::new(Filter::from_syntax_node(
source, child,
)?))),
"find" => Ok(Statement::Find(Box::new(Find::from_syntax_node(
source, child,
)?))),
"remove" => Ok(Statement::Remove(Box::new(Remove::from_syntax_node(
source, child,
)?))),
"select" => Ok(Statement::Select(Box::new(Select::from_syntax_node(
source, child,
)?))),
"insert" => Ok(Statement::Insert(Box::new(Insert::from_syntax_node(
source, child,
)?))),
"index_assignment" => Ok(Statement::IndexAssignment(Box::new(IndexAssignment::from_syntax_node(
source, child,
)?))),
_ => Err(Error::UnexpectedSyntaxNode {
expected: "assignment, expression, if...else, while, for, transform, filter, tool, async, find, remove, select, insert, index_assignment or yield",
actual: child.kind(),
location: child.start_position(),
relevant_source: source[child.byte_range()].to_string(),
}),
}
}
fn run(&self, source: &str, context: &mut Map) -> Result<Value> {
match self {
Statement::Assignment(assignment) => assignment.run(source, context),
Statement::Expression(expression) => expression.run(source, context),
Statement::IfElse(if_else) => if_else.run(source, context),
Statement::Match(r#match) => r#match.run(source, context),
Statement::While(r#while) => r#while.run(source, context),
Statement::Block(block) => block.run(source, context),
Statement::For(r#for) => r#for.run(source, context),
Statement::Transform(transform) => transform.run(source, context),
Statement::Filter(filter) => filter.run(source, context),
Statement::Find(find) => find.run(source, context),
Statement::Remove(remove) => remove.run(source, context),
Statement::Select(select) => select.run(source, context),
Statement::Insert(insert) => insert.run(source, context),
Statement::IndexAssignment(index_assignment) => index_assignment.run(source, context),
}
}
}

View File

@ -0,0 +1,55 @@
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{AbstractTree, Block, Expression, Identifier, List, Map, Result, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Transform {
identifier: Identifier,
expression: Expression,
item: Block,
}
impl AbstractTree for Transform {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
let identifier_node = node.child(1).unwrap();
let identifier = Identifier::from_syntax_node(source, identifier_node)?;
let expression_node = node.child(3).unwrap();
let expression = Expression::from_syntax_node(source, expression_node)?;
let item_node = node.child(4).unwrap();
let item = Block::from_syntax_node(source, item_node)?;
Ok(Transform {
identifier,
expression,
item,
})
}
fn run(&self, source: &str, context: &mut Map) -> Result<Value> {
let expression_run = self.expression.run(source, context)?;
let values = expression_run.as_list()?.items();
let key = self.identifier.inner();
let new_values = values
.par_iter()
.map(|value| {
let iter_context = Map::clone_from(context).unwrap();
iter_context
.variables_mut()
.unwrap()
.insert(key.clone(), value.clone());
self.item
.run(source, &mut iter_context.clone())
.unwrap_or_default()
})
.filter(|value| !value.is_empty())
.collect();
Ok(Value::List(List::with_items(new_values)))
}
}

34
src/abstract_tree/use.rs Normal file
View File

@ -0,0 +1,34 @@
use std::fs::read_to_string;
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{evaluate_with_context, AbstractTree, Result, Value, ValueNode, VariableMap};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Use {
path: ValueNode,
}
impl AbstractTree for Use {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
let path_node = node.child(1).unwrap();
let value_node = ValueNode::from_syntax_node(source, path_node)?;
Ok(Use { path: value_node })
}
fn run(&self, source: &str, context: &mut VariableMap) -> Result<Value> {
let run_node = self.path.run(source, context)?;
let path = run_node.as_string()?;
let file_contents = read_to_string(path)?;
let mut temp_context = VariableMap::new();
let eval_result = evaluate_with_context(&file_contents, &mut temp_context)?;
while let Some((key, value)) = temp_context.inner_mut().pop_first() {
context.set_value(key, value)?;
}
Ok(eval_result)
}
}

View File

@ -0,0 +1,214 @@
use std::{collections::BTreeMap, ops::Range};
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{
AbstractTree, Block, Error, Expression, Function, Identifier, List, Map, Result, Statement,
Table, Value, ValueType,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct ValueNode {
value_type: ValueType,
start_byte: usize,
end_byte: usize,
}
impl ValueNode {
pub fn new(value_type: ValueType, start_byte: usize, end_byte: usize) -> Self {
Self {
value_type,
start_byte,
end_byte,
}
}
pub fn byte_range(&self) -> Range<usize> {
self.start_byte..self.end_byte
}
}
impl AbstractTree for ValueNode {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
debug_assert_eq!("value", node.kind());
let child = node.child(0).unwrap();
let value_type = match child.kind() {
"integer" => ValueType::Integer,
"float" => ValueType::Float,
"string" => ValueType::String,
"boolean" => ValueType::Boolean,
"empty" => ValueType::Empty,
"list" => {
let mut expressions = Vec::new();
for index in 1..child.child_count() - 1 {
let current_node = child.child(index).unwrap();
if current_node.is_named() {
let expression = Expression::from_syntax_node(source, current_node)?;
expressions.push(expression);
}
}
ValueType::List(expressions)
}
"table" => {
let identifier_list_node = child.child(1).unwrap();
let identifier_count = identifier_list_node.child_count();
let mut column_names = Vec::with_capacity(identifier_count);
for index in 0..identifier_count {
let identifier_node = identifier_list_node.child(index).unwrap();
if identifier_node.is_named() {
let identifier = Identifier::from_syntax_node(source, identifier_node)?;
column_names.push(identifier)
}
}
let expression_node = child.child(2).unwrap();
let expression = Expression::from_syntax_node(source, expression_node)?;
ValueType::Table {
column_names,
rows: Box::new(expression),
}
}
"map" => {
let mut child_nodes = BTreeMap::new();
let mut current_key = "".to_string();
for index in 0..child.child_count() - 1 {
let child_syntax_node = child.child(index).unwrap();
if child_syntax_node.kind() == "identifier" {
current_key =
Identifier::from_syntax_node(source, child_syntax_node)?.take_inner();
}
if child_syntax_node.kind() == "statement" {
let key = current_key.clone();
let statement = Statement::from_syntax_node(source, child_syntax_node)?;
child_nodes.insert(key, statement);
}
}
ValueType::Map(child_nodes)
}
"function" => {
let parameters_node = child.child_by_field_name("parameters");
let parameters = if let Some(node) = parameters_node {
let mut parameter_list = Vec::new();
for index in 0..node.child_count() {
let child_node = node.child(index).unwrap();
if child_node.is_named() {
let parameter = Identifier::from_syntax_node(source, child_node)?;
parameter_list.push(parameter);
}
}
Some(parameter_list)
} else {
None
};
let body_node = child.child_by_field_name("body").unwrap();
let body = Block::from_syntax_node(source, body_node)?;
ValueType::Function(Function::new(parameters, body))
}
_ => {
return Err(Error::UnexpectedSyntaxNode {
expected:
"string, integer, float, boolean, list, table, map, function or empty",
actual: child.kind(),
location: child.start_position(),
relevant_source: source[child.byte_range()].to_string(),
})
}
};
Ok(ValueNode {
value_type,
start_byte: child.start_byte(),
end_byte: child.end_byte(),
})
}
fn run(&self, source: &str, context: &mut Map) -> Result<Value> {
let value_source = &source[self.byte_range()];
let value = match &self.value_type {
ValueType::Any => todo!(),
ValueType::String => {
let without_quotes = &value_source[1..value_source.len() - 1];
Value::String(without_quotes.to_string())
}
ValueType::Float => Value::Float(value_source.parse().unwrap()),
ValueType::Integer => Value::Integer(value_source.parse().unwrap()),
ValueType::Boolean => Value::Boolean(value_source.parse().unwrap()),
ValueType::List(nodes) => {
let mut values = Vec::with_capacity(nodes.len());
for node in nodes {
let value = node.run(source, context)?;
values.push(value);
}
Value::List(List::with_items(values))
}
ValueType::Empty => Value::Empty,
ValueType::Map(nodes) => {
let map = Map::new();
{
let mut variables = map.variables_mut()?;
for (key, node) in nodes {
let value = node.run(source, context)?;
variables.insert(key.clone(), value);
}
}
Value::Map(map)
}
ValueType::Table {
column_names,
rows: row_expression,
} => {
let mut headers = Vec::with_capacity(column_names.len());
let mut rows = Vec::new();
for identifier in column_names {
let name = identifier.inner().clone();
headers.push(name)
}
let _row_values = row_expression.run(source, context)?;
let row_values = _row_values.as_list()?.items();
for value in row_values.iter() {
let row = value.as_list()?.items().clone();
rows.push(row)
}
let table = Table::from_raw_parts(headers, rows);
Value::Table(table)
}
ValueType::Function(function) => Value::Function(function.clone()),
};
Ok(value)
}
}

View File

@ -0,0 +1,42 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{AbstractTree, Block, Expression, Map, Result, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct While {
expression: Expression,
block: Block,
}
impl AbstractTree for While {
fn from_syntax_node(source: &str, node: Node) -> crate::Result<Self> {
debug_assert_eq!("while", node.kind());
let expression_node = node.child(1).unwrap();
let expression = Expression::from_syntax_node(source, expression_node)?;
let block_node = node.child(2).unwrap();
let block = Block::from_syntax_node(source, block_node)?;
Ok(While { expression, block })
}
fn run(&self, source: &str, context: &mut Map) -> Result<Value> {
while self.expression.run(source, context)?.as_boolean()? {
self.block.run(source, context)?;
}
Ok(Value::Empty)
}
}
#[cfg(test)]
mod tests {
use crate::evaluate;
#[test]
fn evalualate_while_loop() {
assert_eq!(evaluate("while false { 'foo' }"), Ok(crate::Value::Empty))
}
}

View File

@ -0,0 +1,50 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{AbstractTree, BuiltInFunction, Expression, FunctionCall, Identifier, Result, Value};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Yield {
call: FunctionCall,
}
impl AbstractTree for Yield {
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
let input_node = node.child(0).unwrap();
let input = Expression::from_syntax_node(source, input_node)?;
let function_node = node.child(3).unwrap();
let mut arguments = Vec::new();
arguments.push(input);
for index in 4..node.child_count() - 1 {
let node = node.child(index).unwrap();
if node.is_named() {
let expression = Expression::from_syntax_node(source, node)?;
arguments.push(expression);
}
}
let call = if function_node.kind() == "built_in_function" {
let function = BuiltInFunction::from_syntax_node(source, function_node)?;
FunctionCall::BuiltIn(Box::new(function))
} else {
let identifier = Identifier::from_syntax_node(source, function_node)?;
FunctionCall::ContextDefined {
name: identifier,
arguments,
}
};
Ok(Yield { call })
}
fn run(&self, source: &str, context: &mut crate::Map) -> Result<Value> {
self.call.run(source, context)
}
}

346
src/error.rs Normal file
View File

@ -0,0 +1,346 @@
//! Error and Result types.
//!
//! To deal with errors from dependencies, either create a new error variant
//! or use the ToolFailure variant if the error can only occur inside a tool.
use tree_sitter::Node;
use crate::{value::Value, Identifier};
use std::{fmt, io, num::ParseFloatError, string::FromUtf8Error, sync::PoisonError, time};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Clone, PartialEq)]
pub enum Error {
UnexpectedSyntaxNode {
expected: &'static str,
actual: &'static str,
location: tree_sitter::Point,
relevant_source: String,
},
/// The 'assert' macro did not resolve successfully.
AssertEqualFailed {
expected: Value,
actual: Value,
},
/// The 'assert' macro did not resolve successfully.
AssertFailed,
/// A row was inserted to a table with the wrong amount of values.
WrongColumnAmount {
expected: usize,
actual: usize,
},
/// An operator was called with the wrong amount of arguments.
ExpectedOperatorArgumentAmount {
expected: usize,
actual: usize,
},
/// A function was called with the wrong amount of arguments.
ExpectedToolArgumentAmount {
tool_name: &'static str,
expected: usize,
actual: usize,
},
/// A function was called with the wrong amount of arguments.
ExpectedAtLeastFunctionArgumentAmount {
identifier: String,
minimum: usize,
actual: usize,
},
ExpectedString {
actual: Value,
},
ExpectedInt {
actual: Value,
},
ExpectedFloat {
actual: Value,
},
/// An integer, floating point or value was expected.
ExpectedNumber {
actual: Value,
},
/// An integer, floating point or string value was expected.
ExpectedNumberOrString {
actual: Value,
},
ExpectedBoolean {
actual: Value,
},
ExpectedList {
actual: Value,
},
ExpectedMinLengthList {
minimum_len: usize,
actual_len: usize,
},
ExpectedFixedLenList {
expected_len: usize,
actual: Value,
},
ExpectedEmpty {
actual: Value,
},
ExpectedMap {
actual: Value,
},
ExpectedTable {
actual: Value,
},
ExpectedFunction {
actual: Value,
},
/// A string, list, map or table value was expected.
ExpectedCollection {
actual: Value,
},
/// A `VariableIdentifier` operation did not find its value in the context.
VariableIdentifierNotFound(String),
/// A `FunctionIdentifier` operation did not find its value in the context.
FunctionIdentifierNotFound(Identifier),
/// The function failed due to an external error.
ToolFailure(String),
/// A custom error explained by its message.
CustomMessage(String),
}
impl Error {
pub fn expect_syntax_node(source: &str, expected: &'static str, actual: Node) -> Result<()> {
if expected == actual.kind() {
Ok(())
} else {
Err(Error::UnexpectedSyntaxNode {
expected,
actual: actual.kind(),
location: actual.start_position(),
relevant_source: source[actual.byte_range()].to_string(),
})
}
}
pub fn expect_tool_argument_amount(
tool_name: &'static str,
expected: usize,
actual: usize,
) -> Result<()> {
if expected == actual {
Ok(())
} else {
Err(Error::ExpectedToolArgumentAmount {
tool_name,
expected,
actual,
})
}
}
}
impl<T> From<PoisonError<T>> for Error {
fn from(value: PoisonError<T>) -> Self {
Error::ToolFailure(value.to_string())
}
}
impl From<FromUtf8Error> for Error {
fn from(value: FromUtf8Error) -> Self {
Error::ToolFailure(value.to_string())
}
}
impl From<ParseFloatError> for Error {
fn from(value: ParseFloatError) -> Self {
Error::ToolFailure(value.to_string())
}
}
impl From<csv::Error> for Error {
fn from(value: csv::Error) -> Self {
Error::ToolFailure(value.to_string())
}
}
impl From<io::Error> for Error {
fn from(value: std::io::Error) -> Self {
Error::ToolFailure(value.to_string())
}
}
impl From<reqwest::Error> for Error {
fn from(value: reqwest::Error) -> Self {
Error::ToolFailure(value.to_string())
}
}
impl From<serde_json::Error> for Error {
fn from(value: serde_json::Error) -> Self {
Error::ToolFailure(value.to_string())
}
}
impl From<time::SystemTimeError> for Error {
fn from(value: time::SystemTimeError) -> Self {
Error::ToolFailure(value.to_string())
}
}
impl From<toml::de::Error> for Error {
fn from(value: toml::de::Error) -> Self {
Error::ToolFailure(value.to_string())
}
}
impl std::error::Error for Error {}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self}")
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Error::*;
match self {
AssertEqualFailed { expected, actual } => {
write!(f, "Equality assertion failed")?;
if expected.is_table() {
write!(f, "\n{expected}\n")?;
} else {
write!(f, " {expected} ")?;
}
write!(f, "does not equal")?;
if actual.is_table() {
write!(f, "\n{actual}")
} else {
write!(f, " {actual}.")
}
}
AssertFailed => write!(
f,
"Assertion failed. A false value was passed to \"assert\"."
),
ExpectedOperatorArgumentAmount { expected, actual } => write!(
f,
"An operator expected {} arguments, but got {}.",
expected, actual
),
ExpectedToolArgumentAmount {
tool_name,
expected,
actual,
} => write!(
f,
"{tool_name} expected {expected} arguments, but got {actual}.",
),
ExpectedAtLeastFunctionArgumentAmount {
minimum,
actual,
identifier,
} => write!(
f,
"{identifier} expected a minimum of {minimum} arguments, but got {actual}.",
),
ExpectedString { actual } => {
write!(f, "Expected a Value::String, but got {:?}.", actual)
}
ExpectedInt { actual } => write!(f, "Expected a Value::Int, but got {:?}.", actual),
ExpectedFloat { actual } => write!(f, "Expected a Value::Float, but got {:?}.", actual),
ExpectedNumber { actual } => write!(
f,
"Expected a Value::Float or Value::Int, but got {:?}.",
actual
),
ExpectedNumberOrString { actual } => write!(
f,
"Expected a Value::Number or a Value::String, but got {:?}.",
actual
),
ExpectedBoolean { actual } => {
write!(f, "Expected a Value::Boolean, but got {:?}.", actual)
}
ExpectedList { actual } => write!(f, "Expected a Value::List, but got {:?}.", actual),
ExpectedMinLengthList {
minimum_len,
actual_len,
} => write!(
f,
"Expected a list of at least {minimum_len} values, but got one with {actual_len}.",
),
ExpectedFixedLenList {
expected_len,
actual,
} => write!(
f,
"Expected a Value::List of len {}, but got {:?}.",
expected_len, actual
),
ExpectedEmpty { actual } => write!(f, "Expected a Value::Empty, but got {:?}.", actual),
ExpectedMap { actual } => write!(f, "Expected a Value::Map, but got {:?}.", actual),
ExpectedTable { actual } => write!(f, "Expected a Value::Table, but got {:?}.", actual),
ExpectedFunction { actual } => {
write!(f, "Expected Value::Function, but got {:?}.", actual)
}
ExpectedCollection { actual } => {
write!(
f,
"Expected a string, list, map or table, but got {:?}.",
actual
)
}
VariableIdentifierNotFound(identifier) => write!(
f,
"Variable identifier is not bound to anything by context: {}.",
identifier
),
FunctionIdentifierNotFound(identifier) => write!(
f,
"Function identifier is not bound to anything by context: {}.",
identifier.inner()
),
UnexpectedSyntaxNode {
expected,
actual,
location,
relevant_source,
} => write!(
f,
"Expected {expected}, but got {actual} at {location}. Code: {relevant_source} ",
),
WrongColumnAmount { expected, actual } => write!(
f,
"Wrong column amount. Expected {expected} but got {actual}."
),
ToolFailure(message) => write!(f, "{message}"),
CustomMessage(message) => write!(f, "{message}"),
}
}
}

272
src/evaluator.rs Normal file
View File

@ -0,0 +1,272 @@
//! The top level of Dust's API with functions to interpret Dust code.
//!
//! You can use this library externally by calling either of the "eval"
//! functions or by constructing your own Evaluator.
use std::fmt::{self, Debug, Formatter};
use tree_sitter::{Parser, Tree as TSTree};
use crate::{language, AbstractTree, Map, Result, Statement, Value};
/// Evaluate the given source code.
///
/// Returns a vector of results from evaluating the source code. Each comment
/// and statemtent will have its own result.
///
/// # Examples
///
/// ```rust
/// # use dust_lang::*;
/// assert_eq!(evaluate("1 + 2 + 3"), Ok(Value::Integer(6)));
/// ```
pub fn evaluate(source: &str) -> Result<Value> {
let mut context = Map::new();
evaluate_with_context(source, &mut context)
}
/// Evaluate the given source code with the given context.
///
/// # Examples
///
/// ```rust
/// # use dust_lang::*;
/// let mut context = Map::new();
///
/// {
/// let mut variables = context.variables_mut().unwrap();
///
/// variables.insert("one".into(), 1.into());
/// variables.insert("two".into(), 2.into());
/// variables.insert("three".into(), 3.into());
/// }
///
/// let dust_code = "four = 4 one + two + three + four";
///
/// assert_eq!(
/// evaluate_with_context(dust_code, &mut context),
/// Ok(Value::Integer(10))
/// );
/// ```
pub fn evaluate_with_context(source: &str, context: &mut Map) -> Result<Value> {
let mut parser = Parser::new();
parser.set_language(language()).unwrap();
Evaluator::new(parser, context, source).run()
}
/// A collection of statements and comments interpreted from a syntax tree.
///
/// The Evaluator turns a tree sitter concrete syntax tree into a vector of
/// abstract trees called [Item][]s that can be run to execute the source code.
pub struct Evaluator<'c, 's> {
_parser: Parser,
context: &'c mut Map,
source: &'s str,
syntax_tree: TSTree,
}
impl Debug for Evaluator<'_, '_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Evaluator context: {}", self.context)
}
}
impl<'c, 's> Evaluator<'c, 's> {
pub fn new(mut parser: Parser, context: &'c mut Map, source: &'s str) -> Self {
let syntax_tree = parser.parse(source, None).unwrap();
Evaluator {
_parser: parser,
context,
source,
syntax_tree,
}
}
pub fn run(self) -> Result<Value> {
let mut cursor = self.syntax_tree.walk();
let root_node = cursor.node();
let mut prev_result = Ok(Value::Empty);
for statement_node in root_node.children(&mut cursor) {
let statement = Statement::from_syntax_node(self.source, statement_node)?;
prev_result = statement.run(self.source, self.context);
}
prev_result
}
pub fn syntax_tree(&self) -> String {
self.syntax_tree.root_node().to_sexp()
}
}
#[cfg(test)]
mod tests {
use crate::{List, Table};
use super::*;
#[test]
fn evaluate_empty() {
assert_eq!(evaluate("x = 9"), Ok(Value::Empty));
assert_eq!(evaluate("x = 1 + 1"), Ok(Value::Empty));
}
#[test]
fn evaluate_integer() {
assert_eq!(evaluate("1"), Ok(Value::Integer(1)));
assert_eq!(evaluate("123"), Ok(Value::Integer(123)));
assert_eq!(evaluate("-666"), Ok(Value::Integer(-666)));
}
#[test]
fn evaluate_float() {
assert_eq!(evaluate("0.1"), Ok(Value::Float(0.1)));
assert_eq!(evaluate("12.3"), Ok(Value::Float(12.3)));
assert_eq!(evaluate("-6.66"), Ok(Value::Float(-6.66)));
}
#[test]
fn evaluate_string() {
assert_eq!(evaluate("\"one\""), Ok(Value::String("one".to_string())));
assert_eq!(evaluate("'one'"), Ok(Value::String("one".to_string())));
assert_eq!(evaluate("`one`"), Ok(Value::String("one".to_string())));
assert_eq!(evaluate("`'one'`"), Ok(Value::String("'one'".to_string())));
assert_eq!(evaluate("'`one`'"), Ok(Value::String("`one`".to_string())));
assert_eq!(
evaluate("\"'one'\""),
Ok(Value::String("'one'".to_string()))
);
}
#[test]
fn evaluate_list() {
assert_eq!(
evaluate("[1, 2, 'foobar']"),
Ok(Value::List(List::with_items(vec![
Value::Integer(1),
Value::Integer(2),
Value::String("foobar".to_string()),
])))
);
}
#[test]
fn evaluate_map() {
let map = Map::new();
{
let mut variables = map.variables_mut().unwrap();
variables.insert("x".to_string(), Value::Integer(1));
variables.insert("foo".to_string(), Value::String("bar".to_string()));
}
assert_eq!(evaluate("{ x = 1, foo = 'bar' }"), Ok(Value::Map(map)));
}
#[test]
fn evaluate_table() {
let mut table = Table::new(vec!["messages".to_string(), "numbers".to_string()]);
table
.insert(vec![Value::String("hiya".to_string()), Value::Integer(42)])
.unwrap();
table
.insert(vec![Value::String("foo".to_string()), Value::Integer(57)])
.unwrap();
table
.insert(vec![Value::String("bar".to_string()), Value::Float(99.99)])
.unwrap();
assert_eq!(
evaluate(
"
table |messages numbers| [
['hiya', 42]
['foo', 57]
['bar', 99.99]
]
"
),
Ok(Value::Table(table))
);
}
#[test]
fn evaluate_if() {
assert_eq!(
evaluate("if true { 'true' }"),
Ok(Value::String("true".to_string()))
);
}
#[test]
fn evaluate_if_else() {
assert_eq!(evaluate("if false { 1 } else { 2 }"), Ok(Value::Integer(2)));
assert_eq!(
evaluate("if true { 1.0 } else { 42.0 }"),
Ok(Value::Float(1.0))
);
}
#[test]
fn evaluate_if_else_else_if_else() {
assert_eq!(
evaluate(
"
if false {
'no'
} else if 1 + 1 == 3 {
'nope'
} else {
'ok'
}
"
),
Ok(Value::String("ok".to_string()))
);
}
#[test]
fn evaluate_if_else_if_else_if_else_if_else() {
assert_eq!(
evaluate(
"
if false {
'no'
} else if 1 + 1 == 1 {
'nope'
} else if 9 / 2 == 4 {
'nope'
} else if 'foo' == 'bar' {
'nope'
} else {
'ok'
}
"
),
Ok(Value::String("ok".to_string()))
);
}
#[test]
fn evaluate_function_call() {
assert_eq!(
evaluate(
"
foobar = |message| => { message }
(foobar 'Hiya')
",
),
Ok(Value::String("Hiya".to_string()))
);
}
#[test]
fn evaluate_built_in_function_call() {
assert_eq!(evaluate("(output 'Hiya')"), Ok(Value::Empty));
}
}

53
src/lib.rs Normal file
View File

@ -0,0 +1,53 @@
//! The Dust library is used to implement the Dust language, `src/main.rs` implements the command
//! line binary.
//!
//! Using this library is simple and straightforward, see the [inferface] module for instructions on
//! interpreting Dust code. Most of the language's features are implemented in the [tools] module.
pub use crate::{
abstract_tree::*,
error::*,
evaluator::*,
value::{function::Function, list::List, map::Map, table::Table, value_type::ValueType, Value},
};
mod abstract_tree;
mod error;
mod evaluator;
mod value;
use tree_sitter::Language;
extern "C" {
fn tree_sitter_dust() -> Language;
}
/// Get the tree-sitter [Language][] for this grammar.
///
/// [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html
pub fn language() -> Language {
unsafe { tree_sitter_dust() }
}
/// The content of the [`node-types.json`][] file for this grammar.
///
/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types
pub const NODE_TYPES: &str = include_str!("../tree-sitter-dust/src/node-types.json");
// Uncomment these to include any queries that this grammar contains
// pub const HIGHLIGHTS_QUERY: &'static str = include_str!("../../queries/highlights.scm");
// pub const INJECTIONS_QUERY: &'static str = include_str!("../../queries/injections.scm");
// pub const LOCALS_QUERY: &'static str = include_str!("../../queries/locals.scm");
// pub const TAGS_QUERY: &'static str = include_str!("../../queries/tags.scm");
#[cfg(test)]
mod tests {
#[test]
fn test_can_load_grammar() {
let mut parser = tree_sitter::Parser::new();
parser
.set_language(super::language())
.expect("Error loading dust language");
}
}

204
src/main.rs Normal file
View File

@ -0,0 +1,204 @@
//! Command line interface for the dust programming language.
use async_std::fs::read_to_string;
use clap::Parser;
use rustyline::{
completion::FilenameCompleter,
error::ReadlineError,
highlight::Highlighter,
hint::{Hint, Hinter, HistoryHinter},
history::DefaultHistory,
Completer, Context, Editor, Helper, Validator,
};
use tree_sitter::Parser as TSParser;
use std::borrow::Cow;
use dust_lang::{evaluate_with_context, language, Evaluator, Map, Value};
/// Command-line arguments to be parsed.
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Dust source code to evaluate.
#[arg(short, long)]
command: Option<String>,
/// Data to assign to the "input" variable.
#[arg(short, long)]
input: Option<String>,
/// A path to file whose contents will be assigned to the "input" variable.
#[arg(short = 'p', long)]
input_path: Option<String>,
/// A path to file whose contents will be assigned to the "input" variable.
#[arg(short = 't', long = "tree")]
show_syntax_tree: bool,
/// Location of the file to run.
path: Option<String>,
}
#[async_std::main]
async fn main() {
let args = Args::parse();
if args.path.is_none() && args.command.is_none() {
return run_cli_shell();
}
let source = if let Some(path) = &args.path {
read_to_string(path).await.unwrap()
} else if let Some(command) = &args.command {
command.clone()
} else {
"".to_string()
};
let mut context = Map::new();
if let Some(input) = args.input {
context
.variables_mut()
.unwrap()
.insert("input".to_string(), Value::String(input));
}
if let Some(path) = args.input_path {
let file_contents = read_to_string(path).await.unwrap();
context
.variables_mut()
.unwrap()
.insert("input".to_string(), Value::String(file_contents));
}
let mut parser = TSParser::new();
parser.set_language(language()).unwrap();
let evaluator = Evaluator::new(parser, &mut context, &source);
if args.show_syntax_tree {
println!("{}", evaluator.syntax_tree());
}
let eval_result = evaluator.run();
match eval_result {
Ok(value) => {
if !value.is_empty() {
println!("{value}")
}
}
Err(error) => eprintln!("{error}"),
}
}
#[derive(Helper, Completer, Validator)]
struct DustReadline {
#[rustyline(Completer)]
completer: FilenameCompleter,
tool_hints: Vec<ToolHint>,
#[rustyline(Hinter)]
_hinter: HistoryHinter,
}
impl DustReadline {
fn new() -> Self {
Self {
completer: FilenameCompleter::new(),
_hinter: HistoryHinter {},
tool_hints: Vec::new(),
}
}
}
struct ToolHint {
display: String,
complete_to: usize,
}
impl Hint for ToolHint {
fn display(&self) -> &str {
&self.display
}
fn completion(&self) -> Option<&str> {
if self.complete_to > 0 {
Some(&self.display[..self.complete_to])
} else {
None
}
}
}
impl ToolHint {
fn suffix(&self, strip_chars: usize) -> ToolHint {
ToolHint {
display: self.display[strip_chars..].to_string(),
complete_to: self.complete_to.saturating_sub(strip_chars),
}
}
}
impl Hinter for DustReadline {
type Hint = ToolHint;
fn hint(&self, line: &str, pos: usize, _ctx: &Context<'_>) -> Option<Self::Hint> {
if line.is_empty() || pos < line.len() {
return None;
}
self.tool_hints.iter().find_map(|tool_hint| {
if tool_hint.display.starts_with(line) {
Some(tool_hint.suffix(pos))
} else {
None
}
})
}
}
impl Highlighter for DustReadline {
fn highlight_hint<'h>(&self, hint: &'h str) -> std::borrow::Cow<'h, str> {
let highlighted = ansi_term::Colour::Red.paint(hint).to_string();
Cow::Owned(highlighted)
}
}
fn run_cli_shell() {
let mut context = Map::new();
let mut rl: Editor<DustReadline, DefaultHistory> = Editor::new().unwrap();
rl.set_helper(Some(DustReadline::new()));
if rl.load_history("target/history.txt").is_err() {
println!("No previous history.");
}
loop {
let readline = rl.readline("* ");
match readline {
Ok(line) => {
let line = line.as_str();
rl.add_history_entry(line).unwrap();
let eval_result = evaluate_with_context(line, &mut context);
match eval_result {
Ok(value) => println!("{value}"),
Err(error) => eprintln!("{error}"),
}
}
Err(ReadlineError::Interrupted) => break,
Err(ReadlineError::Eof) => break,
Err(error) => eprintln!("{error}"),
}
}
rl.save_history("target/history.txt").unwrap();
}

38
src/value/function.rs Normal file
View File

@ -0,0 +1,38 @@
use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
use crate::{Block, Identifier};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Function {
parameters: Option<Vec<Identifier>>,
body: Box<Block>,
}
impl Function {
pub fn new(parameters: Option<Vec<Identifier>>, body: Block) -> Self {
Function {
parameters,
body: Box::new(body),
}
}
pub fn identifiers(&self) -> &Option<Vec<Identifier>> {
&self.parameters
}
pub fn body(&self) -> &Block {
&self.body
}
}
impl Display for Function {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"function < {:?} > {{ {:?} }}", // TODO: Correct this output
self.parameters, self.body
)
}
}

60
src/value/list.rs Normal file
View File

@ -0,0 +1,60 @@
use std::{
cmp::Ordering,
sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard},
};
use crate::Value;
#[derive(Debug, Clone)]
pub struct List(Arc<RwLock<Vec<Value>>>);
impl List {
pub fn new() -> Self {
List(Arc::new(RwLock::new(Vec::new())))
}
pub fn with_capacity(capacity: usize) -> Self {
List(Arc::new(RwLock::new(Vec::with_capacity(capacity))))
}
pub fn with_items(items: Vec<Value>) -> Self {
List(Arc::new(RwLock::new(items)))
}
pub fn items(&self) -> RwLockReadGuard<'_, Vec<Value>> {
self.0.read().unwrap()
}
pub fn items_mut(&self) -> RwLockWriteGuard<'_, Vec<Value>> {
self.0.write().unwrap()
}
}
impl Eq for List {}
impl PartialEq for List {
fn eq(&self, other: &Self) -> bool {
let left = self.0.read().unwrap().clone().into_iter();
let right = other.0.read().unwrap().clone().into_iter();
left.eq(right)
}
}
impl Ord for List {
fn cmp(&self, other: &Self) -> Ordering {
let left = self.0.read().unwrap().clone().into_iter();
let right = other.0.read().unwrap().clone().into_iter();
left.cmp(right)
}
}
impl PartialOrd for List {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
let left = self.0.read().unwrap().clone().into_iter();
let right = other.0.read().unwrap().clone().into_iter();
left.partial_cmp(right)
}
}

121
src/value/map.rs Normal file
View File

@ -0,0 +1,121 @@
use serde::Serialize;
use std::{
cmp::Ordering,
collections::BTreeMap,
fmt::{self, Display, Formatter},
sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard},
};
use crate::{value::Value, List, Result, Table};
/// A collection dust variables comprised of key-value pairs.
///
/// The inner value is a BTreeMap in order to allow VariableMap instances to be sorted and compared
/// to one another.
#[derive(Clone, Debug)]
pub struct Map {
variables: Arc<RwLock<BTreeMap<String, Value>>>,
}
impl Map {
/// Creates a new instace.
pub fn new() -> Self {
Map {
variables: Arc::new(RwLock::new(BTreeMap::new())),
}
}
pub fn clone_from(other: &Self) -> Result<Self> {
let mut new_map = BTreeMap::new();
for (key, value) in other.variables()?.iter() {
new_map.insert(key.clone(), value.clone());
}
Ok(Map {
variables: Arc::new(RwLock::new(new_map)),
})
}
pub fn variables(&self) -> Result<RwLockReadGuard<BTreeMap<String, Value>>> {
Ok(self.variables.read()?)
}
pub fn variables_mut(&self) -> Result<RwLockWriteGuard<BTreeMap<String, Value>>> {
Ok(self.variables.write()?)
}
}
impl Default for Map {
fn default() -> Self {
Self::new()
}
}
impl Eq for Map {}
impl PartialEq for Map {
fn eq(&self, other: &Self) -> bool {
let left = self.variables.read().unwrap().clone().into_iter();
let right = other.variables.read().unwrap().clone().into_iter();
left.eq(right)
}
}
impl Ord for Map {
fn cmp(&self, other: &Self) -> Ordering {
let left = self.variables.read().unwrap().clone().into_iter();
let right = other.variables.read().unwrap().clone().into_iter();
left.cmp(right)
}
}
impl PartialOrd for Map {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
let left = self.variables.read().unwrap().clone().into_iter();
let right = other.variables.read().unwrap().clone().into_iter();
left.partial_cmp(right)
}
}
impl Display for Map {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
writeln!(f, "{{")?;
let variables = self.variables.read().unwrap().clone().into_iter();
for (key, value) in variables {
writeln!(f, " {key} = {value}")?;
}
write!(f, "}}")
}
}
impl From<&Table> for Result<Map> {
fn from(value: &Table) -> Result<Map> {
let map = Map::new();
for (row_index, row) in value.rows().iter().enumerate() {
map.variables_mut()?
.insert(
row_index.to_string(),
Value::List(List::with_items(row.clone())),
)
.unwrap();
}
Ok(map)
}
}
impl Serialize for Map {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.variables.serialize(serializer)
}
}

805
src/value/mod.rs Normal file
View File

@ -0,0 +1,805 @@
//! Types that represent runtime values.
use crate::{
error::{Error, Result},
Function, List, Map, Table, ValueType,
};
use serde::{
de::{MapAccess, SeqAccess, Visitor},
ser::SerializeTuple,
Deserialize, Serialize, Serializer,
};
use std::{
cmp::Ordering,
convert::TryFrom,
fmt::{self, Display, Formatter},
marker::PhantomData,
ops::{Add, AddAssign, Div, Mul, Rem, Sub, SubAssign},
};
pub mod function;
pub mod list;
pub mod map;
pub mod table;
pub mod value_type;
/// Dust value representation.
///
/// Every dust variable has a key and a Value. Variables are represented by
/// storing them in a VariableMap. This means the map of variables is itself a
/// value that can be treated as any other.
#[derive(Debug, Clone, Default)]
pub enum Value {
List(List),
Map(Map),
Table(Table),
Function(Function),
String(String),
Float(f64),
Integer(i64),
Boolean(bool),
#[default]
Empty,
}
impl Value {
pub fn value_type(&self) -> ValueType {
ValueType::from(self)
}
pub fn is_table(&self) -> bool {
matches!(self, Value::Table(_))
}
pub fn is_string(&self) -> bool {
matches!(self, Value::String(_))
}
pub fn is_integer(&self) -> bool {
matches!(self, Value::Integer(_))
}
pub fn is_float(&self) -> bool {
matches!(self, Value::Float(_))
}
pub fn is_number(&self) -> bool {
matches!(self, Value::Integer(_) | Value::Float(_))
}
pub fn is_boolean(&self) -> bool {
matches!(self, Value::Boolean(_))
}
pub fn is_list(&self) -> bool {
matches!(self, Value::List(_))
}
pub fn is_empty(&self) -> bool {
matches!(self, Value::Empty)
}
pub fn is_map(&self) -> bool {
matches!(self, Value::Map(_))
}
pub fn is_function(&self) -> bool {
matches!(self, Value::Map(_))
}
/// Borrows the value stored in `self` as `String`, or returns `Err` if `self` is not a `Value::String`.
pub fn as_string(&self) -> Result<&String> {
match self {
Value::String(string) => Ok(string),
value => Err(Error::ExpectedString {
actual: value.clone(),
}),
}
}
/// Copies the value stored in `self` as `i64`, or returns `Err` if `self` is not a `Value::Int`.
pub fn as_integer(&self) -> Result<i64> {
match self {
Value::Integer(i) => Ok(*i),
value => Err(Error::ExpectedInt {
actual: value.clone(),
}),
}
}
/// Copies the value stored in `self` as `f64`, or returns `Err` if `self` is not a `Primitive::Float`.
pub fn as_float(&self) -> Result<f64> {
match self {
Value::Float(f) => Ok(*f),
value => Err(Error::ExpectedFloat {
actual: value.clone(),
}),
}
}
/// Copies the value stored in `self` as `f64`, or returns `Err` if `self` is not a `Primitive::Float` or `Value::Int`.
/// Note that this method silently converts `i64` to `f64`, if `self` is a `Value::Int`.
pub fn as_number(&self) -> Result<f64> {
match self {
Value::Float(f) => Ok(*f),
Value::Integer(i) => Ok(*i as f64),
value => Err(Error::ExpectedNumber {
actual: value.clone(),
}),
}
}
/// Copies the value stored in `self` as `bool`, or returns `Err` if `self` is not a `Primitive::Boolean`.
pub fn as_boolean(&self) -> Result<bool> {
match self {
Value::Boolean(boolean) => Ok(*boolean),
value => Err(Error::ExpectedBoolean {
actual: value.clone(),
}),
}
}
/// Borrows the value stored in `self` as `Vec<Value>`, or returns `Err` if `self` is not a `Value::List`.
pub fn as_list(&self) -> Result<&List> {
match self {
Value::List(list) => Ok(list),
value => Err(Error::ExpectedList {
actual: value.clone(),
}),
}
}
/// Borrows the value stored in `self` as `Vec<Value>`, or returns `Err` if `self` is not a `Value::List`.
pub fn into_inner_list(self) -> Result<List> {
match self {
Value::List(list) => Ok(list),
value => Err(Error::ExpectedList {
actual: value.clone(),
}),
}
}
/// Borrows the value stored in `self` as `Vec<Value>`, or returns `Err` if `self` is not a `Value::Map`.
pub fn as_map(&self) -> Result<&Map> {
match self {
Value::Map(map) => Ok(map),
value => Err(Error::ExpectedMap {
actual: value.clone(),
}),
}
}
/// Borrows the value stored in `self` as `Vec<Value>`, or returns `Err` if `self` is not a `Value::Table`.
pub fn as_table(&self) -> Result<&Table> {
match self {
Value::Table(table) => Ok(table),
value => Err(Error::ExpectedTable {
actual: value.clone(),
}),
}
}
/// Borrows the value stored in `self` as `Function`, or returns `Err` if
/// `self` is not a `Value::Function`.
pub fn as_function(&self) -> Result<&Function> {
match self {
Value::Function(function) => Ok(function),
value => Err(Error::ExpectedFunction {
actual: value.clone(),
}),
}
}
/// Returns `()`, or returns`Err` if `self` is not a `Value::Empty`.
pub fn as_empty(&self) -> Result<()> {
match self {
Value::Empty => Ok(()),
value => Err(Error::ExpectedEmpty {
actual: value.clone(),
}),
}
}
/// Returns an owned table, either by cloning or converting the inner value.
pub fn to_table(&self) -> Result<Table> {
match self {
Value::Table(table) => Ok(table.clone()),
Value::List(list) => Ok(Table::from(list)),
Value::Map(map) => Result::from(map),
value => Err(Error::ExpectedTable {
actual: value.clone(),
}),
}
}
}
impl Add for Value {
type Output = Result<Value>;
fn add(self, other: Self) -> Self::Output {
if let (Ok(left), Ok(right)) = (self.as_integer(), other.as_integer()) {
return Ok(Value::Integer(left + right));
}
if let (Ok(left), Ok(right)) = (self.as_number(), other.as_number()) {
return Ok(Value::Float(left + right));
}
if let (Ok(left), Ok(right)) = (self.as_string(), other.as_string()) {
return Ok(Value::String(left.to_string() + right));
}
if self.is_string() || other.is_string() {
return Ok(Value::String(self.to_string() + &other.to_string()));
}
let non_number_or_string = if !self.is_number() == !self.is_string() {
self
} else {
other
};
Err(Error::ExpectedNumberOrString {
actual: non_number_or_string,
})
}
}
impl Sub for Value {
type Output = Result<Self>;
fn sub(self, other: Self) -> Self::Output {
match (self.as_integer(), other.as_integer()) {
(Ok(left), Ok(right)) => return Ok(Value::Integer(left - right)),
_ => {}
}
match (self.as_number(), other.as_number()) {
(Ok(left), Ok(right)) => return Ok(Value::Float(left - right)),
_ => {}
}
let non_number = if !self.is_number() { self } else { other };
Err(Error::ExpectedNumber { actual: non_number })
}
}
impl Mul for Value {
type Output = Result<Self>;
fn mul(self, other: Self) -> Self::Output {
if self.is_integer() && other.is_integer() {
let left = self.as_integer().unwrap();
let right = other.as_integer().unwrap();
let value = Value::Integer(left.saturating_mul(right));
Ok(value)
} else {
let left = self.as_number()?;
let right = other.as_number()?;
let value = Value::Float(left * right);
Ok(value)
}
}
}
impl Div for Value {
type Output = Result<Self>;
fn div(self, other: Self) -> Self::Output {
let left = self.as_number()?;
let right = other.as_number()?;
let result = left / right;
let value = if result % 2.0 == 0.0 {
Value::Integer(result as i64)
} else {
Value::Float(result)
};
Ok(value)
}
}
impl Rem for Value {
type Output = Result<Self>;
fn rem(self, other: Self) -> Self::Output {
let left = self.as_integer()?;
let right = other.as_integer()?;
let result = left % right;
Ok(Value::Integer(result))
}
}
impl AddAssign for Value {
fn add_assign(&mut self, other: Self) {
match (self, other) {
(Value::Integer(left), Value::Integer(right)) => *left += right,
(Value::Float(left), Value::Float(right)) => *left += right,
(Value::Float(left), Value::Integer(right)) => *left += right as f64,
(Value::String(left), Value::String(right)) => *left += &right,
(Value::List(list), value) => list.items_mut().push(value),
_ => {}
}
}
}
impl SubAssign for Value {
fn sub_assign(&mut self, other: Self) {
match (self, other) {
(Value::Integer(left), Value::Integer(right)) => *left -= right,
(Value::Float(left), Value::Float(right)) => *left -= right,
(Value::Float(left), Value::Integer(right)) => *left -= right as f64,
_ => {}
}
}
}
impl Eq for Value {}
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Value::Integer(left), Value::Integer(right)) => left == right,
(Value::Float(left), Value::Float(right)) => left == right,
(Value::Boolean(left), Value::Boolean(right)) => left == right,
(Value::String(left), Value::String(right)) => left == right,
(Value::List(left), Value::List(right)) => left == right,
(Value::Map(left), Value::Map(right)) => left == right,
(Value::Table(left), Value::Table(right)) => left == right,
(Value::Function(left), Value::Function(right)) => left == right,
(Value::Empty, Value::Empty) => true,
_ => false,
}
}
}
impl PartialOrd for Value {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Value {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(Value::String(left), Value::String(right)) => left.cmp(right),
(Value::String(_), _) => Ordering::Greater,
(Value::Float(left), Value::Float(right)) => left.total_cmp(right),
(Value::Integer(left), Value::Integer(right)) => left.cmp(right),
(Value::Float(float), Value::Integer(integer)) => {
let int_as_float = *integer as f64;
float.total_cmp(&int_as_float)
}
(Value::Integer(integer), Value::Float(float)) => {
let int_as_float = *integer as f64;
int_as_float.total_cmp(float)
}
(Value::Float(_), _) => Ordering::Greater,
(Value::Integer(_), _) => Ordering::Greater,
(Value::Boolean(left), Value::Boolean(right)) => left.cmp(right),
(Value::Boolean(_), _) => Ordering::Greater,
(Value::List(left), Value::List(right)) => left.cmp(right),
(Value::List(_), _) => Ordering::Greater,
(Value::Map(left), Value::Map(right)) => left.cmp(right),
(Value::Map(_), _) => Ordering::Greater,
(Value::Table(left), Value::Table(right)) => left.cmp(right),
(Value::Table(_), _) => Ordering::Greater,
(Value::Function(left), Value::Function(right)) => left.cmp(right),
(Value::Function(_), _) => Ordering::Greater,
(Value::Empty, Value::Empty) => Ordering::Equal,
(Value::Empty, _) => Ordering::Less,
}
}
}
impl Serialize for Value {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Value::String(inner) => serializer.serialize_str(inner),
Value::Float(inner) => serializer.serialize_f64(*inner),
Value::Integer(inner) => serializer.serialize_i64(*inner),
Value::Boolean(inner) => serializer.serialize_bool(*inner),
Value::List(inner) => {
let items = inner.items();
let mut list = serializer.serialize_tuple(items.len())?;
for value in items.iter() {
list.serialize_element(value)?;
}
list.end()
}
Value::Empty => todo!(),
Value::Map(inner) => inner.serialize(serializer),
Value::Table(inner) => inner.serialize(serializer),
Value::Function(inner) => inner.serialize(serializer),
}
}
}
impl Display for Value {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Value::String(string) => write!(f, "{string}"),
Value::Float(float) => write!(f, "{float}"),
Value::Integer(int) => write!(f, "{int}"),
Value::Boolean(boolean) => write!(f, "{boolean}"),
Value::Empty => write!(f, "empty"),
Value::List(list) => {
write!(f, "[")?;
for value in list.items().iter() {
write!(f, " {value} ")?;
}
write!(f, "]")
}
Value::Map(map) => write!(f, "{map}"),
Value::Table(table) => write!(f, "{table}"),
Value::Function(function) => write!(f, "{function}"),
}
}
}
impl From<String> for Value {
fn from(string: String) -> Self {
Value::String(string)
}
}
impl From<&str> for Value {
fn from(string: &str) -> Self {
Value::String(string.to_string())
}
}
impl From<f64> for Value {
fn from(float: f64) -> Self {
Value::Float(float)
}
}
impl From<i64> for Value {
fn from(int: i64) -> Self {
Value::Integer(int)
}
}
impl From<bool> for Value {
fn from(boolean: bool) -> Self {
Value::Boolean(boolean)
}
}
impl From<Vec<Value>> for Value {
fn from(vec: Vec<Value>) -> Self {
Value::List(List::with_items(vec))
}
}
impl From<Value> for Result<Value> {
fn from(value: Value) -> Self {
Ok(value)
}
}
impl From<()> for Value {
fn from(_: ()) -> Self {
Value::Empty
}
}
impl TryFrom<Value> for String {
type Error = Error;
fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
if let Value::String(value) = value {
Ok(value)
} else {
Err(Error::ExpectedString { actual: value })
}
}
}
impl TryFrom<Value> for f64 {
type Error = Error;
fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
if let Value::Float(value) = value {
Ok(value)
} else {
Err(Error::ExpectedFloat { actual: value })
}
}
}
impl TryFrom<Value> for i64 {
type Error = Error;
fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
if let Value::Integer(value) = value {
Ok(value)
} else {
Err(Error::ExpectedInt { actual: value })
}
}
}
impl TryFrom<Value> for bool {
type Error = Error;
fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
if let Value::Boolean(value) = value {
Ok(value)
} else {
Err(Error::ExpectedBoolean { actual: value })
}
}
}
struct ValueVisitor {
marker: PhantomData<fn() -> Value>,
}
impl ValueVisitor {
fn new() -> Self {
ValueVisitor {
marker: PhantomData,
}
}
}
impl<'de> Visitor<'de> for ValueVisitor {
type Value = Value;
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
formatter.write_str("Any valid whale data.")
}
fn visit_bool<E>(self, v: bool) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Value::Boolean(v))
}
fn visit_i8<E>(self, v: i8) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_i64(v as i64)
}
fn visit_i16<E>(self, v: i16) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_i64(v as i64)
}
fn visit_i32<E>(self, v: i32) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_i64(v as i64)
}
fn visit_i64<E>(self, v: i64) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Value::Integer(v))
}
fn visit_i128<E>(self, v: i128) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
if v > i64::MAX as i128 {
Ok(Value::Integer(i64::MAX))
} else {
Ok(Value::Integer(v as i64))
}
}
fn visit_u8<E>(self, v: u8) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_u64(v as u64)
}
fn visit_u16<E>(self, v: u16) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_u64(v as u64)
}
fn visit_u32<E>(self, v: u32) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_u64(v as u64)
}
fn visit_u64<E>(self, v: u64) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_i64(v as i64)
}
fn visit_u128<E>(self, v: u128) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_i128(v as i128)
}
fn visit_f32<E>(self, v: f32) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_f64(v as f64)
}
fn visit_f64<E>(self, v: f64) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Value::Float(v))
}
fn visit_char<E>(self, v: char) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(&v.to_string())
}
fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Value::String(v.to_string()))
}
fn visit_borrowed_str<E>(self, v: &'de str) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(v)
}
fn visit_string<E>(self, v: String) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Value::String(v))
}
fn visit_bytes<E>(self, v: &[u8]) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
let _ = v;
Err(serde::de::Error::invalid_type(
serde::de::Unexpected::Bytes(v),
&self,
))
}
fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_bytes(v)
}
fn visit_byte_buf<E>(self, v: Vec<u8>) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_bytes(&v)
}
fn visit_none<E>(self) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Value::Empty)
}
fn visit_some<D>(self, deserializer: D) -> std::result::Result<Self::Value, D::Error>
where
D: serde::Deserializer<'de>,
{
let _ = deserializer;
Err(serde::de::Error::invalid_type(
serde::de::Unexpected::Option,
&self,
))
}
fn visit_unit<E>(self) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Value::Empty)
}
fn visit_newtype_struct<D>(self, deserializer: D) -> std::result::Result<Self::Value, D::Error>
where
D: serde::Deserializer<'de>,
{
let _ = deserializer;
Err(serde::de::Error::invalid_type(
serde::de::Unexpected::NewtypeStruct,
&self,
))
}
fn visit_seq<A>(self, mut access: A) -> std::result::Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut list = Vec::new();
while let Some(value) = access.next_element()? {
list.push(value);
}
Ok(Value::List(List::with_items(list)))
}
fn visit_map<M>(self, mut access: M) -> std::result::Result<Value, M::Error>
where
M: MapAccess<'de>,
{
let map = Map::new();
{
let mut variables = map.variables_mut().unwrap();
while let Some((key, value)) = access.next_entry()? {
variables.insert(key, value);
}
}
Ok(Value::Map(map))
}
fn visit_enum<A>(self, data: A) -> std::result::Result<Self::Value, A::Error>
where
A: serde::de::EnumAccess<'de>,
{
let _ = data;
Err(serde::de::Error::invalid_type(
serde::de::Unexpected::Enum,
&self,
))
}
fn __private_visit_untagged_option<D>(self, _: D) -> std::result::Result<Self::Value, ()>
where
D: serde::Deserializer<'de>,
{
Err(())
}
}
impl<'de> Deserialize<'de> for Value {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_any(ValueVisitor::new())
}
}

346
src/value/table.rs Normal file
View File

@ -0,0 +1,346 @@
use crate::{Error, List, Map, Result, Value};
use comfy_table::{Cell, Color, ContentArrangement, Table as ComfyTable};
use serde::{Deserialize, Serialize};
use std::{
cmp::Ordering,
fmt::{self, Display, Formatter},
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Table {
headers: Vec<String>,
rows: Vec<Vec<Value>>,
primary_key_index: usize,
}
impl Table {
pub fn new(headers: Vec<String>) -> Self {
Table {
headers,
rows: Vec::new(),
primary_key_index: 0,
}
}
pub fn with_capacity(capacity: usize, headers: Vec<String>) -> Self {
Table {
headers,
rows: Vec::with_capacity(capacity),
primary_key_index: 0,
}
}
pub fn from_raw_parts(headers: Vec<String>, rows: Vec<Vec<Value>>) -> Self {
Table {
headers,
rows,
primary_key_index: 0,
}
}
pub fn reserve(&mut self, additional: usize) {
self.rows.reserve(additional);
}
pub fn headers(&self) -> &Vec<String> {
&self.headers
}
pub fn rows(&self) -> &Vec<Vec<Value>> {
&self.rows
}
pub fn len(&self) -> usize {
self.rows.len()
}
pub fn is_empty(&self) -> bool {
self.rows.is_empty()
}
pub fn sort(&mut self) {
self.rows.sort();
}
pub fn insert(&mut self, row: Vec<Value>) -> Result<()> {
if row.len() != self.headers.len() {
return Err(Error::WrongColumnAmount {
expected: self.headers.len(),
actual: row.len(),
});
}
self.rows.push(row);
Ok(())
}
pub fn remove(&mut self, index: usize) -> Result<()> {
self.rows.remove(index);
Ok(())
}
pub fn get_row(&self, index: usize) -> Option<&Vec<Value>> {
self.rows.get(index)
}
pub fn get(&self, value: &Value) -> Option<&Vec<Value>> {
let primary_key = self.headers().get(self.primary_key_index)?;
self.get_where(primary_key, value)
}
pub fn get_where(&self, column_name: &str, expected: &Value) -> Option<&Vec<Value>> {
let column_index = self.get_column_index(column_name)?;
for row in &self.rows {
if let Some(actual) = row.get(column_index) {
if actual == expected {
return Some(row);
}
}
}
None
}
pub fn filter(&self, column_name: &str, expected: &Value) -> Option<Table> {
let mut filtered = Table::new(self.headers.clone());
let column_index = self.get_column_index(column_name)?;
for row in &self.rows {
let actual = row.get(column_index).unwrap();
if actual == expected {
let _ = filtered.insert(row.clone());
}
}
Some(filtered)
}
pub fn get_column_index(&self, column_name: &str) -> Option<usize> {
let column_names = &self.headers;
for (i, column) in column_names.iter().enumerate() {
if column == column_name {
return Some(i);
}
}
None
}
}
impl Display for Table {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let mut table = ComfyTable::new();
table
.load_preset("││──├─┼┤│ ┬┴╭╮╰╯")
.set_content_arrangement(ContentArrangement::Dynamic)
.set_header(&self.headers);
for row in &self.rows {
let row = row.iter().map(|value| {
let text = match value {
Value::List(list) => {
let mut string = "(".to_string();
for (index, value) in list.items().iter().enumerate() {
if index > 0 {
string.push_str(", ");
}
string.push_str(&value.to_string());
}
string.push(')');
string
}
Value::Map(map) => format!("Map ({} items)", map.variables().unwrap().len()),
Value::Table(table) => format!("Table ({} items)", table.len()),
Value::Function(_) => "Function".to_string(),
Value::Empty => "Empty".to_string(),
value => value.to_string(),
};
let mut cell = Cell::new(text).bg(Color::Rgb {
r: 40,
g: 40,
b: 40,
});
if value.is_string() {
cell = cell.fg(Color::Green);
}
if value.is_integer() {
cell = cell.fg(Color::Blue);
}
if value.is_boolean() {
cell = cell.fg(Color::Red);
}
if value.is_function() {
cell = cell.fg(Color::Cyan);
}
cell
});
table.add_row(row);
}
if self.headers.is_empty() {
table.set_header(["empty"]);
}
write!(f, "{table}")
}
}
impl From<&Value> for Table {
fn from(value: &Value) -> Self {
match value {
Value::String(string) => {
let mut table = Table::new(vec!["string".to_string()]);
table
.insert(vec![Value::String(string.to_string())])
.unwrap();
table
}
Value::Float(float) => {
let mut table = Table::new(vec!["float".to_string()]);
table.insert(vec![Value::Float(*float)]).unwrap();
table
}
Value::Integer(integer) => {
let mut table = Table::new(vec!["integer".to_string()]);
table.insert(vec![Value::Integer(*integer)]).unwrap();
table
}
Value::Boolean(boolean) => {
let mut table = Table::new(vec!["boolean".to_string()]);
table.insert(vec![Value::Boolean(*boolean)]).unwrap();
table
}
Value::List(list) => Self::from(list),
Value::Empty => Table::new(Vec::with_capacity(0)),
Value::Map(map) => Result::<Table>::from(map).unwrap(),
Value::Table(table) => table.clone(),
Value::Function(function) => {
let mut table = Table::new(vec!["function".to_string()]);
table
.insert(vec![Value::Function(function.clone())])
.unwrap();
table
}
}
}
}
impl From<&List> for Table {
fn from(list: &List) -> Self {
let mut table = Table::new(vec!["index".to_string(), "item".to_string()]);
for (i, value) in list.items().iter().enumerate() {
table
.insert(vec![Value::Integer(i as i64), value.clone()])
.unwrap();
}
table
}
}
impl From<&mut List> for Table {
fn from(list: &mut List) -> Self {
let mut table = Table::new(vec!["index".to_string(), "item".to_string()]);
for (i, value) in list.items().iter().enumerate() {
if let Ok(list) = value.as_list() {
table.insert(list.items().clone()).unwrap();
} else {
table
.insert(vec![Value::Integer(i as i64), value.clone()])
.unwrap();
}
}
table
}
}
impl From<Map> for Result<Table> {
fn from(map: Map) -> Self {
let variables = map.variables()?;
let keys = variables.keys().cloned().collect();
let values = variables.values().cloned().collect();
let mut table = Table::new(keys);
table.insert(values)?;
Ok(table)
}
}
impl From<&Map> for Result<Table> {
fn from(map: &Map) -> Self {
let variables = map.variables()?;
let keys = variables.keys().cloned().collect();
let values = variables.values().cloned().collect();
let mut table = Table::new(keys);
table.insert(values)?;
Ok(table)
}
}
impl From<&mut Map> for Result<Table> {
fn from(map: &mut Map) -> Self {
let variables = map.variables()?;
let keys = variables.keys().cloned().collect();
let values = variables.values().cloned().collect();
let mut table = Table::new(keys);
table
.insert(values)
.expect("Failed to create Table from Map. This is a no-op.");
Ok(table)
}
}
impl Eq for Table {}
impl PartialEq for Table {
fn eq(&self, other: &Self) -> bool {
if self.headers != other.headers {
return false;
}
self.rows == other.rows
}
}
impl PartialOrd for Table {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.headers.partial_cmp(&other.headers)
}
}
impl Ord for Table {
fn cmp(&self, other: &Self) -> Ordering {
self.headers.cmp(&other.headers)
}
}

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