Compare commits

..

9 Commits
main ... dev

Author SHA1 Message Date
c30216f1f7 Merge branches 2023-10-05 15:34:10 -04:00
3125302e60 Write README and example; Clean up 2023-10-05 15:27:08 -04:00
2484e29df6 Restructure and clean up 2023-10-05 13:59:49 -04:00
5c8686f16f Update README 2023-10-05 12:08:04 -04:00
0dbaadf8c6 Update README 2023-10-05 12:04:02 -04:00
e55e2962c4 Remove mistake 2023-10-05 11:40:48 -04:00
fca650229b Begin rewriting README 2023-10-05 11:39:19 -04:00
19f600cd24 Add tree sitter submodule 2023-09-27 22:52:03 -04:00
1f11904065 Begin tree sitter implementation 2023-09-21 05:19:06 -04:00
163 changed files with 8845 additions and 61199 deletions

2
.gitignore vendored
View File

@ -1,2 +1,2 @@
target/
node_modules/
target/

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "tree-sitter-dust"]
path = tree-sitter-dust
url = ssh://git@git.jeffa.io:22022/jeff/tree-sitter-dust.git

3916
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,50 +1,51 @@
[package]
name = "dust-lang"
description = "General purpose programming language"
version = "0.4.2"
version = "0.1.1"
description = "Data-oriented programming language and interactive shell."
authors = ["jeff <dev@jeffa.io.com>"]
repository = "https://git.jeffa.io/jeff/dust.git"
edition = "2021"
homepage = "https://git.jeffa.io/jeff/dust"
readme = "README.md"
license = "MIT"
authors = ["Jeff Anderson"]
edition = "2018"
default-run = "dust"
[[bin]]
name = "dust"
path = "src/main.rs"
[profile.dev]
opt-level = 1
[profile.dev.package."*"]
opt-level = 3
[[bin]]
name = "gui"
[dependencies]
clap = { version = "4.4.4", features = ["derive"] }
csv = "1.2.2"
libc = "0.2.148"
log = "0.4.20"
rand = "0.8.5"
rayon = "1.8.0"
reqwest = { version = "0.11.20", features = ["blocking", "json"] }
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.107"
toml = "0.8.1"
tree-sitter = "0.20.10"
enum-iterator = "1.4.1"
env_logger = "0.10"
reedline = { version = "0.28.0", features = ["clipboard", "sqlite"] }
crossterm = "0.27.0"
nu-ansi-term = "0.49.0"
humantime = "2.1.0"
stanza = "0.5.1"
colored = "2.1.0"
lyneate = "0.2.1"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
env_logger = "0.10"
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2", features = ["js"] }
wasm-bindgen-futures = "0.4"
[lib]
name = "dust_lib"
path = "src/lib.rs"
[build-dependencies]
cc = "1.0"
cc = "*"
[dependencies]
tree-sitter = "0.20.10"
tree-sitter-rust = "0.20.3"
rand = "0.8.5"
chrono = "0.4.26"
trash = "3.0.3"
rayon = "1.7.0"
serde = { version = "1.0.171", features = ["derive"] }
sysinfo = "0.29.6"
toml = "0.7.6"
toml_edit = "0.19.14"
comfy-table = "7.0.1"
clap = { version = "4.3.19", features = ["derive"] }
git2 = "0.17.2"
csv = "1.2.2"
json = "0.12.4"
reqwest = { version = "0.11.18", features = ["blocking", "json"] }
serde_json = "1.0.104"
egui_extras = "0.22.0"
rustyline = { version = "12.0.0", features = ["with-file-history", "derive"] }
ansi_term = "0.12.1"
iced = "0.10.0"
egui = "0.22.0"
eframe = "0.22.0"
env_logger = "0.10.0"
once_cell = "1.18.0"

264
README.md
View File

@ -1,100 +1,222 @@
# Dust
High-level programming language with effortless concurrency, automatic memory management, type safety and strict error handling.
Dust is a data-oriented 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 expression-based, has first-class functions, lexical scope and lightweight syntax. Dust's grammar is formally defined in code and its minimalism is in large part due to its tree sitter parser, which is lightning-fast, accurate and thoroughly tested.
![Dust version of an example from The Rust Programming Language.](https://git.jeffa.io/jeff/dust/raw/branch/main/docs/assets/example_0.png)
A basic dust program:
```dust
output { "Hello world!" }
```
Dust can do two (or more) things at the same time with effortless concurrency:
```dust
run <
function { output 'will this one finish first?' }
function { output 'or will this one?' }
>
```
Dust can do amazing things with data. To load CSV data, isolate a column and render it as a line plot in a GUI window:
```dust
read_file("examples/assets/faithful.csv")
-> from_csv
-> rows
-> transform <{item.1}>
-> plot
```
Dust is also a minimal, obvious data format. It is easier to write than JSON and easier to read than TOML and YAML. However, because it is a programming language, it is able to self-reference, perform calculations or load external data.
```dust
foo = "bar"
numbers = [1 2 3 4]
truths = {
dust = "the best thing ever"
favorite_number = numbers.3
another_number = numbers.0 + numbers.1
}
old_faithful_data = read_file { "faithful.csv" }
```
<!--toc:start-->
- [Dust](#dust)
- [Features](#features)
- [Easy to Read and Write](#easy-to-read-and-write)
- [Effortless Concurrency](#effortless-concurrency)
- [Helpful Errors](#helpful-errors)
- [Static analysis](#static-analysis)
- [Debugging](#debugging)
- [Automatic Memory Management](#automatic-memory-management)
- [Error Handling](#error-handling)
- [Installation and Usage](#installation-and-usage)
- [Usage](#usage)
- [Installation](#installation)
- [Implementation](#implementation)
- [Contributing](#contributing)
- [The Dust Programming Language](#the-dust-programming-language)
- [Declaring Variables](#declaring-variables)
- [Integers and Floats](#integers-and-floats)
- [Lists](#lists)
- [Maps](#maps)
- [Tables](#tables)
- [Functions](#functions)
- [Empty Values](#empty-values)
<!--toc:end-->
## Features
### Easy to Read and Write
- Simplicity: Dust is designed to be easy to learn and powerful to use, without compromising either.
- Speed: Dust is built on [Tree Sitter] and [Rust] to prioritize performance and correctness.
- Data format: Dust is data-oriented, so first and foremost it makes a great language for defining data.
- Pipelines: Like a pipe in bash, dust features the yield `->` operator.
- 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.
Dust has simple, easy-to-learn syntax.
## Usage
```js
output('Hello world!')
```
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.
### Effortless Concurrency
Write multi-threaded code as easily as you would write code for a single thread.
```js
async {
output('Will this one print first?')
output('Or will this one?')
output('Who knows! Each "output" will run in its own thread!')
}
```
### Helpful Errors
Dust shows you exactly where your code went wrong and suggests changes.
![Example of syntax error output.](https://git.jeffa.io/jeff/dust/raw/branch/main/docs/assets/syntax_error.png)
### Static analysis
Your code is always validated for safety before it is run.
![Example of type error output.](https://git.jeffa.io/jeff/dust/raw/branch/main/docs/assets/type_error.png)
Dust
### Debugging
Just set the environment variable `DUST_LOG=info` and Dust will tell you exactly what your code is doing while it's doing it. If you set `DUST_LOG=trace`, it will output detailed logs about parsing, abstraction, validation, memory management and runtime. Here are some of the logs from the end of a simple [fizzbuzz example](https://git.jeffa.io/jeff/dust/src/branch/main/examples/fizzbuzz.ds).
![Example of debug output.](https://git.jeffa.io/jeff/dust/raw/branch/main/docs/assets/debugging.png)
### Automatic Memory Management
Thanks to static analysis, Dust knows exactly how many times each variable is used. This allows Dust to free memory as soon as the variable will no longer be used, without any help from the user.
### Error Handling
Runtime errors are no problem with Dust. The `Result` type represents the output of an operation that might fail. The user must decide what to do in the case of an error.
To get help with the shell you can use the "help" tool.
```dust
match io:stdin() {
Result::Ok(input) -> output("We read this input: " + input)
Result::Error(message) -> output("We got this error: " + message)
help # Returns a table will all tool info.
help {"random"} # Returns a table with info on tools in the specified group.
# The above is simply a shorthand for this:
help -> where { input, function <tool> { tool == "random" } }
```
## 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 run `cargo run` to start the shell. To see other command line options, use `cargo run -- --help`.
## Implementation
Dust is formally defined as a Tree Sitter grammar in the tree-sitter-dust module. 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. Dust does not use Javascript at runtime.
Tree Sitter generates a concrete syntax tree, which the Rust code maps to an abstract syntax tree by traversing each node once. Tree sitter is fast enough to be updated on every keystroke which is perfect for a data-oriented language like Dust because it allows only the relevant sections to be re-evaluated and the result displayed instantly.
## Contributing
Please submit any thoughts or suggestions for this project. For instructions on the internal API, see the library documentation. Implementation tests are written in dust and are run by a corresponding rust test so dust tests will be run when `cargo test` is called.
## The Dust Programming Language
It should not take long for a new user to learn the language, especially with the assistance of the shell. If your editor supports tree sitter, you can use [tree-sitter-dust] for syntax highlighting and completion support. Aside from this guide, the best way to learn dust is to read the examples and tests to get a better idea of what dust can do.
### Declaring Variables
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:
- string
- integer
- floating point value
- boolean
- list
- map
- table
- function
Here are some examples of variables in dust.
```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`
}
```
## Installation and Usage
Note that strings can be wrapped with any kind of quote: single, double or backticks. Numbers are always integers by default. And commas are optional in lists.
There are two ways to compile Dust. **It is best to clone the repository and compile the latest code**, otherwise the program may be a different version than the one shown on GitHub. Either way, you must have `rustup`, `cmake` and a C compiler installed.
### Integers and Floats
To install from the git repository:
Integer and floating point values are dust's numeric types. Any whole number (i.e. without a decimal) is an integer. Floats are declared by adding a single decimal to or number. If you divide integers or do any kind of math with a float, you will create a float value.
```fish
git clone https://git.jeffa.io/jeff/dust
cd dust
cargo run --release
### Lists
Lists are sequential collections. They can be built by grouping values with square brackets. Commas are optional. Values can be indexed by their position to access their contents. Their contents can be indexed using dot notation with an integer. Dust lists are zero-indexed.
```dust
list = [true 41 "Ok"]
assert_equal { list.0 true }
the_answer = list.1 + 1
assert_equal { the_answer, 42 }
```
To install with cargo:
### Maps
```fish
cargo install dust-lang
dust
Maps are flexible collections with arbitrary key-value pairs, similar to JSON objects. Under the hood, all of dust's runtime variables are stored in a map, so, as with variables, the key is always a string. A map is created with a pair of curly braces and its entries and just variables declared inside those braces. Map contents can be accessed using dot notation and a value's key.
```dust
reminder = {
message = "Buy milk"
tags = ["groceries", "home"]
}
output { reminder.message }
```
## Benchmarks
### Tables
## Development Status
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. The column names are followed by a pair of curly braces filled with list values. Each list will become a row in the new table.
Currently, Dust is being prepared for version 1.0. Until then, there may be breaking changes to the language and CLI.
```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 where 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 { count { animals }, 6 };
```
### 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 and use a set of curly braces to pass arguments (or leave them empty to pass nothing). 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. Use your best judgement, the parser will disambiguate any valid syntax.
```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 }
}
```
### Empty Values
Dust does not have a null type. Instead, it uses the "empty" type to represent a lack of any other value. There is no syntax to create this value: it is only used by the interpreter. Note that Dust does have the NaN value, which is a floating point value that must exist in order for floats to work as intended. Integers will never be NaN and no value will ever be null or undefined.
[dnf]: https://dnf.readthedocs.io/en/latest/index.html
[evalexpr]: https://github.com/ISibboI/evalexpr
[rustup]: https://rustup.rs

View File

@ -1,17 +1,40 @@
fn main() {
let src_dir = std::path::Path::new("tree-sitter-dust/src");
let mut c_config = cc::Build::new();
let src_dir = std::path::Path::new("src");
c_config.include(src_dir);
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");
// If your language uses an external scanner written in C,
// then include this block of code:
/*
let scanner_path = src_dir.join("scanner.c");
c_config.file(&scanner_path);
println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
*/
c_config.compile("parser");
println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap());
// If your language uses an external scanner written in C++,
// then include this block of code:
/*
let mut cpp_config = cc::Build::new();
cpp_config.cpp(true);
cpp_config.include(&src_dir);
cpp_config
.flag_if_supported("-Wno-unused-parameter")
.flag_if_supported("-Wno-unused-but-set-variable");
let scanner_path = src_dir.join("scanner.cc");
cpp_config.file(&scanner_path);
cpp_config.compile("scanner");
println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
*/
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,515 +0,0 @@
# Dust Language Reference
!!! This is a **work in progress** and has incomplete information. !!!
This is an in-depth description of the syntax and abstractions used by the Dust language. It is not
necessary to read or understand all of it before you start using Dust. Instead, refer to it when
you need help with the syntax or understanding how the code is run.
Each section of this document corresponds to a node in the concrete syntax tree. Creating this tree
is the first step in interpreting Dust code. Second, the syntax tree is traversed and an abstract
tree is generated. Each node in the syntax tree corresponds to a node in the abstract tree. Third,
the abstract tree is verified to ensure that it will not generate any values that violate the type
restrictions. Finally, the abstract tree is run, beginning at the [root](#root).
You may reference the [grammar file](tree-sitter-dust/grammar.js) and the [Tree Sitter docs]
(https://tree-sitter.github.io/) while reading this guide to understand how the language is parsed.
<!--toc:start-->
- [Dust Language Reference](#dust-language-reference)
- [Root](#root)
- [Values](#values)
- [Boolean](#boolean)
- [Integer](#integer)
- [Float](#float)
- [Range](#range)
- [String](#string)
- [List](#list)
- [Map](#map)
- [Function](#function)
- [Option](#option)
- [Structure](#structure)
- [Types](#types)
- [Basic Types](#basic-types)
- [Number](#number)
- [Any](#any)
- [None](#none)
- [List Type](#list-type)
- [Map Type](#map-type)
- [Iter](#iter)
- [Function Type](#function-type)
- [Option Type](#option-type)
- [Custom Types](#custom-types)
- [Statements](#statements)
- [Assignment](#assignment)
- [Blocks](#blocks)
- [Synchronous Blocks](#synchronous-blocks)
- [Asynchronous Blocks](#asynchronous-blocks)
- [Break](#break)
- [For Loop](#for-loop)
- [While Loop](#while-loop)
- [If/Else](#ifelse)
- [Match](#match)
- [Pipe](#pipe)
- [Expression](#expression)
- [Expressions](#expressions)
- [Identifier](#identifier)
- [Index](#index)
- [Logic](#logic)
- [Math](#math)
- [Value](#value)
- [New](#new)
- [Command](#command)
- [Built-In Values](#built-in-values)
- [Comments](#comments)
<!--toc:end-->
## Root
The root node represents all of the source code. It is a sequence of [statements](#statements) that
are executed synchronously, in order. The output of the program is always the result of the final
statement or the first error encountered.
## Values
There are ten kinds of value in Dust. Some are very simple and are parsed directly from the source
code, some are collections and others are used in special ways, like functions and structures. All
values can be assinged to an [identifier](#identifiers).
Dust does not have a null type. Absent values are represented with the `none` value, which is a
kind of [option](#option). You may not create a variable without a value and no variable can ever
be in an 'undefined' state during execution.
### Boolean
Booleans are true or false. They are represented by the literal tokens `true` and `false`.
### Integer
Integers are whole numbers that may be positive, negative or zero. Internally, an integer is a
signed 64-bit value.
```dust
42
```
Integers always **overflow** when their maximum or minimum value is reached. Overflowing means that
if the value is too high or low for the 64-bit integer, it will wrap around. You can use the built-
in values `int:max` and `int:min` to get the highest and lowest possible values.
```dust
assert_equal(int:max + 1, int:min)
assert_equal(int:min - 1, int:max)
```
### Float
A float is a numeric value with a decimal. Floats are 64-bit and, like integers, will **overflow**
at their bounds.
```dust
42.0
```
### Range
A range represents a contiguous sequence of integers. Dust ranges are **inclusive** so both the high
and low bounds will be represented.
```dust
0..100
```
### String
A string is a **utf-8** sequence used to represent text. Strings can be wrapped in single or double quotes as well as backticks.
```dust
'42'
"42"
`42`
'forty-two'
```
### List
A list is **collection** of values stored as a sequence and accessible by [indexing](#index) their position with an integer. Lists indexes begin at zero for the first item.
```dust
[ 42 'forty-two' ]
[ 123, 'one', 'two', 'three' ]
```
Note that the commas are optional, including trailing commas.
```dust
[1 2 3 4 5]:2
# Output: 3
```
### Map
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 a colon `:`. Commas may optionally be included after the key-value
pairs.
```dust
reminder = {
message = "Buy milk"
tags = ["groceries", "home"]
}
reminder:message
# Output: Buy milk
```
Internally a map is represented by a B-tree. The implicit advantage of using a B-tree instead of a
hash map is that a B-tree is sorted and therefore can be easily compared to another. Maps are also
used by the interpreter as the data structure for holding variables. You can even inspect the active
**execution context** by calling the built-in `context()` function.
The map stores each [identifier](#identifiers)'s key with a value and the value's type. For internal
use by the interpreter, a type can be set to a key without a value. This makes it possible to check
the types of values before they are computed.
### Function
A function encapsulates a section of the abstract tree so that it can be run seperately and with
different arguments. The function body is a [block](#block), so adding `async` will cause the body
to run like any other `async` block. Unlike some languages, there are no concepts like futures or
async functions in Dust.
Functions are **first-class values** in Dust, so they can be assigned to variables like any other
value.
```dust
# This simple function has no arguments and no return value.
say_hi = () <none> {
output("hi") # The "output" function is a built-in that prints to stdout.
}
# This function has one argument and will return a value.
add_one = (number <num>) <num> {
number + 1
}
say_hi()
assert_equal(add_one(3), 4)
```
Functions can also be **anonymous**. This is useful for using **callbacks** (i.e. functions that are
called by another function).
```dust
# Use a callback to retain only the numeric characters in a string.
str:retain(
'a1b2c3'
(char <str>) <bool> {
is_some(int:parse(char))
}
)
```
### Option
An option represents a value that may not be present. It has two variants: **some** and **none**.
```dust
say_something = (message <option(str)>) <str> {
either_or(message, "hiya")
}
say_something(some("goodbye"))
# goodbye
say_something(none)
# hiya
```
Dust includes built-in functions to work with option values: `is_none`, `is_some` and `either_or`.
### Structure
A structure is a **concrete type value**. It is a value, like any other, and can be [assigned]
(#assignment) to an [identifier](#identifier). It can then be instantiated as a [map](#map) that
will only allow the variables present in the structure. Default values may be provided for each
variable in the structure, which will be propagated to the map it creates. Values without defaults
must be given a value during instantiation.
```dust
struct User {
name <str>
email <str>
id <int> = generate_id()
}
bob = new User {
name = "Bob"
email = "bob@example.com"
}
# The variable "bob" is a structured map.
```
A map created by using [new](#new) is called a **structured map**. In other languages it may be
called a "homomorphic mapped type". Dust will generate errors if you try to set any values on the
structured map that are not allowed by the structure.
## Types
Dust enforces strict type checking. To make the language easier to write, **type inference** is used
to allow variables to be declared without specifying the type. Instead, the interpreter will figure
it out and set the strictest type possible.
To make the type-setting syntax easier to distinguish from the rest of your code, a **type
specification** is wrapped in pointed brackets. So variable assignment using types looks like this:
```dust
my_float <float> = 666.0
```
### Basic Types
The simple types, and their notation are:
- boolean `bool`
- integer `int`
- float `float`
- string `str`
### Number
The `num` type may represent a value of type `int` or `float`.
### Any
The `any` type does not enforce type bounds.
### None
The `none` type indicates that no value should be found after executing the statement or block, with
one expection: the `none` variant of the `option` type.
### List Type
A list's contents can be specified to create type-safe lists. The `list(str)` type would only allow
string values. Writing `list` without the parentheses and content type is equivalent to writing
`list(any)`.
### Map Type
The `map` type is unstructured and can hold any key-value pair.
### Iter
The `iter` type refers to types that can be used with a [for loop](#for-loop). These include `list`,
`range`, `string` and `map`.
### Function Type
A function's type specification is more complex than other types. A function value must always have
its arguments and return type specified when the **function value** is created.
```dust
my_function = (number <int>, text <str>) <none> {
output(number)
output(text)
}
```
But what if we need to specify a **function type** without creating the function value? This is
necessary when using callbacks or defining structures that have functions set at instantiation.
```dust
use_adder = (adder <(int) -> int>, number <int>) -> <int> {
adder(number)
}
use_adder(
(i <int>) <int> { i + 2 }
40
)
# Output: 42
```
```dust
struct Message {
send_n_times <(str, int) -> none>
}
stdout_message = new Message {
send_n_times = (content <str>, n <int>) <none> {
for _ in 0..n {
output(content)
}
}
}
```
### Option Type
The `option(type)` type is expected to be either `some(value)` or `none`. The type of the value
inside the `some` is always specified.
```dust
result <option(str)> = none
for file in fs:read_dir("./") {
if file:size > 100 {
result = some(file:path)
break
}
}
output(result)
```
```dust
get_line_break_index(text <str>) <some(int)> {
str:find(text, '\n')
}
```
### Custom Types
Custom types such as **structures** are referenced by their variable identifier.
```dust
File = struct {
path <str>
size <int>
type <str>
}
print_file_info(file <File>) <none> {
info = file:path
+ '\n'
+ file:size
+ '\n'
+ file:type
output(info)
}
```
## Statements
TODO
### Assignment
TODO
### Blocks
TODO
#### Synchronous Blocks
TODO
#### Asynchronous Blocks
```dust
# An async block will run each statement in its own thread.
async {
output(random_integer())
output(random_float())
output(random_boolean())
}
```
```dust
data = async {
output("Reading a file...")
read("examples/assets/faithful.csv")
}
```
### Break
TODO
### For Loop
TODO
```dust
list = [ 1, 2, 3 ]
for number in list {
output(number + 1)
}
```
### While Loop
TODO
A **while** loop continues until a predicate is false.
```dust
i = 0
while i < 10 {
output(i)
i += 1
}
```
### If/Else
TODO
### Match
TODO
### Pipe
TODO
### Expression
TODO
## Expressions
TODO
#### Identifier
TODO
#### Index
TODO
#### Logic
TODO
#### Math
TODO
#### Value
TODO
#### New
TODO
#### Command
TODO
## Built-In Values
TODO
## Comments
TODO

File diff suppressed because it is too large Load Diff

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,19 +0,0 @@
create_random_numbers = (count <int>) <none> {
numbers = []
while length(numbers) < count {
numbers += random:integer()
}
output("Made " + length(numbers) + " numbers.")
}
output("This will print first.")
async {
create_random_numbers(1000)
create_random_numbers(100)
create_random_numbers(10)
}
output("This will print last.")

View File

@ -1,17 +0,0 @@
async {
{
^echo 'Starting 1...'
^sleep 1
^echo 'Finished 1.'
}
{
^echo 'Starting 2...'
^sleep 2
^echo 'Finished 2.'
}
{
^echo 'Starting 3...'
^sleep 3
^echo 'Finished 3.'
}
}

View File

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

View File

@ -1,53 +0,0 @@
cards = {
rooms = ['Library' 'Kitchen' 'Conservatory']
suspects = ['White' 'Green' 'Scarlett']
weapons = ['Rope' 'Lead_Pipe' 'Knife']
}
is_ready_to_solve = (cards <map>) <bool> {
(length(cards:suspects) == 1)
&& (length(cards:rooms) == 1)
&& (length(cards:weapons) == 1)
}
remove_card = (cards <map>, opponent_card <str>) <none> {
cards:rooms -= opponent_card
cards:suspects -= opponent_card
cards:weapons -= opponent_card
}
make_guess = (cards <map>, current_room <str>) <none> {
if is_ready_to_solve(cards) {
output(
'I accuse '
+ cards:suspects:0
+ ' in the '
+ cards:rooms:0
+ ' with the '
+ cards:weapons:0
+ '!'
)
} else {
output(
'I question '
+ random:from(cards:suspects)
+ ' in the '
+ current_room
+ ' with the '
+ random:from(cards:weapons)
+ '.'
)
}
}
take_turn = (cards <map>, opponent_card <str>, current_room <str>) <none> {
remove_card(cards opponent_card)
make_guess(cards current_room)
}
take_turn(cards 'Rope' 'Kitchen')
take_turn(cards 'Library' 'Kitchen')
take_turn(cards 'Conservatory' 'Kitchen')
take_turn(cards 'White' 'Kitchen')
take_turn(cards 'Green' 'Kitchen')
take_turn(cards 'Knife' 'Kitchen')

View File

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

View File

@ -1,9 +1,11 @@
fib = (i <int>) <int> {
if i <= 1 {
1
} else {
fib(i - 1) + fib(i - 2)
}
fibonacci = function <number> {
if number <= 0 then 0
else if number == 1 then 1
else
first = fibonacci { number - 2 }
second = fibonacci { number - 1 }
first + second
}
fib(8)

View File

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

View File

@ -1,26 +0,0 @@
# This is a Dust version of an example from the Rust Book.
#
# https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html
output("Guess the number.")
secret_number = int:random_range(0..=100)
loop {
output("Please input your guess.")
input = io:stdin():expect("Failed to read line.")
guess = int:parse(input)
output("You guessed: " + guess)
match cmp(guess, secret_number) {
Ordering::Less -> output("Too small!")
Ordering::Greater -> output("Too big!")
Ordering::Equal -> {
output("You win!")
break
}
}
}

View File

@ -1 +0,0 @@
output('Hello, world!')

View File

@ -1,12 +0,0 @@
data = json:parse(fs:read_file('examples/assets/jq_data.json'))
new_data = []
for commit_data in data as collection {
new_data += {
message = commit_data:commit:message
name = commit_data:commit:committer:name
}
}
new_data

View File

@ -1,13 +0,0 @@
stuff = [
random:integer()
random:integer()
random:integer()
random:float()
random:float()
random:float()
random:boolean()
random:boolean()
random:boolean()
]
random:from(stuff)

View File

@ -1,19 +0,0 @@
raw_data = fs:read_file('examples/assets/seaCreatures.json')
sea_creatures = json:parse(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

View File

@ -1,35 +0,0 @@
#!/usr/bin/fish
# This script is has the following prerequisites (aside from fish):
# - hyperfine
# - dust (can be installed with "cargo install dust-lang")
# - jq
# - nodejs
# - nushell
# - dielectron.json (can be downloaded from https://opendata.cern.ch/record/304)
hyperfine \
--shell none \
--parameter-list data_path examples/assets/seaCreatures.json \
--warmup 3 \
"target/release/dust -c 'length(json:parse(fs:read_file(\"{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'"
hyperfine \
--shell none \
--parameter-list data_path examples/assets/jq_data.json \
--warmup 3 \
"target/release/dust -c 'length(json:parse(fs:read_file(\"{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'"
hyperfine \
--shell none \
--parameter-list data_path dielectron.json \
--warmup 3 \
"target/release/dust -c 'length(json:parse(fs:read_file(\"{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'"

View File

@ -1,8 +0,0 @@
#!/usr/bin/fish
# Build the project in debug mode.
cd tree-sitter-dust/
tree-sitter generate --debug-build --no-bindings
cd ..
cargo build

View File

@ -1,8 +0,0 @@
#!/bin/fish
# Build the project in release mode.
cd tree-sitter-dust/
tree-sitter generate --no-bindings
cd ..
cargo build --release

View File

@ -1,9 +0,0 @@
#!/usr/bin/fish
# Build the project in debug mode.
cd tree-sitter-dust/
tree-sitter generate --debug-build --no-bindings
tree-sitter test
cd ..
cargo test

View File

@ -1,131 +0,0 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Expression, Format, List, SourcePosition, Type, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct As {
expression: Expression,
r#type: Type,
position: SourcePosition,
}
impl AbstractTree for As {
fn from_syntax(node: Node, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("as", node)?;
let expression_node = node.child(0).unwrap();
let expression = Expression::from_syntax(expression_node, source, context)?;
let type_node = node.child(2).unwrap();
let r#type = Type::from_syntax(type_node, source, context)?;
Ok(As {
expression,
r#type,
position: SourcePosition::from(node.range()),
})
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(self.r#type.clone())
}
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
let initial_type = self.expression.expected_type(context)?;
if self.r#type.accepts(&initial_type) {
return Ok(());
}
if let Type::ListOf(item_type) = &self.r#type {
match &initial_type {
Type::ListOf(expected_item_type) => {
println!("{item_type} {expected_item_type}");
if !item_type.accepts(&expected_item_type) {
return Err(ValidationError::TypeCheck {
expected: self.r#type.clone(),
actual: initial_type.clone(),
position: self.position,
});
}
}
Type::String => {
if let Type::String = item_type.as_ref() {
} else {
return Err(ValidationError::ConversionImpossible {
initial_type,
target_type: self.r#type.clone(),
});
}
}
Type::Any => {
// Do no validation when converting from "any" to a list.
// This effectively defers to runtime behavior, potentially
// causing a runtime error.
}
_ => {
return Err(ValidationError::ConversionImpossible {
initial_type,
target_type: self.r#type.clone(),
})
}
}
}
Ok(())
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let value = self.expression.run(source, context)?;
let converted_value = if self.r#type.accepts(&value.r#type()?) {
return Ok(value);
} else if let Type::ListOf(_) = self.r#type {
match value {
Value::List(list) => Value::List(list),
Value::String(string) => {
let chars = string
.chars()
.map(|char| Value::String(char.to_string()))
.collect();
Value::List(List::with_items(chars))
}
_ => {
return Err(RuntimeError::ConversionImpossible {
from: value.r#type()?,
to: self.r#type.clone(),
position: self.position.clone(),
});
}
}
} else if let Type::Integer = self.r#type {
match value {
Value::Integer(integer) => Value::Integer(integer),
Value::Float(float) => Value::Integer(float as i64),
_ => {
return Err(RuntimeError::ConversionImpossible {
from: value.r#type()?,
to: self.r#type.clone(),
position: self.position.clone(),
})
}
}
} else {
todo!()
};
Ok(converted_value)
}
}
impl Format for As {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -1,186 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
context::Context,
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, AssignmentOperator, Format, Function, Identifier, SourcePosition, Statement,
SyntaxNode, Type, TypeSpecification, Value,
};
/// Variable assignment, including add-assign and subtract-assign operations.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Assignment {
identifier: Identifier,
type_specification: Option<TypeSpecification>,
operator: AssignmentOperator,
statement: Statement,
syntax_position: SourcePosition,
}
impl AbstractTree for Assignment {
fn from_syntax(
syntax_node: SyntaxNode,
source: &str,
context: &Context,
) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("assignment", syntax_node)?;
let child_count = syntax_node.child_count();
let identifier_node = syntax_node.child(0).unwrap();
let identifier = Identifier::from_syntax(identifier_node, source, context)?;
let type_node = syntax_node.child(1).unwrap();
let type_specification = if type_node.kind() == "type_specification" {
Some(TypeSpecification::from_syntax(type_node, source, context)?)
} else {
None
};
let operator_node = syntax_node.child(child_count - 2).unwrap();
let operator = AssignmentOperator::from_syntax(operator_node, source, context)?;
let statement_node = syntax_node.child(child_count - 1).unwrap();
let statement = Statement::from_syntax(statement_node, source, context)?;
Ok(Assignment {
identifier,
type_specification,
operator,
statement,
syntax_position: syntax_node.range().into(),
})
}
fn validate(&self, source: &str, context: &Context) -> Result<(), ValidationError> {
if let AssignmentOperator::Equal = self.operator {
let r#type = if let Some(definition) = &self.type_specification {
definition.inner().clone()
} else {
self.statement.expected_type(context)?
};
log::info!("Setting type: {} <{}>", self.identifier, r#type);
context.set_type(self.identifier.clone(), r#type)?;
}
if let Some(type_specification) = &self.type_specification {
match self.operator {
AssignmentOperator::Equal => {
let expected = type_specification.inner();
let actual = self.statement.expected_type(context)?;
if !expected.accepts(&actual) {
return Err(ValidationError::TypeCheck {
expected: expected.clone(),
actual,
position: self.syntax_position,
});
}
}
AssignmentOperator::PlusEqual => {
if let Type::ListOf(expected) = type_specification.inner() {
let actual = self.identifier.expected_type(context)?;
if !expected.accepts(&actual) {
return Err(ValidationError::TypeCheck {
expected: expected.as_ref().clone(),
actual,
position: self.syntax_position,
});
}
} else {
let expected = type_specification.inner();
let actual = self.identifier.expected_type(context)?;
if !expected.accepts(&actual) {
return Err(ValidationError::TypeCheck {
expected: expected.clone(),
actual,
position: self.syntax_position,
});
}
}
}
AssignmentOperator::MinusEqual => todo!(),
}
} else {
match self.operator {
AssignmentOperator::Equal => {}
AssignmentOperator::PlusEqual => {
if let Type::ListOf(expected) = self.identifier.expected_type(context)? {
let actual = self.statement.expected_type(context)?;
if !expected.accepts(&actual) {
return Err(ValidationError::TypeCheck {
expected: expected.as_ref().clone(),
actual,
position: self.syntax_position,
});
}
}
}
AssignmentOperator::MinusEqual => todo!(),
}
}
self.statement.validate(source, context)?;
Ok(())
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let right = self.statement.run(source, context)?;
let new_value = match self.operator {
AssignmentOperator::PlusEqual => {
let left = self.identifier.run(source, context)?;
left.add(right, self.syntax_position)?
}
AssignmentOperator::MinusEqual => {
if let Some(left) = context.get_value(&self.identifier)? {
left.subtract(right, self.syntax_position)?
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::VariableIdentifierNotFound(self.identifier.clone()),
));
}
}
AssignmentOperator::Equal => right,
};
if let Value::Function(Function::ContextDefined(function_node)) = &new_value {
function_node
.context()
.set_value(self.identifier.clone(), new_value.clone())?;
}
log::info!("RUN assignment: {} = {}", self.identifier, new_value);
context.set_value(self.identifier.clone(), new_value)?;
Ok(Value::none())
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::None)
}
}
impl Format for Assignment {
fn format(&self, output: &mut String, indent_level: u8) {
self.identifier.format(output, indent_level);
if let Some(type_specification) = &self.type_specification {
type_specification.format(output, indent_level);
}
output.push(' ');
self.operator.format(output, indent_level);
output.push(' ');
self.statement.format(output, 0);
}
}

View File

@ -1,62 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, SyntaxNode, Type, Value,
};
/// Operators that be used in an assignment statement.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum AssignmentOperator {
Equal,
PlusEqual,
MinusEqual,
}
impl AbstractTree for AssignmentOperator {
fn from_syntax(
node: SyntaxNode,
_source: &str,
_context: &Context,
) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("assignment_operator", node)?;
let operator_node = node.child(0).unwrap();
let operator = match operator_node.kind() {
"=" => AssignmentOperator::Equal,
"+=" => AssignmentOperator::PlusEqual,
"-=" => AssignmentOperator::MinusEqual,
_ => {
return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "=, += or -=".to_string(),
actual: operator_node.kind().to_string(),
position: node.range().into(),
})
}
};
Ok(operator)
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
Ok(Value::none())
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::None)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
}
impl Format for AssignmentOperator {
fn format(&self, output: &mut String, _indent_level: u8) {
match self {
AssignmentOperator::Equal => output.push('='),
AssignmentOperator::PlusEqual => output.push_str("+="),
AssignmentOperator::MinusEqual => output.push_str("-="),
}
}
}

View File

@ -1,170 +0,0 @@
use std::{
fmt::{self, Formatter},
sync::RwLock,
};
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use crate::{
error::{rw_lock_error::RwLockError, RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, Statement, SyntaxNode, Type, Value,
};
/// Abstract representation of a block.
///
/// A block is almost identical to the root except that it must have curly
/// braces and can optionally be asynchronous. A block evaluates to the value of
/// its final statement but an async block will short-circuit if a statement
/// results in an error. Note that this will be the first statement to encounter
/// an error at runtime, not necessarilly the first statement as they are
/// written.
#[derive(Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Block {
is_async: bool,
contains_return: bool,
statements: Vec<Statement>,
}
impl Block {
pub fn contains_return(&self) -> bool {
self.contains_return
}
}
impl AbstractTree for Block {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("block", node)?;
let first_child = node.child(0).unwrap();
let is_async = first_child.kind() == "async";
let mut contains_return = false;
let statement_count = if is_async {
node.child_count() - 3
} else {
node.child_count() - 2
};
let mut statements = Vec::with_capacity(statement_count);
let block_context = Context::with_variables_from(context)?;
for index in 1..node.child_count() - 1 {
let child_node = node.child(index).unwrap();
if child_node.kind() == "statement" {
let statement = Statement::from_syntax(child_node, source, &block_context)?;
if statement.is_return() {
contains_return = true;
}
statements.push(statement);
}
}
Ok(Block {
is_async,
contains_return,
statements,
})
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
for statement in &self.statements {
statement.validate(_source, _context)?;
}
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
if self.is_async {
let statements = &self.statements;
let final_result = RwLock::new(Ok(Value::none()));
statements
.into_par_iter()
.enumerate()
.find_map_first(|(index, statement)| {
let result = statement.run(_source, _context);
let should_return = if self.contains_return {
statement.is_return()
} else {
index == statements.len() - 1
};
if should_return {
let get_write_lock = final_result.write();
match get_write_lock {
Ok(mut final_result) => {
*final_result = result;
None
}
Err(_error) => Some(Err(RuntimeError::RwLock(RwLockError))),
}
} else {
None
}
})
.unwrap_or(final_result.into_inner().map_err(|_| RwLockError)?)
} else {
for (index, statement) in self.statements.iter().enumerate() {
if statement.is_return() {
return statement.run(_source, _context);
}
if index == self.statements.len() - 1 {
return statement.run(_source, _context);
}
}
Ok(Value::none())
}
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
for (index, statement) in self.statements.iter().enumerate() {
if statement.is_return() {
return statement.expected_type(_context);
}
if index == self.statements.len() - 1 {
return statement.expected_type(_context);
}
}
Ok(Type::None)
}
}
impl Format for Block {
fn format(&self, output: &mut String, indent_level: u8) {
if self.is_async {
output.push_str("async {\n");
} else {
output.push_str("{\n");
}
for (index, statement) in self.statements.iter().enumerate() {
if index > 0 {
output.push('\n');
}
statement.format(output, indent_level + 1);
}
output.push('\n');
Block::indent(output, indent_level);
output.push('}');
}
}
impl fmt::Debug for Block {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("Block")
.field("is_async", &self.is_async)
.field("statements", &self.statements)
.finish()
}
}

View File

@ -1,76 +0,0 @@
use std::process::{self, Stdio};
use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, Type, Value,
};
/// An external program invokation.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Command {
command_text: String,
command_arguments: Vec<String>,
}
impl AbstractTree for Command {
fn from_syntax(
node: SyntaxNode,
source: &str,
_context: &Context,
) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("command", node)?;
let command_text_node = node.child(1).unwrap();
let command_text = source[command_text_node.byte_range()].to_string();
let mut command_arguments = Vec::new();
for index in 2..node.child_count() {
let text_node = node.child(index).unwrap();
let mut text = source[text_node.byte_range()].to_string();
if (text.starts_with('\'') && text.ends_with('\''))
|| (text.starts_with('"') && text.ends_with('"'))
|| (text.starts_with('`') && text.ends_with('`'))
{
text = text[1..text.len() - 1].to_string();
}
command_arguments.push(text);
}
Ok(Command {
command_text,
command_arguments,
})
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::String)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
let output = process::Command::new(&self.command_text)
.args(&self.command_arguments)
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()?
.wait_with_output()?
.stdout;
Ok(Value::String(String::from_utf8(output)?))
}
}
impl Format for Command {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -1,92 +0,0 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, EnumInstance, Format, Identifier, Type, TypeDefinition, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct EnumDefinition {
identifier: Identifier,
variants: Vec<(Identifier, Vec<Type>)>,
}
impl EnumDefinition {
pub fn new(identifier: Identifier, variants: Vec<(Identifier, Vec<Type>)>) -> Self {
Self {
identifier,
variants,
}
}
pub fn instantiate(&self, variant: Identifier, content: Option<Value>) -> EnumInstance {
EnumInstance::new(self.identifier.clone(), variant, content)
}
pub fn identifier(&self) -> &Identifier {
&self.identifier
}
pub fn variants(&self) -> &Vec<(Identifier, Vec<Type>)> {
&self.variants
}
}
impl AbstractTree for EnumDefinition {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("enum_definition", node)?;
let identifier_node = node.child(1).unwrap();
let identifier = Identifier::from_syntax(identifier_node, source, context)?;
let mut variants = Vec::new();
let mut current_identifier: Option<Identifier> = None;
let mut types = Vec::new();
for index in 3..node.child_count() - 1 {
let child = node.child(index).unwrap();
if child.kind() == "identifier" {
if let Some(identifier) = &current_identifier {
variants.push((identifier.clone(), types));
}
current_identifier = Some(Identifier::from_syntax(child, source, context)?);
types = Vec::new();
}
if child.kind() == "type" {
let r#type = Type::from_syntax(child, source, context)?;
types.push(r#type);
}
}
Ok(EnumDefinition {
identifier,
variants,
})
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::None)
}
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
context.set_definition(self.identifier.clone(), TypeDefinition::Enum(self.clone()))?;
self.identifier.validate(_source, context)?;
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
Ok(Value::none())
}
}
impl Format for EnumDefinition {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -1,70 +0,0 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, Identifier, Type, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct EnumPattern {
name: Identifier,
variant: Identifier,
inner_identifier: Option<Identifier>,
}
impl EnumPattern {
pub fn name(&self) -> &Identifier {
&self.name
}
pub fn variant(&self) -> &Identifier {
&self.variant
}
pub fn inner_identifier(&self) -> &Option<Identifier> {
&self.inner_identifier
}
}
impl AbstractTree for EnumPattern {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("enum_pattern", node)?;
let enum_name_node = node.child(0).unwrap();
let name = Identifier::from_syntax(enum_name_node, source, context)?;
let enum_variant_node = node.child(2).unwrap();
let variant = Identifier::from_syntax(enum_variant_node, source, context)?;
let inner_identifier = if let Some(child) = node.child(4) {
Some(Identifier::from_syntax(child, source, context)?)
} else {
None
};
Ok(EnumPattern {
name,
variant,
inner_identifier,
})
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::None)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
Ok(Value::none())
}
}
impl Format for EnumPattern {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -1,120 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
value_node::ValueNode,
AbstractTree, As, Command, Context, Format, FunctionCall, Identifier, Index, Logic, Math,
SyntaxNode, Type, Value,
};
/// Abstract representation of an expression statement.
///
/// Unlike statements, which can involve complex logic, an expression is
/// expected to evaluate to a value. However, an expression can still contain
/// nested statements and may evaluate to an empty value.
#[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(Box<FunctionCall>),
Command(Command),
As(Box<As>),
}
impl AbstractTree for Expression {
fn from_syntax(
node: SyntaxNode,
source: &str,
_context: &Context,
) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("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() {
"as" => Expression::As(Box::new(As::from_syntax(child, source, _context)?)),
"value" => Expression::Value(ValueNode::from_syntax(child, source, _context)?),
"identifier" => {
Expression::Identifier(Identifier::from_syntax(child, source, _context)?)
}
"index" => Expression::Index(Box::new(Index::from_syntax(child, source, _context)?)),
"math" => Expression::Math(Box::new(Math::from_syntax(child, source, _context)?)),
"logic" => Expression::Logic(Box::new(Logic::from_syntax(child, source, _context)?)),
"function_call" => Expression::FunctionCall(Box::new(FunctionCall::from_syntax(
child, source, _context,
)?)),
"command" => Expression::Command(Command::from_syntax(child, source, _context)?),
_ => {
return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "value, identifier, index, math, logic, function call, as or command"
.to_string(),
actual: child.kind().to_string(),
position: node.range().into(),
})
}
};
Ok(expression)
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
match self {
Expression::Value(value_node) => value_node.expected_type(_context),
Expression::Identifier(identifier) => identifier.expected_type(_context),
Expression::Math(math) => math.expected_type(_context),
Expression::Logic(logic) => logic.expected_type(_context),
Expression::FunctionCall(function_call) => function_call.expected_type(_context),
Expression::Index(index) => index.expected_type(_context),
Expression::Command(command) => command.expected_type(_context),
Expression::As(r#as) => r#as.expected_type(_context),
}
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
match self {
Expression::Value(value_node) => value_node.validate(_source, _context),
Expression::Identifier(identifier) => identifier.validate(_source, _context),
Expression::Math(math) => math.validate(_source, _context),
Expression::Logic(logic) => logic.validate(_source, _context),
Expression::FunctionCall(function_call) => function_call.validate(_source, _context),
Expression::Index(index) => index.validate(_source, _context),
Expression::Command(command) => command.validate(_source, _context),
Expression::As(r#as) => r#as.validate(_source, _context),
}
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
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::Index(index) => index.run(_source, _context),
Expression::Command(command) => command.run(_source, _context),
Expression::As(r#as) => r#as.run(_source, _context),
}
}
}
impl Format for Expression {
fn format(&self, _output: &mut String, _indent_level: u8) {
match self {
Expression::Value(value_node) => value_node.format(_output, _indent_level),
Expression::Identifier(identifier) => identifier.format(_output, _indent_level),
Expression::Math(math) => math.format(_output, _indent_level),
Expression::Logic(logic) => logic.format(_output, _indent_level),
Expression::FunctionCall(function_call) => function_call.format(_output, _indent_level),
Expression::Index(index) => index.format(_output, _indent_level),
Expression::Command(command) => command.format(_output, _indent_level),
Expression::As(r#as) => r#as.format(_output, _indent_level),
}
}
}

View File

@ -1,153 +0,0 @@
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Block, Context, Expression, Format, Identifier, SourcePosition, SyntaxNode, Type,
Value,
};
/// Abstract representation of a for loop statement.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct For {
is_async: bool,
item_id: Identifier,
collection: Expression,
block: Block,
source_position: SourcePosition,
#[serde(skip)]
context: Context,
}
impl AbstractTree for For {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("for", node)?;
let for_node = node.child(0).unwrap();
let is_async = match for_node.kind() {
"for" => false,
"async for" => true,
_ => {
return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "for or async for".to_string(),
actual: for_node.kind().to_string(),
position: node.range().into(),
})
}
};
let identifier_node = node.child(1).unwrap();
let identifier = Identifier::from_syntax(identifier_node, source, context)?;
let expression_node = node.child(3).unwrap();
let expression = Expression::from_syntax(expression_node, source, context)?;
let loop_context = Context::with_variables_from(context)?;
let item_node = node.child(4).unwrap();
let item = Block::from_syntax(item_node, source, &loop_context)?;
Ok(For {
is_async,
item_id: identifier,
collection: expression,
block: item,
source_position: SourcePosition::from(node.range()),
context: loop_context,
})
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::None)
}
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
self.collection.validate(_source, context)?;
let collection_type = self.collection.expected_type(context)?;
let item_type = match collection_type {
Type::Any => Type::Any,
Type::Collection => Type::Any,
Type::List => Type::Any,
Type::ListOf(_) => todo!(),
Type::ListExact(_) => todo!(),
Type::Map(_) => todo!(),
Type::String => todo!(),
Type::Range => todo!(),
_ => {
return Err(ValidationError::TypeCheck {
expected: Type::Collection,
actual: collection_type,
position: self.source_position,
});
}
};
let key = self.item_id.clone();
self.context.inherit_all_from(context)?;
self.context.set_type(key, item_type)?;
self.item_id.validate(_source, &self.context)?;
self.block.validate(_source, &self.context)
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
self.context.inherit_all_from(context)?;
let expression_run = self.collection.run(source, context)?;
let key = &self.item_id;
if let Value::Range(range) = expression_run {
if self.is_async {
range.into_par_iter().try_for_each(|integer| {
self.context.add_allowance(key)?;
self.context
.set_value(key.clone(), Value::Integer(integer))?;
self.block.run(source, &self.context).map(|_value| ())
})?;
} else {
for i in range {
self.context.add_allowance(key)?;
self.context.set_value(key.clone(), Value::Integer(i))?;
self.block.run(source, &self.context)?;
}
}
return Ok(Value::none());
}
if let Value::List(list) = &expression_run {
if self.is_async {
list.items()?.par_iter().try_for_each(|value| {
self.context.add_allowance(key)?;
self.context.set_value(key.clone(), value.clone())?;
self.block.run(source, &self.context).map(|_value| ())
})?;
} else {
for value in list.items()?.iter() {
self.context.add_allowance(key)?;
self.context.set_value(key.clone(), value.clone())?;
self.block.run(source, &self.context)?;
}
}
}
Ok(Value::none())
}
}
impl Format for For {
fn format(&self, output: &mut String, indent_level: u8) {
if self.is_async {
output.push_str("async for ");
} else {
output.push_str("for ");
}
self.item_id.format(output, indent_level);
output.push_str(" in ");
self.collection.format(output, indent_level);
output.push(' ');
self.block.format(output, indent_level);
}
}

View File

@ -1,202 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
built_in_functions::Callable,
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Expression, Format, Function, FunctionExpression, SourcePosition,
SyntaxNode, Type, Value,
};
/// A function being invoked and the arguments it is being passed.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct FunctionCall {
function_expression: FunctionExpression,
arguments: Vec<Expression>,
syntax_position: SourcePosition,
}
impl FunctionCall {
/// Returns a new FunctionCall.
pub fn new(
function_expression: FunctionExpression,
arguments: Vec<Expression>,
syntax_position: SourcePosition,
) -> Self {
Self {
function_expression,
arguments,
syntax_position,
}
}
}
impl AbstractTree for FunctionCall {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("function_call", node)?;
let function_node = node.child(0).unwrap();
let function_expression = FunctionExpression::from_syntax(function_node, source, context)?;
let mut arguments = Vec::new();
for index in 2..node.child_count() - 1 {
let child = node.child(index).unwrap();
if child.is_named() {
let expression = Expression::from_syntax(child, source, context)?;
arguments.push(expression);
}
}
Ok(FunctionCall {
function_expression,
arguments,
syntax_position: node.range().into(),
})
}
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
match &self.function_expression {
FunctionExpression::Identifier(identifier) => {
let identifier_type = identifier.expected_type(context)?;
if let Type::Function {
parameter_types: _,
return_type,
} = &identifier_type
{
Ok(*return_type.clone())
} else {
Ok(identifier_type)
}
}
FunctionExpression::FunctionCall(function_call) => function_call.expected_type(context),
FunctionExpression::Value(value_node) => {
let value_type = value_node.expected_type(context)?;
if let Type::Function { return_type, .. } = value_type {
Ok(*return_type)
} else {
Ok(value_type)
}
}
FunctionExpression::Index(index) => {
let index_type = index.expected_type(context)?;
if let Type::Function { return_type, .. } = index_type {
Ok(*return_type)
} else {
Ok(index_type)
}
}
}
}
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
self.function_expression.validate(_source, context)?;
let function_expression_type = self.function_expression.expected_type(context)?;
let parameter_types = if let Type::Function {
parameter_types, ..
} = function_expression_type
{
parameter_types
} else {
return Err(ValidationError::TypeCheckExpectedFunction {
actual: function_expression_type,
position: self.syntax_position,
});
};
if self.arguments.len() != parameter_types.len() {
return Err(ValidationError::ExpectedFunctionArgumentAmount {
expected: parameter_types.len(),
actual: self.arguments.len(),
position: self.syntax_position,
});
}
for (index, expression) in self.arguments.iter().enumerate() {
expression.validate(_source, context)?;
if let Some(expected) = parameter_types.get(index) {
let actual = expression.expected_type(context)?;
if !expected.accepts(&actual) {
return Err(ValidationError::TypeCheck {
expected: expected.clone(),
actual,
position: self.syntax_position,
});
}
}
}
Ok(())
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let value = match &self.function_expression {
FunctionExpression::Identifier(identifier) => {
if let Some(value) = context.get_value(identifier)? {
value.clone()
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::VariableIdentifierNotFound(identifier.clone()),
));
}
}
FunctionExpression::FunctionCall(function_call) => {
function_call.run(source, context)?
}
FunctionExpression::Value(value_node) => value_node.run(source, context)?,
FunctionExpression::Index(index) => index.run(source, context)?,
};
let function = value.as_function()?;
match function {
Function::BuiltIn(built_in_function) => {
let mut arguments = Vec::with_capacity(self.arguments.len());
for expression in &self.arguments {
let value = expression.run(source, context)?;
arguments.push(value);
}
built_in_function.call(&arguments, source, context)
}
Function::ContextDefined(function_node) => {
let call_context = Context::with_variables_from(function_node.context())?;
call_context.inherit_from(context)?;
let parameter_expression_pairs =
function_node.parameters().iter().zip(self.arguments.iter());
for (identifier, expression) in parameter_expression_pairs {
let value = expression.run(source, context)?;
call_context.set_value(identifier.clone(), value)?;
}
function_node.body().run(source, &call_context)
}
}
}
}
impl Format for FunctionCall {
fn format(&self, output: &mut String, indent_level: u8) {
self.function_expression.format(output, indent_level);
output.push('(');
for expression in &self.arguments {
expression.format(output, indent_level);
}
output.push(')');
}
}

View File

@ -1,91 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, FunctionCall, Identifier, Index, SyntaxNode, Type, Value,
ValueNode,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum FunctionExpression {
Identifier(Identifier),
FunctionCall(Box<FunctionCall>),
Value(ValueNode),
Index(Index),
}
impl AbstractTree for FunctionExpression {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("function_expression", node)?;
let first_child = node.child(0).unwrap();
let child = if first_child.is_named() {
first_child
} else {
node.child(1).unwrap()
};
let function_expression = match child.kind() {
"identifier" => {
FunctionExpression::Identifier(Identifier::from_syntax(child, source, context)?)
}
"function_call" => FunctionExpression::FunctionCall(Box::new(
FunctionCall::from_syntax(child, source, context)?,
)),
"value" => FunctionExpression::Value(ValueNode::from_syntax(child, source, context)?),
"index" => FunctionExpression::Index(Index::from_syntax(child, source, context)?),
_ => {
return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "identifier, function call, value or index".to_string(),
actual: child.kind().to_string(),
position: node.range().into(),
})
}
};
Ok(function_expression)
}
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
match self {
FunctionExpression::Identifier(identifier) => identifier.expected_type(context),
FunctionExpression::FunctionCall(function_call) => function_call.expected_type(context),
FunctionExpression::Value(value_node) => value_node.expected_type(context),
FunctionExpression::Index(index) => index.expected_type(context),
}
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
match self {
FunctionExpression::Identifier(identifier) => identifier.validate(_source, _context),
FunctionExpression::FunctionCall(function_call) => {
function_call.validate(_source, _context)
}
FunctionExpression::Value(value_node) => value_node.validate(_source, _context),
FunctionExpression::Index(index) => index.validate(_source, _context),
}
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
match self {
FunctionExpression::Identifier(identifier) => identifier.run(source, context),
FunctionExpression::FunctionCall(function_call) => function_call.run(source, context),
FunctionExpression::Value(value_node) => value_node.run(source, context),
FunctionExpression::Index(index) => index.run(source, context),
}
}
}
impl Format for FunctionExpression {
fn format(&self, output: &mut String, indent_level: u8) {
match self {
FunctionExpression::Value(value_node) => value_node.format(output, indent_level),
FunctionExpression::Identifier(identifier) => identifier.format(output, indent_level),
FunctionExpression::FunctionCall(function_call) => {
function_call.format(output, indent_level)
}
FunctionExpression::Index(index) => index.format(output, indent_level),
}
}
}

View File

@ -1,179 +0,0 @@
use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Block, Context, Format, Function, Identifier, SourcePosition, SyntaxNode, Type,
TypeSpecification, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct FunctionNode {
parameters: Vec<Identifier>,
body: Block,
r#type: Type,
syntax_position: SourcePosition,
#[serde(skip)]
context: Context,
}
impl FunctionNode {
pub fn parameters(&self) -> &Vec<Identifier> {
&self.parameters
}
pub fn body(&self) -> &Block {
&self.body
}
pub fn r#type(&self) -> &Type {
&self.r#type
}
pub fn syntax_position(&self) -> &SourcePosition {
&self.syntax_position
}
pub fn context(&self) -> &Context {
&self.context
}
pub fn return_type(&self) -> &Type {
match &self.r#type {
Type::Function {
parameter_types: _,
return_type,
} => return_type.as_ref(),
_ => &Type::None,
}
}
}
impl AbstractTree for FunctionNode {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("function", node)?;
let child_count = node.child_count();
let mut parameters = Vec::new();
let mut parameter_types = Vec::new();
for index in 1..child_count - 3 {
let child = node.child(index).unwrap();
if child.kind() == "identifier" {
let identifier = Identifier::from_syntax(child, source, context)?;
parameters.push(identifier);
}
if child.kind() == "type_specification" {
let type_specification = TypeSpecification::from_syntax(child, source, context)?;
parameter_types.push(type_specification.take_inner());
}
}
let return_type_node = node.child(child_count - 2).unwrap();
let return_type = TypeSpecification::from_syntax(return_type_node, source, context)?;
let function_context = Context::with_variables_from(context)?;
let body_node = node.child(child_count - 1).unwrap();
let body = Block::from_syntax(body_node, source, &function_context)?;
let r#type = Type::function(parameter_types, return_type.take_inner());
let syntax_position = node.range().into();
Ok(FunctionNode {
parameters,
body,
r#type,
syntax_position,
context: function_context,
})
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(self.r#type().clone())
}
fn validate(&self, source: &str, context: &Context) -> Result<(), ValidationError> {
if let Type::Function {
parameter_types,
return_type,
} = &self.r#type
{
self.context.inherit_from(context)?;
for (parameter, r#type) in self.parameters.iter().zip(parameter_types.iter()) {
self.context.set_type(parameter.clone(), r#type.clone())?;
}
let actual = self.body.expected_type(&self.context)?;
if !return_type.accepts(&actual) {
return Err(ValidationError::TypeCheck {
expected: return_type.as_ref().clone(),
actual,
position: self.syntax_position,
});
}
self.body.validate(source, &self.context)?;
Ok(())
} else {
Err(ValidationError::TypeCheckExpectedFunction {
actual: self.r#type.clone(),
position: self.syntax_position,
})
}
}
fn run(&self, _source: &str, context: &Context) -> Result<Value, RuntimeError> {
self.context.inherit_from(context)?;
let self_as_value = Value::Function(Function::ContextDefined(self.clone()));
Ok(self_as_value)
}
}
impl Format for FunctionNode {
fn format(&self, output: &mut String, indent_level: u8) {
let (parameter_types, return_type) = if let Type::Function {
parameter_types,
return_type,
} = &self.r#type
{
(parameter_types, return_type)
} else {
return;
};
output.push('(');
for (identifier, r#type) in self.parameters.iter().zip(parameter_types.iter()) {
identifier.format(output, indent_level);
output.push_str(" <");
r#type.format(output, indent_level);
output.push('>');
}
output.push_str(") <");
return_type.format(output, indent_level);
output.push_str("> ");
self.body.format(output, indent_level);
}
}
impl Display for FunctionNode {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let mut string = String::new();
self.format(&mut string, 0);
f.write_str(&string)
}
}

View File

@ -1,167 +0,0 @@
use std::{
fmt::{self, Display, Formatter},
sync::Arc,
};
use serde::{de::Visitor, Deserialize, Serialize};
use crate::{
built_in_identifiers::all_built_in_identifiers,
built_in_values::all_built_in_values,
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, SyntaxNode, Type, Value,
};
/// A string by which a variable is known to a context.
///
/// Every variable is a key-value pair. An identifier holds the key part of that
/// pair. Its inner value can be used to retrieve a Value instance from a Map.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct Identifier(Arc<String>);
impl Identifier {
pub fn new(key: &str) -> Self {
for built_in_identifier in all_built_in_identifiers() {
let identifier = built_in_identifier.get();
if &key == identifier.inner().as_ref() {
return identifier.clone();
}
}
Identifier(Arc::new(key.to_string()))
}
pub fn from_raw_parts(arc: Arc<String>) -> Self {
Identifier(arc)
}
pub fn inner(&self) -> &Arc<String> {
&self.0
}
pub fn contains(&self, string: &str) -> bool {
self.0.as_ref() == string
}
}
impl AbstractTree for Identifier {
fn from_syntax(
node: SyntaxNode,
source: &str,
_context: &Context,
) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("identifier", node)?;
let text = &source[node.byte_range()];
debug_assert!(!text.is_empty());
Ok(Identifier::new(text))
}
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
let variable_exists = context.add_allowance(self)?;
if variable_exists {
Ok(())
} else {
for built_in_value in all_built_in_values() {
if built_in_value.name() == self.inner().as_ref() {
return Ok(());
}
}
Err(ValidationError::VariableIdentifierNotFound(self.clone()))
}
}
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
if let Some(r#type) = context.get_type(self)? {
Ok(r#type)
} else {
for built_in_value in all_built_in_values() {
if built_in_value.name() == self.inner().as_ref() {
return Ok(built_in_value.get().r#type()?);
}
}
Err(ValidationError::VariableIdentifierNotFound(self.clone()))
}
}
fn run(&self, _source: &str, context: &Context) -> Result<Value, RuntimeError> {
if let Some(value) = context.get_value(self)? {
return Ok(value);
} else {
for built_in_value in all_built_in_values() {
if built_in_value.name() == self.inner().as_ref() {
return Ok(built_in_value.get().clone());
}
}
}
Err(RuntimeError::ValidationFailure(
ValidationError::VariableIdentifierNotFound(self.clone()),
))
}
}
impl Format for Identifier {
fn format(&self, output: &mut String, _indent_level: u8) {
output.push_str(&self.0);
}
}
impl From<String> for Identifier {
fn from(value: String) -> Self {
Identifier::from_raw_parts(Arc::new(value))
}
}
impl From<&str> for Identifier {
fn from(value: &str) -> Self {
Identifier::new(value)
}
}
impl Display for Identifier {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl Serialize for Identifier {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.0.as_ref())
}
}
impl<'de> Deserialize<'de> for Identifier {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_string(IdentifierVisitor)
}
}
struct IdentifierVisitor;
impl<'de> Visitor<'de> for IdentifierVisitor {
type Value = Identifier;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("valid UFT-8 sequence")
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Identifier(Arc::new(v)))
}
}

View File

@ -1,160 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Block, Context, Expression, Format, SourcePosition, SyntaxNode, Type, 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>,
source_position: SourcePosition,
}
impl AbstractTree for IfElse {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
let if_expression_node = node.child(0).unwrap().child(1).unwrap();
let if_expression = Expression::from_syntax(if_expression_node, source, context)?;
let if_block_node = node.child(0).unwrap().child(2).unwrap();
let if_block = Block::from_syntax(if_block_node, source, context)?;
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(expression_node, source, context)?;
else_if_expressions.push(expression);
let block_node = child.child(2).unwrap();
let block = Block::from_syntax(block_node, source, context)?;
else_if_blocks.push(block);
}
if child.kind() == "else" {
let else_node = child.child(1).unwrap();
else_block = Some(Block::from_syntax(else_node, source, context)?);
}
}
Ok(IfElse {
if_expression,
if_block,
else_if_expressions,
else_if_blocks,
else_block,
source_position: SourcePosition::from(node.range()),
})
}
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
self.if_block.expected_type(context)
}
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
self.if_expression.validate(_source, context)?;
self.if_block.validate(_source, context)?;
let expected = self.if_block.expected_type(context)?;
let else_ifs = self
.else_if_expressions
.iter()
.zip(self.else_if_blocks.iter());
for (expression, block) in else_ifs {
expression.validate(_source, context)?;
block.validate(_source, context)?;
let actual = block.expected_type(context)?;
if !expected.accepts(&actual) {
return Err(ValidationError::TypeCheck {
expected,
actual,
position: self.source_position,
});
}
}
if let Some(block) = &self.else_block {
block.validate(_source, context)?;
let actual = block.expected_type(context)?;
if !expected.accepts(&actual) {
return Err(ValidationError::TypeCheck {
expected,
actual,
position: self.source_position,
});
}
}
Ok(())
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let if_boolean = self.if_expression.run(source, context)?.as_boolean()?;
if if_boolean {
self.if_block.run(source, context)
} else {
let else_ifs = self
.else_if_expressions
.iter()
.zip(self.else_if_blocks.iter());
for (expression, block) in else_ifs {
let if_boolean = expression.run(source, context)?.as_boolean()?;
if if_boolean {
return block.run(source, context);
}
}
if let Some(block) = &self.else_block {
block.run(source, context)
} else {
Ok(Value::none())
}
}
}
}
impl Format for IfElse {
fn format(&self, output: &mut String, indent_level: u8) {
output.push_str("if ");
self.if_expression.format(output, indent_level);
output.push(' ');
self.if_block.format(output, indent_level);
let else_ifs = self
.else_if_expressions
.iter()
.zip(self.else_if_blocks.iter());
for (expression, block) in else_ifs {
output.push_str("else if ");
expression.format(output, indent_level);
output.push(' ');
block.format(output, indent_level);
}
if let Some(block) = &self.else_block {
output.push_str("else ");
block.format(output, indent_level);
}
}
}

View File

@ -1,134 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, Identifier, IndexExpression, SourcePosition, SyntaxNode, Type,
Value,
};
/// Abstract representation of an index expression.
///
/// An index is a means of accessing values stored in list, maps and strings.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Index {
pub collection: IndexExpression,
pub index: IndexExpression,
source_position: SourcePosition,
}
impl AbstractTree for Index {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("index", node)?;
let collection_node = node.child(0).unwrap();
let collection = IndexExpression::from_syntax(collection_node, source, context)?;
let index_node = node.child(2).unwrap();
let index = IndexExpression::from_syntax(index_node, source, context)?;
Ok(Index {
collection,
index,
source_position: SourcePosition::from(node.range()),
})
}
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
match self.collection.expected_type(context)? {
Type::ListOf(item_type) => Ok(*item_type.clone()),
Type::Map(map_types_option) => {
if let (Some(map_type), IndexExpression::Identifier(identifier)) =
(map_types_option, &self.index)
{
if let Some(r#type) = map_type.get(&identifier) {
Ok(r#type.clone())
} else {
Ok(Type::Any)
}
} else {
Ok(Type::Any)
}
}
Type::None => Ok(Type::None),
r#type => Ok(r#type),
}
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
self.collection.validate(_source, _context)?;
let collection_type = self.collection.expected_type(_context)?;
if let (Type::Map(type_map_option), IndexExpression::Identifier(identifier)) =
(collection_type, &self.index)
{
if let Some(type_map) = type_map_option {
if !type_map.contains_key(identifier) {
return Err(ValidationError::VariableIdentifierNotFound(
identifier.clone(),
));
}
}
} else {
self.index.validate(_source, _context)?;
}
Ok(())
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let value = self.collection.run(source, context)?;
match value {
Value::List(list) => {
let index = self.index.run(source, context)?.as_integer()? as usize;
let item = list.items()?.get(index).cloned().unwrap_or_default();
Ok(item)
}
Value::Map(map) => {
let map = map.inner();
let value = if let IndexExpression::Identifier(identifier) = &self.index {
if let Some(value) = map.get(identifier) {
value
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::VariableIdentifierNotFound(identifier.clone()),
));
}
} else {
let index_value = self.index.run(source, context)?;
let identifier = Identifier::new(index_value.as_string()?);
if let Some(value) = map.get(&identifier) {
value
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::VariableIdentifierNotFound(identifier.clone()),
));
}
};
Ok(value.clone())
}
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(RuntimeError::ValidationFailure(
ValidationError::ExpectedCollection { actual: value },
)),
}
}
}
impl Format for Index {
fn format(&self, output: &mut String, indent_level: u8) {
self.collection.format(output, indent_level);
output.push(':');
self.index.format(output, indent_level);
}
}

View File

@ -1,96 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, AssignmentOperator, Context, Format, Identifier, Index, IndexExpression,
SourcePosition, Statement, SyntaxNode, Type, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct IndexAssignment {
index: Index,
operator: AssignmentOperator,
statement: Statement,
position: SourcePosition,
}
impl AbstractTree for IndexAssignment {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("index_assignment", node)?;
let index_node = node.child(0).unwrap();
let index = Index::from_syntax(index_node, source, context)?;
let operator_node = node.child(1).unwrap();
let operator = AssignmentOperator::from_syntax(operator_node, source, context)?;
let statement_node = node.child(2).unwrap();
let statement = Statement::from_syntax(statement_node, source, context)?;
Ok(IndexAssignment {
index,
operator,
statement,
position: node.range().into(),
})
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::None)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
self.index.validate(_source, _context)?;
self.statement.validate(_source, _context)
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let _index_collection = self.index.collection.run(source, context)?;
let index_identifier = if let IndexExpression::Identifier(identifier) = &self.index.index {
identifier
} else {
let index_run = self.index.index.run(source, context)?;
let expected_identifier = Identifier::new(index_run.as_string()?);
return Err(RuntimeError::ValidationFailure(
ValidationError::VariableIdentifierNotFound(expected_identifier),
));
};
let value = self.statement.run(source, context)?;
let new_value = match self.operator {
AssignmentOperator::PlusEqual => {
if let Some(previous_value) = context.get_value(index_identifier)? {
previous_value.add(value, self.position)?
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::VariableIdentifierNotFound(index_identifier.clone()),
));
}
}
AssignmentOperator::MinusEqual => {
if let Some(previous_value) = context.get_value(index_identifier)? {
previous_value.subtract(value, self.position)?
} else {
Value::none()
}
}
AssignmentOperator::Equal => value,
};
context.set_value(index_identifier.clone(), new_value)?;
Ok(Value::none())
}
}
impl Format for IndexAssignment {
fn format(&self, output: &mut String, indent_level: u8) {
self.index.format(output, indent_level);
output.push(' ');
self.operator.format(output, indent_level);
output.push(' ');
self.statement.format(output, indent_level);
}
}

View File

@ -1,98 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
value_node::ValueNode,
AbstractTree, Context, Format, FunctionCall, Identifier, Index, SyntaxNode, Type, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum IndexExpression {
Value(ValueNode),
Identifier(Identifier),
Index(Box<Index>),
FunctionCall(Box<FunctionCall>),
}
impl AbstractTree for IndexExpression {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("index_expression", node)?;
let first_child = node.child(0).unwrap();
let child = if first_child.is_named() {
first_child
} else {
node.child(1).unwrap()
};
let abstract_node = match child.kind() {
"value" => IndexExpression::Value(ValueNode::from_syntax(child, source, context)?),
"identifier" => {
IndexExpression::Identifier(Identifier::from_syntax(child, source, context)?)
}
"index" => {
IndexExpression::Index(Box::new(Index::from_syntax(child, source, context)?))
}
"function_call" => IndexExpression::FunctionCall(Box::new(FunctionCall::from_syntax(
child, source, context,
)?)),
_ => {
return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "value, identifier, index or function call".to_string(),
actual: child.kind().to_string(),
position: node.range().into(),
})
}
};
Ok(abstract_node)
}
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
match self {
IndexExpression::Value(value_node) => value_node.expected_type(context),
IndexExpression::Identifier(identifier) => identifier.expected_type(context),
IndexExpression::Index(index) => index.expected_type(context),
IndexExpression::FunctionCall(function_call) => function_call.expected_type(context),
}
}
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
match self {
IndexExpression::Value(value_node) => value_node.validate(_source, context),
IndexExpression::Identifier(identifier) => {
context.add_allowance(identifier)?;
Ok(())
}
IndexExpression::Index(index) => index.validate(_source, context),
IndexExpression::FunctionCall(function_call) => {
function_call.validate(_source, context)
}
}
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
match self {
IndexExpression::Value(value_node) => value_node.run(source, context),
IndexExpression::Identifier(identifier) => identifier.run(source, context),
IndexExpression::Index(index) => index.run(source, context),
IndexExpression::FunctionCall(function_call) => function_call.run(source, context),
}
}
}
impl Format for IndexExpression {
fn format(&self, output: &mut String, indent_level: u8) {
match self {
IndexExpression::Value(value_node) => {
value_node.format(output, indent_level);
}
IndexExpression::Identifier(identifier) => identifier.format(output, indent_level),
IndexExpression::FunctionCall(function_call) => {
function_call.format(output, indent_level)
}
IndexExpression::Index(index) => index.format(output, indent_level),
}
}
}

View File

@ -1,95 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Expression, Format, LogicOperator, SyntaxNode, Type, Value,
};
/// Abstract representation of a logic expression.
#[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: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("logic", node)?;
let first_node = node.child(0).unwrap();
let (left_node, operator_node, right_node) = {
if first_node.is_named() {
(first_node, node.child(1).unwrap(), node.child(2).unwrap())
} else {
(
node.child(1).unwrap(),
node.child(2).unwrap(),
node.child(3).unwrap(),
)
}
};
let left = Expression::from_syntax(left_node, source, context)?;
let operator = LogicOperator::from_syntax(operator_node, source, context)?;
let right = Expression::from_syntax(right_node, source, context)?;
Ok(Logic {
left,
operator,
right,
})
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::Boolean)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
log::info!("VALIDATE logic expression");
self.left.validate(_source, _context)?;
self.right.validate(_source, _context)
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let left = self.left.run(source, context)?;
let right = self.right.run(source, context)?;
log::info!("RUN logic expression: {left} {} {right}", self.operator);
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::LessOrEqual => left <= right,
};
Ok(Value::Boolean(result))
}
}
impl Format for Logic {
fn format(&self, output: &mut String, indent_level: u8) {
self.left.format(output, indent_level);
output.push(' ');
self.operator.format(output, indent_level);
output.push(' ');
self.right.format(output, indent_level);
}
}

View File

@ -1,93 +0,0 @@
use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, SyntaxNode, Type, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum LogicOperator {
Equal,
NotEqual,
And,
Or,
Greater,
Less,
GreaterOrEqual,
LessOrEqual,
}
impl AbstractTree for LogicOperator {
fn from_syntax(
node: SyntaxNode,
_source: &str,
_context: &Context,
) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("logic_operator", node)?;
let operator_node = node.child(0).unwrap();
let operator = match operator_node.kind() {
"==" => LogicOperator::Equal,
"!=" => LogicOperator::NotEqual,
"&&" => LogicOperator::And,
"||" => LogicOperator::Or,
">" => LogicOperator::Greater,
"<" => LogicOperator::Less,
">=" => LogicOperator::GreaterOrEqual,
"<=" => LogicOperator::LessOrEqual,
_ => {
return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "==, !=, &&, ||, >, <, >= or <=".to_string(),
actual: operator_node.kind().to_string(),
position: node.range().into(),
})
}
};
Ok(operator)
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::None)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
Ok(Value::none())
}
}
impl Format for LogicOperator {
fn format(&self, output: &mut String, _indent_level: u8) {
match self {
LogicOperator::Equal => output.push('='),
LogicOperator::NotEqual => output.push_str("!="),
LogicOperator::And => output.push_str("&&"),
LogicOperator::Or => output.push_str("||"),
LogicOperator::Greater => output.push('>'),
LogicOperator::Less => output.push('<'),
LogicOperator::GreaterOrEqual => output.push_str(">="),
LogicOperator::LessOrEqual => output.push_str("<="),
}
}
}
impl Display for LogicOperator {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
LogicOperator::Equal => write!(f, "="),
LogicOperator::NotEqual => write!(f, "!="),
LogicOperator::And => write!(f, "&&"),
LogicOperator::Or => write!(f, "||"),
LogicOperator::Greater => write!(f, ">"),
LogicOperator::Less => write!(f, "<"),
LogicOperator::GreaterOrEqual => write!(f, ">="),
LogicOperator::LessOrEqual => write!(f, "<="),
}
}
}

View File

@ -1,117 +0,0 @@
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, Identifier, Map, SourcePosition, Statement, Type,
TypeSpecification, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct MapNode {
properties: BTreeMap<Identifier, (Statement, Option<Type>)>,
position: SourcePosition,
}
impl MapNode {
pub fn properties(&self) -> &BTreeMap<Identifier, (Statement, Option<Type>)> {
&self.properties
}
}
impl AbstractTree for MapNode {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("map", node)?;
let mut properties = BTreeMap::new();
let mut current_identifier = None;
let mut current_type = None;
for index in 0..node.child_count() - 1 {
let child = node.child(index).unwrap();
if child.kind() == "identifier" {
current_identifier = Some(Identifier::from_syntax(child, source, context)?);
current_type = None;
}
if child.kind() == "type_specification" {
current_type =
Some(TypeSpecification::from_syntax(child, source, context)?.take_inner());
}
if child.kind() == "statement" {
let statement = Statement::from_syntax(child, source, context)?;
if let Some(identifier) = &current_identifier {
properties.insert(identifier.clone(), (statement, current_type.clone()));
}
}
}
Ok(MapNode {
properties,
position: SourcePosition::from(node.range()),
})
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
if self.properties.is_empty() {
return Ok(Type::Map(None));
}
let mut type_map = BTreeMap::new();
for (identifier, (statement, r#type_option)) in &self.properties {
let r#type = if let Some(r#type) = type_option {
r#type.clone()
} else {
statement.expected_type(_context)?
};
type_map.insert(identifier.clone(), r#type);
}
Ok(Type::Map(Some(type_map)))
}
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
for (_key, (statement, r#type)) in &self.properties {
statement.validate(_source, context)?;
if let Some(expected) = r#type {
let actual = statement.expected_type(context)?;
if !expected.accepts(&actual) {
return Err(ValidationError::TypeCheck {
expected: expected.clone(),
actual,
position: self.position.clone(),
});
}
}
}
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
let mut map = Map::new();
for (key, (statement, _)) in &self.properties {
let value = statement.run(_source, _context)?;
map.set(key.clone(), value);
}
Ok(Value::Map(map))
}
}
impl Format for MapNode {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -1,145 +0,0 @@
//! 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 as SyntaxNode;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Expression, Format, MatchPattern, Statement, Type, Value,
};
/// Abstract representation of a match statement.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Match {
matcher: Expression,
options: Vec<(MatchPattern, Statement)>,
fallback: Option<Box<Statement>>,
#[serde(skip)]
context: Context,
}
impl AbstractTree for Match {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("match", node)?;
let matcher_node = node.child(1).unwrap();
let matcher = Expression::from_syntax(matcher_node, source, context)?;
let mut options = Vec::new();
let mut previous_pattern = None;
let mut next_statement_is_fallback = false;
let mut fallback = None;
for index in 2..node.child_count() {
let child = node.child(index).unwrap();
if child.kind() == "match_pattern" {
previous_pattern = Some(MatchPattern::from_syntax(child, source, context)?);
}
if child.kind() == "statement" {
let statement = Statement::from_syntax(child, source, context)?;
if next_statement_is_fallback {
fallback = Some(Box::new(statement));
next_statement_is_fallback = false;
} else if let Some(expression) = &previous_pattern {
options.push((expression.clone(), statement));
}
}
}
Ok(Match {
matcher,
options,
fallback,
context: Context::default(),
})
}
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
let (_, first_statement) = self.options.first().unwrap();
first_statement.expected_type(context)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
self.matcher.validate(_source, _context)?;
for (match_pattern, statement) in &self.options {
if let MatchPattern::EnumPattern(enum_pattern) = match_pattern {
if let Some(identifier) = enum_pattern.inner_identifier() {
self.context.set_type(identifier.clone(), Type::Any)?;
}
}
match_pattern.validate(_source, _context)?;
statement.validate(_source, &self.context)?;
}
if let Some(statement) = &self.fallback {
statement.validate(_source, _context)?;
}
Ok(())
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let matcher_value = self.matcher.run(source, context)?;
for (pattern, statement) in &self.options {
if let (Value::Enum(enum_instance), MatchPattern::EnumPattern(enum_pattern)) =
(&matcher_value, pattern)
{
if enum_instance.name() == enum_pattern.name()
&& enum_instance.variant() == enum_pattern.variant()
{
let statement_context = Context::with_variables_from(context)?;
if let (Some(identifier), Some(value)) =
(enum_pattern.inner_identifier(), enum_instance.value())
{
statement_context.set_value(identifier.clone(), value.as_ref().clone())?;
}
return statement.run(source, &statement_context);
}
}
let pattern_value = pattern.run(source, context)?;
if matcher_value == pattern_value {
return statement.run(source, context);
}
}
if let Some(fallback) = &self.fallback {
fallback.run(source, context)
} else {
Ok(Value::none())
}
}
}
impl Format for Match {
fn format(&self, output: &mut String, indent_level: u8) {
output.push_str("match ");
self.matcher.format(output, indent_level);
output.push_str(" {");
for (expression, statement) in &self.options {
expression.format(output, indent_level);
output.push_str(" => ");
statement.format(output, indent_level);
}
if let Some(statement) = &self.fallback {
output.push_str("* => ");
statement.format(output, indent_level);
}
output.push('}');
}
}

View File

@ -1,64 +0,0 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, EnumPattern, Format, Type, Value, ValueNode,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum MatchPattern {
EnumPattern(EnumPattern),
Value(ValueNode),
Wildcard,
}
impl AbstractTree for MatchPattern {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("match_pattern", node)?;
let child = node.child(0).unwrap();
let pattern = match child.kind() {
"enum_pattern" => {
MatchPattern::EnumPattern(EnumPattern::from_syntax(child, source, context)?)
}
"value" => MatchPattern::Value(ValueNode::from_syntax(child, source, context)?),
"*" => MatchPattern::Wildcard,
_ => {
return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "enum pattern or value".to_string(),
actual: child.kind().to_string(),
position: node.range().into(),
})
}
};
Ok(pattern)
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
match self {
MatchPattern::EnumPattern(enum_pattern) => enum_pattern.expected_type(_context),
MatchPattern::Value(value_node) => value_node.expected_type(_context),
MatchPattern::Wildcard => todo!(),
}
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
match self {
MatchPattern::EnumPattern(enum_pattern) => enum_pattern.run(_source, _context),
MatchPattern::Value(value_node) => value_node.run(_source, _context),
MatchPattern::Wildcard => todo!(),
}
}
}
impl Format for MatchPattern {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -1,74 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Expression, Format, MathOperator, SourcePosition, SyntaxNode, Type,
Value,
};
/// Abstract representation of a math operation.
///
/// Dust currently supports the four basic operations and the modulo (or
/// remainder) operator.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Math {
left: Expression,
operator: MathOperator,
right: Expression,
position: SourcePosition,
}
impl AbstractTree for Math {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("math", node)?;
let left_node = node.child(0).unwrap();
let left = Expression::from_syntax(left_node, source, context)?;
let operator_node = node.child(1).unwrap();
let operator = MathOperator::from_syntax(operator_node, source, context)?;
let right_node = node.child(2).unwrap();
let right = Expression::from_syntax(right_node, source, context)?;
Ok(Math {
left,
operator,
right,
position: node.range().into(),
})
}
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
self.left.expected_type(context)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
self.left.validate(_source, _context)?;
self.right.validate(_source, _context)
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let left = self.left.run(source, context)?;
let right = self.right.run(source, context)?;
let value = match self.operator {
MathOperator::Add => left.add(right, self.position)?,
MathOperator::Subtract => left.subtract(right, self.position)?,
MathOperator::Multiply => left.multiply(right, self.position)?,
MathOperator::Divide => left.divide(right, self.position)?,
MathOperator::Modulo => left.modulo(right, self.position)?,
};
Ok(value)
}
}
impl Format for Math {
fn format(&self, output: &mut String, indent_level: u8) {
self.left.format(output, indent_level);
output.push(' ');
self.operator.format(output, indent_level);
output.push(' ');
self.right.format(output, indent_level);
}
}

View File

@ -1,69 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, SyntaxNode, Type, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum MathOperator {
Add,
Subtract,
Multiply,
Divide,
Modulo,
}
impl AbstractTree for MathOperator {
fn from_syntax(
node: SyntaxNode,
_source: &str,
_context: &Context,
) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("math_operator", node)?;
let operator_node = node.child(0).unwrap();
let operator = match operator_node.kind() {
"+" => MathOperator::Add,
"-" => MathOperator::Subtract,
"*" => MathOperator::Multiply,
"/" => MathOperator::Divide,
"%" => MathOperator::Modulo,
_ => {
return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "+, -, *, / or %".to_string(),
actual: operator_node.kind().to_string(),
position: node.range().into(),
})
}
};
Ok(operator)
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::None)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
Ok(Value::none())
}
}
impl Format for MathOperator {
fn format(&self, output: &mut String, _indent_level: u8) {
let char = match self {
MathOperator::Add => '+',
MathOperator::Subtract => '-',
MathOperator::Multiply => '*',
MathOperator::Divide => '/',
MathOperator::Modulo => '%',
};
output.push(char);
}
}

View File

@ -1,178 +0,0 @@
//! Abstract, executable representations of corresponding items found in Dust
//! source code. The types that implement [AbstractTree] are inteded to be
//! created by an [Interpreter].
pub mod r#as;
pub mod assignment;
pub mod assignment_operator;
pub mod block;
pub mod command;
pub mod enum_defintion;
pub mod enum_pattern;
pub mod expression;
pub mod r#for;
pub mod function_call;
pub mod function_expression;
pub mod function_node;
pub mod identifier;
pub mod if_else;
pub mod index;
pub mod index_assignment;
pub mod index_expression;
pub mod logic;
pub mod logic_operator;
pub mod map_node;
pub mod r#match;
pub mod match_pattern;
pub mod math;
pub mod math_operator;
pub mod statement;
pub mod struct_definition;
pub mod r#type;
pub mod type_definition;
pub mod type_specification;
pub mod value_node;
pub mod r#while;
pub use {
assignment::*, assignment_operator::*, block::*, command::*, enum_defintion::*,
enum_pattern::*, expression::*, function_call::*, function_expression::*, function_node::*,
identifier::*, if_else::*, index::*, index_assignment::IndexAssignment, index_expression::*,
logic::*, logic_operator::*, map_node::*, match_pattern::*, math::*, math_operator::*, r#as::*,
r#for::*, r#match::*, r#type::*, r#while::*, statement::*, struct_definition::*,
type_definition::*, type_specification::*, value_node::*,
};
use serde::{Deserialize, Serialize};
use crate::{
context::Context,
error::{RuntimeError, SyntaxError, ValidationError},
SyntaxNode, Value,
};
/// A detailed report of a position in the source code string.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct SourcePosition {
pub start_byte: usize,
pub end_byte: usize,
pub start_row: usize,
pub start_column: usize,
pub end_row: usize,
pub end_column: usize,
}
impl From<tree_sitter::Range> for SourcePosition {
fn from(range: tree_sitter::Range) -> Self {
SourcePosition {
start_byte: range.start_byte,
end_byte: range.end_byte,
start_row: range.start_point.row + 1,
start_column: range.start_point.column,
end_row: range.end_point.row + 1,
end_column: range.end_point.column,
}
}
}
/// Abstraction that represents a whole, executable unit of dust code.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Root {
statements: Vec<Statement>,
}
// TODO Change Root to use tree sitter's cursor to traverse the statements
// instead of indexes. This will be more performant when there are a lot of
// top-level statements in the tree.
impl AbstractTree for Root {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("root", node)?;
let statement_count = node.child_count();
let mut statements = Vec::with_capacity(statement_count);
for index in 0..statement_count {
let statement_node = node.child(index).unwrap();
let statement = Statement::from_syntax(statement_node, source, context)?;
statements.push(statement);
}
Ok(Root { statements })
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
for statement in &self.statements {
statement.validate(_source, _context)?;
}
Ok(())
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let mut value = Value::none();
for statement in &self.statements {
value = statement.run(source, context)?;
if statement.is_return() {
return Ok(value);
}
}
Ok(value)
}
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
self.statements.last().unwrap().expected_type(context)
}
}
impl Format for Root {
fn format(&self, output: &mut String, indent_level: u8) {
for (index, statement) in self.statements.iter().enumerate() {
if index > 0 {
output.push('\n');
}
statement.format(output, indent_level);
output.push('\n');
}
}
}
/// 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 + Format {
/// Interpret the syntax tree at the given node and return the abstraction.
/// Returns a syntax error if the source is invalid.
///
/// 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: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError>;
/// Return the type of the value that this abstract node will create when
/// run. Returns a validation error if the tree is invalid.
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError>;
/// Verify the type integrity of the node. Returns a validation error if the
/// tree is invalid.
fn validate(&self, source: &str, context: &Context) -> Result<(), ValidationError>;
/// Execute this node's logic and return a value. Returns a runtime error if
/// the node cannot resolve to a value.
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError>;
}
pub trait Format {
fn format(&self, output: &mut String, indent_level: u8);
fn indent(output: &mut String, indent_level: u8) {
for _ in 0..indent_level {
output.push_str(" ");
}
}
}

View File

@ -1,45 +0,0 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, Identifier, Type, TypeSpecification, Value, ValueNode,
};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct New {
identifier: Identifier,
properties: Vec<(Identifier, ValueNode, Option<TypeSpecification>)>,
}
impl AbstractTree for New {
fn from_syntax(node: Node, source: &str, context: &Context) -> Result<Self, SyntaxError> {
let identifier_node = node.child(1).unwrap();
let identifier = Identifier::from_syntax(identifier_node, source, context)?;
let properties = Vec::new();
Ok(New {
identifier,
properties,
})
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
todo!()
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
todo!()
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
todo!()
}
}
impl Format for New {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -1,202 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Assignment, Block, Context, Expression, For, Format, IfElse, IndexAssignment,
Match, SyntaxNode, Type, TypeDefinition, Value, While,
};
/// Abstract representation of a statement.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Statement {
is_return: bool,
statement_kind: StatementKind,
}
impl Statement {
pub fn is_return(&self) -> bool {
self.is_return
}
}
impl AbstractTree for Statement {
fn from_syntax(
node: SyntaxNode,
source: &str,
_context: &Context,
) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("statement", node)?;
let first_child = node.child(0).unwrap();
let mut is_return = first_child.kind() == "return" || first_child.kind() == "break";
let child = if is_return {
node.child(1).unwrap()
} else {
first_child
};
let statement_kind = StatementKind::from_syntax(child, source, _context)?;
if let StatementKind::Block(block) = &statement_kind {
if block.contains_return() {
is_return = true;
}
};
Ok(Statement {
is_return,
statement_kind,
})
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
self.statement_kind.expected_type(_context)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
self.statement_kind.validate(_source, _context)
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
self.statement_kind.run(_source, _context)
}
}
impl Format for Statement {
fn format(&self, _output: &mut String, _indent_level: u8) {
self.statement_kind.format(_output, _indent_level)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
enum StatementKind {
Assignment(Box<Assignment>),
Expression(Expression),
IfElse(Box<IfElse>),
Match(Match),
While(Box<While>),
Block(Box<Block>),
For(Box<For>),
IndexAssignment(Box<IndexAssignment>),
TypeDefinition(TypeDefinition),
}
impl AbstractTree for StatementKind {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("statement_kind", node)?;
let child = node.child(0).unwrap();
match child.kind() {
"assignment" => Ok(StatementKind::Assignment(Box::new(
Assignment::from_syntax(child, source, context)?,
))),
"expression" => Ok(StatementKind::Expression(Expression::from_syntax(
child, source, context,
)?)),
"if_else" => Ok(StatementKind::IfElse(Box::new(IfElse::from_syntax(
child, source, context,
)?))),
"while" => Ok(StatementKind::While(Box::new(While::from_syntax(
child, source, context,
)?))),
"block" => Ok(StatementKind::Block(Box::new(Block::from_syntax(
child, source, context,
)?))),
"for" => Ok(StatementKind::For(Box::new(For::from_syntax(
child, source, context,
)?))),
"index_assignment" => Ok(StatementKind::IndexAssignment(Box::new(
IndexAssignment::from_syntax(child, source, context)?,
))),
"match" => Ok(StatementKind::Match(Match::from_syntax(
child, source, context,
)?)),
"type_definition" => Ok(StatementKind::TypeDefinition(TypeDefinition::from_syntax(
child, source, context
)?)),
_ => Err(SyntaxError::UnexpectedSyntaxNode {
expected:
"assignment, index assignment, expression, type_definition, block, return, if...else, while, for or match".to_string(),
actual: child.kind().to_string(),
position: node.range().into(),
}),
}
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
match self {
StatementKind::Assignment(assignment) => assignment.expected_type(_context),
StatementKind::Expression(expression) => expression.expected_type(_context),
StatementKind::IfElse(if_else) => if_else.expected_type(_context),
StatementKind::Match(r#match) => r#match.expected_type(_context),
StatementKind::While(r#while) => r#while.expected_type(_context),
StatementKind::Block(block) => block.expected_type(_context),
StatementKind::For(r#for) => r#for.expected_type(_context),
StatementKind::IndexAssignment(index_assignment) => {
index_assignment.expected_type(_context)
}
StatementKind::TypeDefinition(type_definition) => {
type_definition.expected_type(_context)
}
}
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
match self {
StatementKind::Assignment(assignment) => assignment.validate(_source, _context),
StatementKind::Expression(expression) => expression.validate(_source, _context),
StatementKind::IfElse(if_else) => if_else.validate(_source, _context),
StatementKind::Match(r#match) => r#match.validate(_source, _context),
StatementKind::While(r#while) => r#while.validate(_source, _context),
StatementKind::Block(block) => block.validate(_source, _context),
StatementKind::For(r#for) => r#for.validate(_source, _context),
StatementKind::IndexAssignment(index_assignment) => {
index_assignment.validate(_source, _context)
}
StatementKind::TypeDefinition(type_definition) => {
type_definition.validate(_source, _context)
}
}
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
match self {
StatementKind::Assignment(assignment) => assignment.run(_source, _context),
StatementKind::Expression(expression) => expression.run(_source, _context),
StatementKind::IfElse(if_else) => if_else.run(_source, _context),
StatementKind::Match(r#match) => r#match.run(_source, _context),
StatementKind::While(r#while) => r#while.run(_source, _context),
StatementKind::Block(block) => block.run(_source, _context),
StatementKind::For(r#for) => r#for.run(_source, _context),
StatementKind::IndexAssignment(index_assignment) => {
index_assignment.run(_source, _context)
}
StatementKind::TypeDefinition(type_definition) => {
type_definition.run(_source, _context)
}
}
}
}
impl Format for StatementKind {
fn format(&self, output: &mut String, indent_level: u8) {
StatementKind::indent(output, indent_level);
match self {
StatementKind::Assignment(assignment) => assignment.format(output, indent_level),
StatementKind::Expression(expression) => expression.format(output, indent_level),
StatementKind::IfElse(if_else) => if_else.format(output, indent_level),
StatementKind::Match(r#match) => r#match.format(output, indent_level),
StatementKind::While(r#while) => r#while.format(output, indent_level),
StatementKind::Block(block) => block.format(output, indent_level),
StatementKind::For(r#for) => r#for.format(output, indent_level),
StatementKind::IndexAssignment(index_assignment) => {
index_assignment.format(output, indent_level)
}
StatementKind::TypeDefinition(type_definition) => {
type_definition.format(output, indent_level)
}
}
}
}

View File

@ -1,120 +0,0 @@
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, Identifier, Map, MapNode, Statement, StructInstance, Type,
TypeDefinition, TypeSpecification, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct StructDefinition {
name: Identifier,
properties: BTreeMap<Identifier, (Option<Statement>, Type)>,
}
impl StructDefinition {
pub fn instantiate(
&self,
new_properties: &MapNode,
source: &str,
context: &Context,
) -> Result<StructInstance, RuntimeError> {
let mut all_properties = Map::new();
for (key, (statement_option, _)) in &self.properties {
if let Some(statement) = statement_option {
let value = statement.run(source, context)?;
all_properties.set(key.clone(), value);
}
}
for (key, (statement, _)) in new_properties.properties() {
let value = statement.run(source, context)?;
all_properties.set(key.clone(), value);
}
Ok(StructInstance::new(self.name.clone(), all_properties))
}
}
impl AbstractTree for StructDefinition {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("struct_definition", node)?;
let name_node = node.child(1).unwrap();
let name = Identifier::from_syntax(name_node, source, context)?;
let mut properties = BTreeMap::new();
let mut current_identifier: Option<Identifier> = None;
let mut current_type: Option<Type> = None;
let mut current_statement = None;
for index in 2..node.child_count() - 1 {
let child_syntax_node = node.child(index).unwrap();
if child_syntax_node.kind() == "identifier" {
if current_statement.is_none() {
if let (Some(identifier), Some(r#type)) = (&current_identifier, &current_type) {
properties.insert(identifier.clone(), (None, r#type.clone()));
}
}
current_type = None;
current_identifier =
Some(Identifier::from_syntax(child_syntax_node, source, context)?);
}
if child_syntax_node.kind() == "type_specification" {
current_type = Some(
TypeSpecification::from_syntax(child_syntax_node, source, context)?
.take_inner(),
);
}
if child_syntax_node.kind() == "statement" {
current_statement =
Some(Statement::from_syntax(child_syntax_node, source, context)?);
if let Some(identifier) = &current_identifier {
let r#type = if let Some(r#type) = &current_type {
r#type.clone()
} else {
Type::None
};
properties.insert(
identifier.clone(),
(current_statement.clone(), r#type.clone()),
);
}
}
}
Ok(StructDefinition { name, properties })
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::None)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
fn run(&self, _source: &str, context: &Context) -> Result<Value, RuntimeError> {
context.set_definition(self.name.clone(), TypeDefinition::Struct(self.clone()))?;
Ok(Value::none())
}
}
impl Format for StructDefinition {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -1,394 +0,0 @@
use std::{
collections::BTreeMap,
fmt::{self, Display, Formatter},
};
use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{
built_in_types::BuiltInType,
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, Identifier, TypeSpecification, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum Type {
Any,
Boolean,
Collection,
Custom {
name: Identifier,
arguments: Vec<Type>,
},
Float,
Function {
parameter_types: Vec<Type>,
return_type: Box<Type>,
},
Integer,
List,
ListOf(Box<Type>),
ListExact(Vec<Type>),
Map(Option<BTreeMap<Identifier, Type>>),
None,
Number,
String,
Range,
}
impl Type {
pub fn custom(name: Identifier, arguments: Vec<Type>) -> Self {
Type::Custom { name, arguments }
}
pub fn option(inner_type: Option<Type>) -> Self {
BuiltInType::Option(inner_type).get().clone()
}
pub fn list(item_type: Type) -> Self {
Type::ListOf(Box::new(item_type))
}
pub fn function(parameter_types: Vec<Type>, return_type: Type) -> Self {
Type::Function {
parameter_types,
return_type: Box::new(return_type),
}
}
/// Returns a boolean indicating whether is type is accepting of the other.
///
/// The types do not need to match exactly. For example, the Any variant matches all of the
/// others and the Number variant accepts Number, Integer and Float.
pub fn accepts(&self, other: &Type) -> bool {
log::info!("Checking type {self} against {other}.");
match (self, other) {
(Type::Any, _)
| (_, Type::Any)
| (Type::Boolean, Type::Boolean)
| (Type::Collection, Type::Collection)
| (Type::Collection, Type::String)
| (Type::Collection, Type::List)
| (Type::List, Type::Collection)
| (Type::Collection, Type::ListExact(_))
| (Type::ListExact(_), Type::Collection)
| (Type::Collection, Type::ListOf(_))
| (Type::ListOf(_), Type::Collection)
| (Type::Collection, Type::Map(_))
| (Type::Map(_), Type::Collection)
| (Type::String, Type::Collection)
| (Type::Float, Type::Float)
| (Type::Integer, Type::Integer)
| (Type::List, Type::List)
| (Type::Map(None), Type::Map(None))
| (Type::Number, Type::Number)
| (Type::Number, Type::Integer)
| (Type::Number, Type::Float)
| (Type::Integer, Type::Number)
| (Type::Float, Type::Number)
| (Type::String, Type::String)
| (Type::None, Type::None) => true,
(Type::Map(left_types), Type::Map(right_types)) => left_types == right_types,
(
Type::Custom {
name: left_name,
arguments: left_arguments,
},
Type::Custom {
name: right_name,
arguments: right_arguments,
},
) => left_name == right_name && left_arguments == right_arguments,
(Type::ListOf(self_item_type), Type::ListOf(other_item_type)) => {
self_item_type.accepts(&other_item_type)
}
(Type::ListExact(self_types), Type::ListExact(other_types)) => {
for (left, right) in self_types.iter().zip(other_types.iter()) {
if !left.accepts(right) {
return false;
}
}
true
}
(Type::ListExact(exact_types), Type::ListOf(of_type))
| (Type::ListOf(of_type), Type::ListExact(exact_types)) => {
exact_types.iter().all(|r#type| r#type == of_type.as_ref())
}
(
Type::Function {
parameter_types: self_parameter_types,
return_type: self_return_type,
},
Type::Function {
parameter_types: other_parameter_types,
return_type: other_return_type,
},
) => {
let parameter_type_pairs = self_parameter_types
.iter()
.zip(other_parameter_types.iter());
for (self_parameter_type, other_parameter_type) in parameter_type_pairs {
if self_parameter_type == other_parameter_type {
return false;
}
}
self_return_type == other_return_type
}
_ => false,
}
}
pub fn is_function(&self) -> bool {
matches!(self, Type::Function { .. })
}
pub fn is_list(&self) -> bool {
matches!(self, Type::ListOf(_))
}
pub fn is_map(&self) -> bool {
matches!(self, Type::Map(_))
}
}
impl AbstractTree for Type {
fn from_syntax(
node: SyntaxNode,
_source: &str,
context: &Context,
) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("type", node)?;
let type_node = node.child(0).unwrap();
let r#type = match type_node.kind() {
"identifier" => {
let name = Identifier::from_syntax(type_node, _source, context)?;
let mut arguments = Vec::new();
for index in 2..node.child_count() - 1 {
let child = node.child(index).unwrap();
if child.is_named() {
let r#type = Type::from_syntax(child, _source, context)?;
arguments.push(r#type);
}
}
Type::custom(name, arguments)
}
"{" => {
let mut type_map = BTreeMap::new();
let mut previous_identifier = None;
for index in 1..node.child_count() - 1 {
let child = node.child(index).unwrap();
if let Some(identifier) = previous_identifier {
let type_specification =
TypeSpecification::from_syntax(child, _source, context)?;
type_map.insert(identifier, type_specification.take_inner());
previous_identifier = None;
} else {
previous_identifier =
Some(Identifier::from_syntax(child, _source, context)?)
}
}
Type::Map(Some(type_map))
}
"[" => {
let item_type_node = node.child(1).unwrap();
let item_type = Type::from_syntax(item_type_node, _source, context)?;
Type::ListOf(Box::new(item_type))
}
"list" => {
let item_type_node = node.child(1);
if let Some(child) = item_type_node {
Type::ListOf(Box::new(Type::from_syntax(child, _source, context)?))
} else {
Type::List
}
}
"any" => Type::Any,
"bool" => Type::Boolean,
"collection" => Type::Collection,
"float" => Type::Float,
"(" => {
let child_count = node.child_count();
let mut parameter_types = Vec::new();
for index in 1..child_count - 2 {
let child = node.child(index).unwrap();
if child.is_named() {
let parameter_type = Type::from_syntax(child, _source, context)?;
parameter_types.push(parameter_type);
}
}
let final_node = node.child(child_count - 1).unwrap();
let return_type = if final_node.is_named() {
Type::from_syntax(final_node, _source, context)?
} else {
Type::option(None)
};
Type::Function {
parameter_types,
return_type: Box::new(return_type),
}
}
"int" => Type::Integer,
"map" => Type::Map(None),
"num" => Type::Number,
"none" => Type::None,
"str" => Type::String,
_ => {
return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "any, bool, float, int, num, str, list, map, custom type, (, [ or {"
.to_string(),
actual: type_node.kind().to_string(),
position: node.range().into(),
})
}
};
Ok(r#type)
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::None)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
Ok(Value::none())
}
}
impl Format for Type {
fn format(&self, output: &mut String, indent_level: u8) {
match self {
Type::Any => output.push_str("any"),
Type::Boolean => output.push_str("bool"),
Type::Collection => output.push_str("collection"),
Type::Custom {
name: _,
arguments: _,
} => todo!(),
Type::Float => output.push_str("float"),
Type::Function {
parameter_types,
return_type,
} => {
output.push('(');
for (index, parameter_type) in parameter_types.iter().enumerate() {
parameter_type.format(output, indent_level);
if index != parameter_types.len() - 1 {
output.push(' ');
}
}
output.push_str(") -> ");
return_type.format(output, indent_level);
}
Type::Integer => output.push_str("int"),
Type::List => todo!(),
Type::ListOf(item_type) => {
output.push('[');
item_type.format(output, indent_level);
output.push(']');
}
Type::ListExact(_) => todo!(),
Type::Map(_) => {
output.push_str("map");
}
Type::None => output.push_str("Option::None"),
Type::Number => output.push_str("num"),
Type::String => output.push_str("str"),
Type::Range => todo!(),
}
}
}
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::Collection => write!(f, "collection"),
Type::Custom { name, arguments } => {
if !arguments.is_empty() {
write!(f, "<")?;
for (index, r#type) in arguments.into_iter().enumerate() {
if index == arguments.len() - 1 {
write!(f, "{}", r#type)?;
} else {
write!(f, "{}, ", r#type)?;
}
}
write!(f, ">")
} else {
write!(f, "{name}")
}
}
Type::Float => write!(f, "float"),
Type::Function {
parameter_types,
return_type,
} => {
write!(f, "(")?;
for (index, parameter_type) in parameter_types.iter().enumerate() {
write!(f, "{parameter_type}")?;
if index != parameter_types.len() - 1 {
write!(f, " ")?;
}
}
write!(f, ")")?;
write!(f, " -> {return_type}")
}
Type::Integer => write!(f, "int"),
Type::List => write!(f, "list"),
Type::ListOf(item_type) => write!(f, "[{item_type}]"),
Type::ListExact(types) => {
write!(f, "[")?;
for (index, r#type) in types.into_iter().enumerate() {
if index == types.len() - 1 {
write!(f, "{}", r#type)?;
} else {
write!(f, "{}, ", r#type)?;
}
}
write!(f, "]")
}
Type::Map(_) => write!(f, "map"),
Type::Number => write!(f, "num"),
Type::None => write!(f, "none"),
Type::String => write!(f, "str"),
Type::Range => todo!(),
}
}
}

View File

@ -1,73 +0,0 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, EnumDefinition, Format, Identifier, StructDefinition, Type, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum TypeDefinition {
Enum(EnumDefinition),
Struct(StructDefinition),
}
impl TypeDefinition {
pub fn identifier(&self) -> &Identifier {
match self {
TypeDefinition::Enum(enum_definition) => enum_definition.identifier(),
TypeDefinition::Struct(_) => todo!(),
}
}
}
impl AbstractTree for TypeDefinition {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("type_definition", node)?;
let child = node.child(0).unwrap();
match child.kind() {
"enum_definition" => Ok(TypeDefinition::Enum(EnumDefinition::from_syntax(
child, source, context,
)?)),
"struct_definition" => Ok(TypeDefinition::Struct(StructDefinition::from_syntax(
child, source, context,
)?)),
_ => Err(SyntaxError::UnexpectedSyntaxNode {
expected: "enum or struct definition".to_string(),
actual: child.kind().to_string(),
position: node.range().into(),
}),
}
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
match self {
TypeDefinition::Enum(enum_definition) => enum_definition.expected_type(_context),
TypeDefinition::Struct(struct_definition) => struct_definition.expected_type(_context),
}
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
match self {
TypeDefinition::Enum(enum_definition) => enum_definition.validate(_source, _context),
TypeDefinition::Struct(struct_definition) => {
struct_definition.validate(_source, _context)
}
}
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
match self {
TypeDefinition::Enum(enum_definition) => enum_definition.run(_source, _context),
TypeDefinition::Struct(struct_definition) => struct_definition.run(_source, _context),
}
}
}
impl Format for TypeDefinition {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -1,56 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, SyntaxNode, Type, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct TypeSpecification {
r#type: Type,
}
impl TypeSpecification {
pub fn new(r#type: Type) -> Self {
Self { r#type }
}
pub fn inner(&self) -> &Type {
&self.r#type
}
pub fn take_inner(self) -> Type {
self.r#type
}
}
impl AbstractTree for TypeSpecification {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("type_specification", node)?;
let type_node = node.child(1).unwrap();
let r#type = Type::from_syntax(type_node, source, context)?;
Ok(TypeSpecification { r#type })
}
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
self.r#type.expected_type(context)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
self.r#type.run(source, context)
}
}
impl Format for TypeSpecification {
fn format(&self, output: &mut String, indent_level: u8) {
output.push('<');
self.r#type.format(output, indent_level);
output.push('>');
}
}

View File

@ -1,357 +0,0 @@
use std::{cmp::Ordering, ops::RangeInclusive};
use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Expression, Format, Function, FunctionNode,
Identifier, List, Type, Value, TypeDefinition, MapNode,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub enum ValueNode {
Boolean(String),
Float(String),
Function(Function),
Integer(String),
String(String),
List(Vec<Expression>),
Map(MapNode),
Range(RangeInclusive<i64>),
Struct {
name: Identifier,
properties: MapNode,
},
Enum {
name: Identifier,
variant: Identifier,
expression: Option<Box<Expression>>,
},
}
impl AbstractTree for ValueNode {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("value", node)?;
let child = node.child(0).unwrap();
let value_node = match child.kind() {
"boolean" => ValueNode::Boolean(source[child.byte_range()].to_string()),
"float" => ValueNode::Float(source[child.byte_range()].to_string()),
"function" => {
let function_node = FunctionNode::from_syntax(child, source, context)?;
ValueNode::Function(Function::ContextDefined(function_node))
}
"integer" => ValueNode::Integer(source[child.byte_range()].to_string()),
"string" => {
let without_quotes = child.start_byte() + 1..child.end_byte() - 1;
ValueNode::String(source[without_quotes].to_string())
}
"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(current_node, source, context)?;
expressions.push(expression);
}
}
ValueNode::List(expressions)
}
"map" => {
ValueNode::Map(MapNode::from_syntax(child, source, context)?)
}
"range" => {
let mut split = source[child.byte_range()].split("..");
let start = split.next().unwrap().parse().unwrap();
let end = split.next().unwrap().parse().unwrap();
ValueNode::Range(start..=end)
}
"enum_instance" => {
let name_node = child.child(0).unwrap();
let name = Identifier::from_syntax(name_node, source, context)?;
let variant_node = child.child(2).unwrap();
let variant = Identifier::from_syntax(variant_node, source, context)?;
let expression = if let Some(expression_node) = child.child(4) {
Some(Box::new(Expression::from_syntax(expression_node, source, context)?))
} else {
None
};
ValueNode::Enum { name, variant , expression }
}
"struct_instance" => {
let name_node = child.child(0).unwrap();
let name = Identifier::from_syntax(name_node, source, context)?;
let properties_node = child.child(2).unwrap();
let properties = MapNode::from_syntax(properties_node, source, context)?;
ValueNode::Struct
{
name,
properties
}
}
_ => {
return Err(SyntaxError::UnexpectedSyntaxNode {
expected:
"string, integer, float, boolean, range, list, map, option, function, struct or enum"
.to_string(),
actual: child.kind().to_string(),
position: node.range().into(),
})
}
};
Ok(value_node)
}
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
let r#type = match self {
ValueNode::Boolean(_) => Type::Boolean,
ValueNode::Float(_) => Type::Float,
ValueNode::Function(function) => function.r#type(),
ValueNode::Integer(_) => Type::Integer,
ValueNode::String(_) => Type::String,
ValueNode::List(expressions) => {
let mut item_types = Vec::new();
for expression in expressions {
let expression_type = expression.expected_type(context)?;
item_types.push(expression_type);
}
Type::ListExact(item_types)
}
ValueNode::Map(map_node) => map_node.expected_type(context)?,
ValueNode::Struct { name, .. } => {
Type::custom(name.clone(), Vec::with_capacity(0))
}
ValueNode::Range(_) => Type::Range,
ValueNode::Enum { name, variant, expression: _ } => {
let types: Vec<Type> = if let Some(type_definition) = context.get_definition(name)? {
if let TypeDefinition::Enum(enum_definition) = type_definition {
let types = enum_definition.variants().into_iter().find_map(|(identifier, types)| {
if identifier == variant {
Some(types.clone())
} else {
None
}
});
if let Some(types) = types {
types
} else {
return Err(ValidationError::VariableIdentifierNotFound(variant.clone()));
}
} else {
return Err(ValidationError::ExpectedEnumDefintion { actual: type_definition.clone() });
}
} else {
return Err(ValidationError::VariableIdentifierNotFound(name.clone()));
};
Type::custom(name.clone(), types.clone())
},
};
Ok(r#type)
}
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
match self {
ValueNode::Function(function) => {
if let Function::ContextDefined(function_node) = function {
function_node.validate(_source, context)?;
}
}
ValueNode::Map(map_node) => map_node.validate(_source, context)?,
ValueNode::Enum { name, expression, .. } => {
name.validate(_source, context)?;
if let Some(expression) = expression {
expression.validate(_source, context)?;
}
}
_ => {},
}
Ok(())
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let value = match self {
ValueNode::Boolean(value_source) => Value::Boolean(value_source.parse().unwrap()),
ValueNode::Float(value_source) => {
let float = value_source.parse()?;
Value::Float(float)
}
ValueNode::Function(function) => Value::Function(function.clone()),
ValueNode::Integer(value_source) => Value::Integer(value_source.parse().unwrap()),
ValueNode::String(value_source) => Value::string(value_source.clone()),
ValueNode::List(expressions) => {
let mut values = Vec::with_capacity(expressions.len());
for node in expressions {
let value = node.run(source, context)?;
values.push(value);
}
Value::List(List::with_items(values))
}
ValueNode::Map(map_node) => map_node.run(source, context)?,
ValueNode::Range(range) => Value::Range(range.clone()),
ValueNode::Struct { name, properties } => {
let instance = if let Some(definition) = context.get_definition(name)? {
if let TypeDefinition::Struct(struct_definition) = definition {
struct_definition.instantiate(properties, source, context)?
} else {
return Err(RuntimeError::ValidationFailure(ValidationError::ExpectedStructDefintion { actual: definition.clone() }))
}
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::TypeDefinitionNotFound(name.clone())
));
};
Value::Struct(instance)
}
ValueNode::Enum { name, variant, expression } => {
let value = if let Some(expression) = expression {
expression.run(source, context)?
} else {
Value::none()
};
let instance = if let Some(definition) = context.get_definition(name)? {
if let TypeDefinition::Enum(enum_defintion) = definition {
enum_defintion.instantiate(variant.clone(), Some(value))
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::ExpectedEnumDefintion {
actual: definition.clone()
}
));
}
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::TypeDefinitionNotFound(name.clone())
));
};
Value::Enum(instance)
},
};
Ok(value)
}
}
impl Format for ValueNode {
fn format(&self, output: &mut String, indent_level: u8) {
match self {
ValueNode::Boolean(source) | ValueNode::Float(source) | ValueNode::Integer(source) => {
output.push_str(source)
}
ValueNode::String(source) => {
output.push('\'');
output.push_str(source);
output.push('\'');
}
ValueNode::Function(function) => function.format(output, indent_level),
ValueNode::List(expressions) => {
output.push('[');
for expression in expressions {
expression.format(output, indent_level);
}
output.push(']');
}
ValueNode::Map(map_node) => map_node.format(output, indent_level),
ValueNode::Struct { name, properties } => {
name.format(output, indent_level);
properties.format(output, indent_level);
}
ValueNode::Range(_) => todo!(),
ValueNode::Enum { .. } => todo!(),
}
}
}
impl Ord for ValueNode {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match (self, other) {
(ValueNode::Boolean(left), ValueNode::Boolean(right)) => left.cmp(right),
(ValueNode::Boolean(_), _) => Ordering::Greater,
(ValueNode::Float(left), ValueNode::Float(right)) => left.cmp(right),
(ValueNode::Float(_), _) => Ordering::Greater,
(ValueNode::Function(left), ValueNode::Function(right)) => left.cmp(right),
(ValueNode::Function(_), _) => Ordering::Greater,
(ValueNode::Integer(left), ValueNode::Integer(right)) => left.cmp(right),
(ValueNode::Integer(_), _) => Ordering::Greater,
(ValueNode::String(left), ValueNode::String(right)) => left.cmp(right),
(ValueNode::String(_), _) => Ordering::Greater,
(ValueNode::List(left), ValueNode::List(right)) => left.cmp(right),
(ValueNode::List(_), _) => Ordering::Greater,
(ValueNode::Map(left), ValueNode::Map(right)) => left.cmp(right),
(ValueNode::Map(_), _) => Ordering::Greater,
(ValueNode::Struct{ name: left_name, properties: left_properties }, ValueNode::Struct {name: right_name, properties: right_properties} ) => {
let name_cmp = left_name.cmp(right_name);
if name_cmp.is_eq() {
left_properties.cmp(right_properties)
} else {
name_cmp
}
},
(ValueNode::Struct {..}, _) => Ordering::Greater,
(
ValueNode::Enum {
name: left_name, variant: left_variant, expression: left_expression
},
ValueNode::Enum {
name: right_name, variant: right_variant, expression: right_expression
}
) => {
let name_cmp = left_name.cmp(right_name);
if name_cmp.is_eq() {
let variant_cmp = left_variant.cmp(right_variant);
if variant_cmp.is_eq() {
left_expression.cmp(right_expression)
} else {
variant_cmp
}
} else {
name_cmp
}
},
(ValueNode::Enum { .. }, _) => Ordering::Greater,
(ValueNode::Range(left), ValueNode::Range(right)) => left.clone().cmp(right.clone()),
(ValueNode::Range(_), _) => Ordering::Less,
}
}
}
impl PartialOrd for ValueNode {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

View File

@ -1,64 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Block, Context, Expression, Format, SyntaxNode, Type, Value,
};
/// Abstract representation of a while loop.
///
/// While executes its block repeatedly until its expression evaluates to true.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct While {
expression: Expression,
block: Block,
}
impl AbstractTree for While {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("while", node)?;
let expression_node = node.child(1).unwrap();
let expression = Expression::from_syntax(expression_node, source, context)?;
let block_node = node.child(2).unwrap();
let block = Block::from_syntax(block_node, source, context)?;
Ok(While { expression, block })
}
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
self.block.expected_type(context)
}
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
log::info!("VALIDATE while loop");
self.expression.validate(_source, context)?;
self.block.validate(_source, context)
}
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
log::info!("RUN while loop start");
while self.expression.run(source, context)?.as_boolean()? {
self.block.run(source, context)?;
}
log::info!("RUN while loop end");
Ok(Value::none())
}
}
impl Format for While {
fn format(&self, output: &mut String, indent_level: u8) {
output.push('\n');
While::indent(output, indent_level);
output.push_str("while ");
self.expression.format(output, indent_level);
output.push(' ');
self.block.format(output, indent_level);
output.push('\n');
}
}

View File

@ -1,59 +0,0 @@
use std::{fs::File, io::Read};
use enum_iterator::{all, Sequence};
use serde::{Deserialize, Serialize};
use crate::{error::RuntimeError, Context, Type, Value};
use super::Callable;
pub fn fs_functions() -> impl Iterator<Item = Fs> {
all()
}
#[derive(Sequence, Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum Fs {
ReadFile,
}
impl Callable for Fs {
fn name(&self) -> &'static str {
match self {
Fs::ReadFile => "read_file",
}
}
fn description(&self) -> &'static str {
match self {
Fs::ReadFile => "Read the contents of a file to a string.",
}
}
fn r#type(&self) -> Type {
match self {
Fs::ReadFile => Type::function(vec![Type::String], Type::String),
}
}
fn call(
&self,
arguments: &[Value],
_source: &str,
_outer_context: &Context,
) -> Result<Value, RuntimeError> {
match self {
Fs::ReadFile => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let path = arguments.first().unwrap().as_string()?;
let mut file = File::open(path)?;
let file_size = file.metadata()?.len() as usize;
let mut file_content = String::with_capacity(file_size);
file.read_to_string(&mut file_content)?;
Ok(Value::string(file_content))
}
}
}
}

View File

@ -1,77 +0,0 @@
use enum_iterator::Sequence;
use serde::{Deserialize, Serialize};
use crate::{error::RuntimeError, Context, Type, Value};
use super::Callable;
pub fn json_functions() -> impl Iterator<Item = Json> {
enum_iterator::all()
}
#[derive(Sequence, Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum Json {
Create,
CreatePretty,
Parse,
}
impl Callable for Json {
fn name(&self) -> &'static str {
match self {
Json::Create => "create",
Json::CreatePretty => "create_pretty",
Json::Parse => "parse",
}
}
fn description(&self) -> &'static str {
match self {
Json::Create => "Convert a value to a JSON string.",
Json::CreatePretty => "Convert a value to a formatted JSON string.",
Json::Parse => "Convert JSON to a value",
}
}
fn r#type(&self) -> Type {
match self {
Json::Create => Type::function(vec![Type::Any], Type::String),
Json::CreatePretty => Type::function(vec![Type::Any], Type::String),
Json::Parse => Type::function(vec![Type::String], Type::Any),
}
}
fn call(
&self,
arguments: &[Value],
_source: &str,
_outer_context: &Context,
) -> Result<Value, RuntimeError> {
match self {
Json::Create => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let value = arguments.first().unwrap();
let json_string = serde_json::to_string(value)?;
Ok(Value::String(json_string))
}
Json::CreatePretty => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let value = arguments.first().unwrap();
let json_string = serde_json::to_string_pretty(value)?;
Ok(Value::String(json_string))
}
Json::Parse => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let json_string = arguments.first().unwrap().as_string()?;
let value = serde_json::from_str(json_string)?;
Ok(value)
}
}
}
}

View File

@ -1,198 +0,0 @@
pub mod fs;
pub mod json;
pub mod str;
use std::fmt::{self, Display, Formatter};
use rand::{random, thread_rng, Rng};
use serde::{Deserialize, Serialize};
use crate::{
error::{RuntimeError, ValidationError},
Context, EnumInstance, Format, Identifier, Type, Value,
};
use self::{fs::Fs, json::Json, str::StrFunction};
pub trait Callable {
fn name(&self) -> &'static str;
fn description(&self) -> &'static str;
fn r#type(&self) -> Type;
fn call(
&self,
arguments: &[Value],
source: &str,
context: &Context,
) -> Result<Value, RuntimeError>;
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum BuiltInFunction {
AssertEqual,
Fs(Fs),
Json(Json),
Length,
Output,
RandomBoolean,
RandomFloat,
RandomFrom,
RandomInteger,
String(StrFunction),
}
impl Callable for BuiltInFunction {
fn name(&self) -> &'static str {
match self {
BuiltInFunction::AssertEqual => "assert_equal",
BuiltInFunction::Fs(fs_function) => fs_function.name(),
BuiltInFunction::Json(json_function) => json_function.name(),
BuiltInFunction::Length => "length",
BuiltInFunction::Output => "output",
BuiltInFunction::RandomBoolean => "boolean",
BuiltInFunction::RandomFloat => "float",
BuiltInFunction::RandomFrom => "from",
BuiltInFunction::RandomInteger => "integer",
BuiltInFunction::String(string_function) => string_function.name(),
}
}
fn description(&self) -> &'static str {
match self {
BuiltInFunction::AssertEqual => "assert_equal",
BuiltInFunction::Fs(fs_function) => fs_function.description(),
BuiltInFunction::Json(json_function) => json_function.description(),
BuiltInFunction::Length => "length",
BuiltInFunction::Output => "output",
BuiltInFunction::RandomBoolean => "boolean",
BuiltInFunction::RandomFloat => "float",
BuiltInFunction::RandomFrom => "from",
BuiltInFunction::RandomInteger => "integer",
BuiltInFunction::String(string_function) => string_function.description(),
}
}
fn r#type(&self) -> Type {
match self {
BuiltInFunction::AssertEqual => Type::function(vec![Type::Any, Type::Any], Type::None),
BuiltInFunction::Fs(fs_function) => fs_function.r#type(),
BuiltInFunction::Json(json_function) => json_function.r#type(),
BuiltInFunction::Length => Type::function(vec![Type::Collection], Type::Integer),
BuiltInFunction::Output => Type::function(vec![Type::Any], Type::None),
BuiltInFunction::RandomBoolean => Type::function(vec![], Type::Boolean),
BuiltInFunction::RandomFloat => Type::function(vec![], Type::Float),
BuiltInFunction::RandomFrom => Type::function(vec![Type::Collection], Type::Any),
BuiltInFunction::RandomInteger => Type::function(vec![], Type::Integer),
BuiltInFunction::String(string_function) => string_function.r#type(),
}
}
fn call(
&self,
arguments: &[Value],
_source: &str,
context: &Context,
) -> Result<Value, RuntimeError> {
match self {
BuiltInFunction::AssertEqual => {
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
let left = arguments.get(0).unwrap();
let right = arguments.get(1).unwrap();
if left == right {
Ok(Value::Enum(EnumInstance::new(
Identifier::new("Result"),
Identifier::new("Ok"),
Some(Value::none()),
)))
} else {
Err(RuntimeError::AssertEqualFailed {
left: left.clone(),
right: right.clone(),
})
}
}
BuiltInFunction::Fs(fs_function) => fs_function.call(arguments, _source, context),
BuiltInFunction::Json(json_function) => json_function.call(arguments, _source, context),
BuiltInFunction::Length => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let value = arguments.first().unwrap();
let length = if let Ok(list) = value.as_list() {
list.items()?.len()
} else if let Ok(map) = value.as_map() {
map.inner().len()
} else if let Ok(str) = value.as_string() {
str.chars().count()
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::ExpectedCollection {
actual: value.clone(),
},
));
};
Ok(Value::Integer(length as i64))
}
BuiltInFunction::Output => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let value = arguments.first().unwrap();
println!("{value}");
Ok(Value::none())
}
BuiltInFunction::RandomBoolean => {
RuntimeError::expect_argument_amount(self.name(), 0, arguments.len())?;
Ok(Value::Boolean(random()))
}
BuiltInFunction::RandomFloat => {
RuntimeError::expect_argument_amount(self.name(), 0, arguments.len())?;
Ok(Value::Float(random()))
}
BuiltInFunction::RandomFrom => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let value = arguments.first().unwrap();
if let Ok(list) = value.as_list() {
let items = list.items()?;
if items.len() == 0 {
Ok(Value::none())
} else {
let random_index = thread_rng().gen_range(0..items.len());
let random_value = items.get(random_index).cloned().unwrap_or_default();
Ok(random_value)
}
} else {
todo!()
}
}
BuiltInFunction::RandomInteger => {
RuntimeError::expect_argument_amount(self.name(), 0, arguments.len())?;
Ok(Value::Integer(random()))
}
BuiltInFunction::String(string_function) => {
string_function.call(arguments, _source, context)
}
}
}
}
impl Format for BuiltInFunction {
fn format(&self, output: &mut String, _indent_level: u8) {
output.push_str(self.name());
}
}
impl Display for BuiltInFunction {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.name())
}
}

View File

@ -1,591 +0,0 @@
use enum_iterator::Sequence;
use serde::{Deserialize, Serialize};
use crate::{error::RuntimeError, Context, EnumInstance, Identifier, List, Type, Value};
use super::Callable;
pub fn string_functions() -> impl Iterator<Item = StrFunction> {
enum_iterator::all()
}
#[derive(Sequence, Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum StrFunction {
AsBytes,
EndsWith,
Find,
Insert,
IsAscii,
IsEmpty,
Lines,
Matches,
Parse,
Remove,
ReplaceRange,
Retain,
Split,
SplitAt,
SplitInclusive,
SplitN,
SplitOnce,
SplitTerminator,
SplitWhitespace,
StartsWith,
StripPrefix,
ToLowercase,
ToUppercase,
Trim,
TrimEnd,
TrimEndMatches,
TrimMatches,
TrimStart,
TrimStartMatches,
Truncate,
}
impl Callable for StrFunction {
fn name(&self) -> &'static str {
match self {
StrFunction::AsBytes => "as_bytes",
StrFunction::EndsWith => "ends_with",
StrFunction::Find => "find",
StrFunction::Insert => "insert",
StrFunction::IsAscii => "is_ascii",
StrFunction::IsEmpty => "is_empty",
StrFunction::Lines => "lines",
StrFunction::Matches => "matches",
StrFunction::Parse => "parse",
StrFunction::Remove => "remove",
StrFunction::ReplaceRange => "replace_range",
StrFunction::Retain => "retain",
StrFunction::Split => "split",
StrFunction::SplitAt => "split_at",
StrFunction::SplitInclusive => "split_inclusive",
StrFunction::SplitN => "split_n",
StrFunction::SplitOnce => "split_once",
StrFunction::SplitTerminator => "split_terminator",
StrFunction::SplitWhitespace => "split_whitespace",
StrFunction::StartsWith => "starts_with",
StrFunction::StripPrefix => "strip_prefix",
StrFunction::ToLowercase => "to_lowercase",
StrFunction::ToUppercase => "to_uppercase",
StrFunction::Trim => "trim",
StrFunction::TrimEnd => "trim_end",
StrFunction::TrimEndMatches => "trim_end_matches",
StrFunction::TrimMatches => "trim_matches",
StrFunction::TrimStart => "trim_start",
StrFunction::TrimStartMatches => "trim_start_matches",
StrFunction::Truncate => "truncate",
}
}
fn description(&self) -> &'static str {
match self {
StrFunction::AsBytes => "TODO",
StrFunction::EndsWith => "TODO",
StrFunction::Find => "TODO",
StrFunction::Insert => "TODO",
StrFunction::IsAscii => "TODO",
StrFunction::IsEmpty => "TODO",
StrFunction::Lines => "TODO",
StrFunction::Matches => "TODO",
StrFunction::Parse => "TODO",
StrFunction::Remove => "TODO",
StrFunction::ReplaceRange => "TODO",
StrFunction::Retain => "TODO",
StrFunction::Split => "TODO",
StrFunction::SplitAt => "TODO",
StrFunction::SplitInclusive => "TODO",
StrFunction::SplitN => "TODO",
StrFunction::SplitOnce => "TODO",
StrFunction::SplitTerminator => "TODO",
StrFunction::SplitWhitespace => "TODO",
StrFunction::StartsWith => "TODO",
StrFunction::StripPrefix => "TODO",
StrFunction::ToLowercase => "TODO",
StrFunction::ToUppercase => "TODO",
StrFunction::Trim => "TODO",
StrFunction::TrimEnd => "TODO",
StrFunction::TrimEndMatches => "TODO",
StrFunction::TrimMatches => "TODO",
StrFunction::TrimStart => "TODO",
StrFunction::TrimStartMatches => "TODO",
StrFunction::Truncate => "TODO",
}
}
fn r#type(&self) -> Type {
match self {
StrFunction::AsBytes => Type::function(vec![Type::String], Type::list(Type::Integer)),
StrFunction::EndsWith => {
Type::function(vec![Type::String, Type::String], Type::Boolean)
}
StrFunction::Find => Type::function(
vec![Type::String, Type::String],
Type::option(Some(Type::Integer)),
),
StrFunction::Insert => Type::function(
vec![Type::String, Type::Integer, Type::String],
Type::String,
),
StrFunction::IsAscii => Type::function(vec![Type::String], Type::Boolean),
StrFunction::IsEmpty => Type::function(vec![Type::String], Type::Boolean),
StrFunction::Lines => Type::function(vec![Type::String], Type::list(Type::String)),
StrFunction::Matches => {
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
}
StrFunction::Parse => Type::function(vec![Type::String], Type::Any),
StrFunction::Remove => Type::function(
vec![Type::String, Type::Integer],
Type::option(Some(Type::String)),
),
StrFunction::ReplaceRange => Type::function(
vec![Type::String, Type::list(Type::Integer), Type::String],
Type::String,
),
StrFunction::Retain => Type::function(
vec![
Type::String,
Type::function(vec![Type::String], Type::Boolean),
],
Type::String,
),
StrFunction::Split => {
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
}
StrFunction::SplitAt => {
Type::function(vec![Type::String, Type::Integer], Type::list(Type::String))
}
StrFunction::SplitInclusive => {
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
}
StrFunction::SplitN => Type::function(
vec![Type::String, Type::Integer, Type::String],
Type::list(Type::String),
),
StrFunction::SplitOnce => {
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
}
StrFunction::SplitTerminator => {
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
}
StrFunction::SplitWhitespace => {
Type::function(vec![Type::String], Type::list(Type::String))
}
StrFunction::StartsWith => {
Type::function(vec![Type::String, Type::String], Type::Boolean)
}
StrFunction::StripPrefix => Type::function(
vec![Type::String, Type::String],
Type::option(Some(Type::String)),
),
StrFunction::ToLowercase => Type::function(vec![Type::String], Type::String),
StrFunction::ToUppercase => Type::function(vec![Type::String], Type::String),
StrFunction::Truncate => {
Type::function(vec![Type::String, Type::Integer], Type::String)
}
StrFunction::Trim => Type::function(vec![Type::String], Type::String),
StrFunction::TrimEnd => Type::function(vec![Type::String], Type::String),
StrFunction::TrimEndMatches => {
Type::function(vec![Type::String, Type::String], Type::String)
}
StrFunction::TrimMatches => {
Type::function(vec![Type::String, Type::String], Type::String)
}
StrFunction::TrimStart => Type::function(vec![Type::String], Type::String),
StrFunction::TrimStartMatches => {
Type::function(vec![Type::String, Type::String], Type::String)
}
}
}
fn call(
&self,
arguments: &[Value],
_source: &str,
_context: &Context,
) -> Result<Value, RuntimeError> {
let value = match self {
StrFunction::AsBytes => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let bytes = string
.bytes()
.map(|byte| Value::Integer(byte as i64))
.collect();
Value::List(List::with_items(bytes))
}
StrFunction::EndsWith => {
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let pattern_string = arguments.get(1).unwrap().as_string()?;
let pattern = pattern_string.as_str();
Value::Boolean(string.ends_with(pattern))
}
StrFunction::Find => {
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let pattern_string = arguments.get(1).unwrap().as_string()?;
let pattern = pattern_string.as_str();
let find = string
.find(pattern)
.map(|index| Value::Integer(index as i64));
if let Some(index) = find {
Value::Enum(EnumInstance::new(
Identifier::new("Option"),
Identifier::new("Some"),
Some(index),
))
} else {
Value::Enum(EnumInstance::new(
Identifier::new("Option"),
Identifier::new("None"),
Some(Value::none()),
))
}
}
StrFunction::IsAscii => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
Value::Boolean(string.is_ascii())
}
StrFunction::IsEmpty => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
Value::Boolean(string.is_empty())
}
StrFunction::Insert => {
RuntimeError::expect_argument_amount(self.name(), 3, arguments.len())?;
let mut string = arguments.first().unwrap().as_string()?.clone();
let index = arguments.get(1).unwrap().as_integer()? as usize;
let insertion = arguments.get(2).unwrap().as_string()?;
string.insert_str(index, insertion);
Value::String(string)
}
StrFunction::Lines => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let lines = string
.lines()
.map(|line| Value::string(line.to_string()))
.collect();
Value::List(List::with_items(lines))
}
StrFunction::Matches => {
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let pattern_string = arguments.get(1).unwrap().as_string()?;
let pattern = pattern_string.as_str();
let matches = string
.matches(pattern)
.map(|pattern| Value::string(pattern.to_string()))
.collect();
Value::List(List::with_items(matches))
}
StrFunction::Parse => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
if let Ok(integer) = string.parse::<i64>() {
Value::Integer(integer)
} else if let Ok(float) = string.parse::<f64>() {
Value::Float(float)
} else {
Value::none()
}
}
StrFunction::Remove => {
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let index = arguments.get(1).unwrap().as_integer()? as usize;
let chars = string.chars().collect::<Vec<char>>();
if index < chars.len() {
let new_string = chars
.iter()
.map(|char| char.to_string())
.collect::<String>();
Value::some(Value::string(new_string))
} else {
Value::none()
}
}
StrFunction::ReplaceRange => {
RuntimeError::expect_argument_amount(self.name(), 3, arguments.len())?;
let mut string = arguments.first().unwrap().as_string()?.clone();
let range = arguments.get(1).unwrap().as_list()?.items()?;
let start = range[0].as_integer()? as usize;
let end = range[1].as_integer()? as usize;
let pattern = arguments.get(2).unwrap().as_string()?;
string.replace_range(start..end, pattern);
Value::String(string)
}
StrFunction::Retain => {
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
todo!();
// let mut string = arguments.first().unwrap().as_string()?.clone();
// let predicate = arguments.get(1).unwrap().as_function()?;
// string.retain(|char| {
// predicate
// .call(&[Value::string(char)], _source, _outer_context)
// .unwrap()
// .as_boolean()
// .unwrap()
// });
// Value::String(string)
}
StrFunction::Split => {
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let pattern_string = arguments.get(1).unwrap().as_string()?;
let pattern = pattern_string.as_str();
let sections = string
.split(pattern)
.map(|section| Value::string(section.to_string()))
.collect();
Value::List(List::with_items(sections))
}
StrFunction::SplitAt => {
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let index = arguments.get(1).unwrap().as_integer()?;
let (left, right) = string.split_at(index as usize);
Value::List(List::with_items(vec![
Value::string(left.to_string()),
Value::string(right.to_string()),
]))
}
StrFunction::SplitInclusive => {
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let pattern_string = arguments.get(1).unwrap().as_string()?;
let pattern = pattern_string.as_str();
let sections = string
.split(pattern)
.map(|pattern| Value::string(pattern.to_string()))
.collect();
Value::List(List::with_items(sections))
}
StrFunction::SplitN => {
RuntimeError::expect_argument_amount(self.name(), 3, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let count = arguments.get(1).unwrap().as_integer()?;
let pattern_string = arguments.get(2).unwrap().as_string()?;
let pattern = pattern_string.as_str();
let sections = string
.splitn(count as usize, pattern)
.map(|pattern| Value::string(pattern.to_string()))
.collect();
Value::List(List::with_items(sections))
}
StrFunction::SplitOnce => {
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let pattern_string = arguments.get(1).unwrap().as_string()?;
let pattern = pattern_string.as_str();
let sections = string.split_once(pattern).map(|(left, right)| {
Value::List(List::with_items(vec![
Value::string(left.to_string()),
Value::string(right.to_string()),
]))
});
if let Some(sections) = sections {
Value::some(sections)
} else {
Value::none()
}
}
StrFunction::SplitTerminator => {
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let pattern_string = arguments.get(1).unwrap().as_string()?;
let pattern = pattern_string.as_str();
let sections = string
.split_terminator(pattern)
.map(|section| Value::string(section.to_string()))
.collect();
Value::List(List::with_items(sections))
}
StrFunction::SplitWhitespace => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let sections = string
.split_whitespace()
.map(|section| Value::string(section.to_string()))
.collect();
Value::List(List::with_items(sections))
}
StrFunction::StartsWith => {
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let pattern_string = arguments.get(1).unwrap().as_string()?;
let pattern = pattern_string.as_str();
Value::Boolean(string.starts_with(pattern))
}
StrFunction::StripPrefix => {
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let prefix_string = arguments.get(1).unwrap().as_string()?;
let prefix = prefix_string.as_str();
let stripped = string
.strip_prefix(prefix)
.map(|remainder| Value::string(remainder.to_string()));
if let Some(value) = stripped {
Value::Enum(EnumInstance::new(
Identifier::new("Option"),
Identifier::new("Some"),
Some(value),
))
} else {
Value::none()
}
}
StrFunction::ToLowercase => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let lowercase = string.to_lowercase();
Value::string(lowercase)
}
StrFunction::ToUppercase => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let uppercase = string.to_uppercase();
Value::string(uppercase)
}
StrFunction::Trim => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let trimmed = arguments.first().unwrap().as_string()?.trim().to_string();
Value::string(trimmed)
}
StrFunction::TrimEnd => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let trimmed = arguments
.first()
.unwrap()
.as_string()?
.trim_end()
.to_string();
Value::string(trimmed)
}
StrFunction::TrimEndMatches => {
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let pattern_string = arguments.get(1).unwrap().as_string()?;
let pattern = pattern_string.as_str();
let trimmed = string.trim_end_matches(pattern).to_string();
Value::string(trimmed)
}
StrFunction::TrimMatches => {
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let pattern = arguments
.get(1)
.unwrap()
.as_string()?
.chars()
.collect::<Vec<char>>();
let trimmed = string.trim_matches(pattern.as_slice()).to_string();
Value::string(trimmed)
}
StrFunction::TrimStart => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let trimmed = arguments
.first()
.unwrap()
.as_string()?
.trim_start()
.to_string();
Value::string(trimmed)
}
StrFunction::TrimStartMatches => {
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
let string = arguments.first().unwrap().as_string()?;
let pattern = arguments
.get(1)
.unwrap()
.as_string()?
.chars()
.collect::<Vec<char>>();
let trimmed = string.trim_start_matches(pattern.as_slice()).to_string();
Value::string(trimmed)
}
StrFunction::Truncate => {
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
let input_string = arguments.first().unwrap().as_string()?;
let new_length = arguments.get(1).unwrap().as_integer()? as usize;
let new_string = input_string
.chars()
.take(new_length)
.map(|char| char.to_string())
.collect();
Value::String(new_string)
}
};
Ok(value)
}
}

View File

@ -1,51 +0,0 @@
use std::sync::{Arc, OnceLock};
use enum_iterator::{all, Sequence};
use crate::Identifier;
pub fn all_built_in_identifiers() -> impl Iterator<Item = BuiltInIdentifier> {
all()
}
static OPTION: OnceLock<Identifier> = OnceLock::new();
static NONE: OnceLock<Identifier> = OnceLock::new();
static SOME: OnceLock<Identifier> = OnceLock::new();
static RESULT: OnceLock<Identifier> = OnceLock::new();
static OK: OnceLock<Identifier> = OnceLock::new();
static ERROR: OnceLock<Identifier> = OnceLock::new();
#[derive(Sequence, Debug)]
pub enum BuiltInIdentifier {
Option,
None,
Some,
Result,
Ok,
Error,
}
impl BuiltInIdentifier {
pub fn get(&self) -> &Identifier {
match self {
BuiltInIdentifier::Option => {
OPTION.get_or_init(|| Identifier::from_raw_parts(Arc::new("Option".to_string())))
}
BuiltInIdentifier::None => {
NONE.get_or_init(|| Identifier::from_raw_parts(Arc::new("None".to_string())))
}
BuiltInIdentifier::Some => {
SOME.get_or_init(|| Identifier::from_raw_parts(Arc::new("Some".to_string())))
}
BuiltInIdentifier::Result => {
RESULT.get_or_init(|| Identifier::from_raw_parts(Arc::new("Result".to_string())))
}
BuiltInIdentifier::Ok => {
OK.get_or_init(|| Identifier::from_raw_parts(Arc::new("Ok".to_string())))
}
BuiltInIdentifier::Error => {
ERROR.get_or_init(|| Identifier::from_raw_parts(Arc::new("Error".to_string())))
}
}
}
}

View File

@ -1,56 +0,0 @@
use std::sync::OnceLock;
use enum_iterator::{all, Sequence};
use crate::{
error::rw_lock_error::RwLockError, Context, EnumDefinition, Identifier, Type, TypeDefinition,
};
static OPTION: OnceLock<Result<TypeDefinition, RwLockError>> = OnceLock::new();
static RESULT: OnceLock<Result<TypeDefinition, RwLockError>> = OnceLock::new();
pub fn all_built_in_type_definitions() -> impl Iterator<Item = BuiltInTypeDefinition> {
all()
}
#[derive(Sequence)]
pub enum BuiltInTypeDefinition {
Option,
Result,
}
impl BuiltInTypeDefinition {
pub fn name(&self) -> &'static str {
match self {
BuiltInTypeDefinition::Option => "Option",
BuiltInTypeDefinition::Result => "Result",
}
}
pub fn get(&self, _context: &Context) -> &Result<TypeDefinition, RwLockError> {
match self {
BuiltInTypeDefinition::Option => OPTION.get_or_init(|| {
let definition = TypeDefinition::Enum(EnumDefinition::new(
Identifier::new(self.name()),
vec![
(Identifier::new("Some"), vec![Type::Any]),
(Identifier::new("None"), Vec::with_capacity(0)),
],
));
Ok(definition)
}),
BuiltInTypeDefinition::Result => RESULT.get_or_init(|| {
let definition = TypeDefinition::Enum(EnumDefinition::new(
Identifier::new(self.name()),
vec![
(Identifier::new("Ok"), vec![Type::Any]),
(Identifier::new("Error"), vec![Type::Any]),
],
));
Ok(definition)
}),
}
}
}

View File

@ -1,29 +0,0 @@
use std::sync::OnceLock;
use crate::{Identifier, Type};
static OPTION: OnceLock<Type> = OnceLock::new();
pub enum BuiltInType {
Option(Option<Type>),
}
impl BuiltInType {
pub fn name(&self) -> &'static str {
match self {
BuiltInType::Option(_) => "Option",
}
}
pub fn get(&self) -> &Type {
match self {
BuiltInType::Option(content_type) => OPTION.get_or_init(|| {
if let Some(content_type) = content_type {
Type::custom(Identifier::new("Option"), vec![content_type.clone()])
} else {
Type::custom(Identifier::new("Option"), Vec::with_capacity(0))
}
}),
}
}
}

View File

@ -1,180 +0,0 @@
use std::{env::args, sync::OnceLock};
use enum_iterator::{all, Sequence};
use serde::{Deserialize, Serialize};
use crate::{
built_in_functions::{fs::fs_functions, json::json_functions, str::string_functions, Callable},
BuiltInFunction, EnumInstance, Function, Identifier, List, Map, Value,
};
static ARGS: OnceLock<Value> = OnceLock::new();
static FS: OnceLock<Value> = OnceLock::new();
static JSON: OnceLock<Value> = OnceLock::new();
static NONE: OnceLock<Value> = OnceLock::new();
static RANDOM: OnceLock<Value> = OnceLock::new();
static STR: OnceLock<Value> = OnceLock::new();
/// Returns the entire built-in value API.
pub fn all_built_in_values() -> impl Iterator<Item = BuiltInValue> {
all()
}
/// A variable with a hard-coded key that is globally available.
#[derive(Sequence, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum BuiltInValue {
/// The arguments used to launch the current program.
Args,
/// Create an error if two values are not equal.
AssertEqual,
/// File system tools.
Fs,
/// JSON format tools.
Json,
/// Get the length of a collection.
Length,
/// The absence of a value.
None,
/// Print a value to stdout.
Output,
/// Random value generators.
Random,
/// String utilities.
Str,
}
impl BuiltInValue {
/// Returns the hard-coded key used to identify the value.
pub fn name(&self) -> &'static str {
match self {
BuiltInValue::Args => "args",
BuiltInValue::AssertEqual => "assert_equal",
BuiltInValue::Fs => "fs",
BuiltInValue::Json => "json",
BuiltInValue::Length => BuiltInFunction::Length.name(),
BuiltInValue::None => "None",
BuiltInValue::Output => "output",
BuiltInValue::Random => "random",
BuiltInValue::Str => "str",
}
}
/// Returns a brief description of the value's features.
///
/// This is used by the shell when suggesting completions.
pub fn description(&self) -> &'static str {
match self {
BuiltInValue::Args => "The command line arguments sent to this program.",
BuiltInValue::AssertEqual => "Error if the two values are not equal.",
BuiltInValue::Fs => "File and directory tools.",
BuiltInValue::Json => "JSON formatting tools.",
BuiltInValue::Length => BuiltInFunction::Length.description(),
BuiltInValue::None => "The absence of a value.",
BuiltInValue::Output => "output",
BuiltInValue::Random => "random",
BuiltInValue::Str => "string",
}
}
/// Returns the value by creating it or, if it has already been accessed, retrieving it from its
/// [OnceLock][].
pub fn get(&self) -> Value {
match self {
BuiltInValue::Args => ARGS
.get_or_init(|| {
let args = args().map(|arg| Value::string(arg.to_string())).collect();
Value::List(List::with_items(args))
})
.clone(),
BuiltInValue::AssertEqual => {
Value::Function(Function::BuiltIn(BuiltInFunction::AssertEqual))
}
BuiltInValue::Fs => FS
.get_or_init(|| {
let mut fs_map = Map::new();
for fs_function in fs_functions() {
let key = fs_function.name();
let value =
Value::Function(Function::BuiltIn(BuiltInFunction::Fs(fs_function)));
fs_map.set(Identifier::new(key), value);
}
Value::Map(fs_map)
})
.clone(),
BuiltInValue::Json => JSON
.get_or_init(|| {
let mut json_map = Map::new();
for json_function in json_functions() {
let key = json_function.name();
let value = Value::Function(Function::BuiltIn(BuiltInFunction::Json(
json_function,
)));
json_map.set(Identifier::new(key), value);
}
Value::Map(json_map)
})
.clone(),
BuiltInValue::Length => Value::Function(Function::BuiltIn(BuiltInFunction::Length)),
BuiltInValue::None => NONE
.get_or_init(|| {
Value::Enum(EnumInstance::new(
Identifier::new("Option"),
Identifier::new("None"),
None,
))
})
.clone(),
BuiltInValue::Output => Value::Function(Function::BuiltIn(BuiltInFunction::Output)),
BuiltInValue::Random => RANDOM
.get_or_init(|| {
let mut random_map = Map::new();
for built_in_function in [
BuiltInFunction::RandomBoolean,
BuiltInFunction::RandomFloat,
BuiltInFunction::RandomFrom,
BuiltInFunction::RandomInteger,
] {
let identifier = Identifier::new(built_in_function.name());
let value = Value::Function(Function::BuiltIn(built_in_function));
random_map.set(identifier, value);
}
Value::Map(random_map)
})
.clone(),
BuiltInValue::Str => STR
.get_or_init(|| {
let mut str_map = Map::new();
for string_function in string_functions() {
let identifier = Identifier::new(string_function.name());
let value = Value::Function(Function::BuiltIn(BuiltInFunction::String(
string_function,
)));
str_map.set(identifier, value);
}
Value::Map(str_map)
})
.clone(),
}
}
}

View File

@ -1,395 +0,0 @@
//! A garbage-collecting execution context that stores variables and type data
//! during the [Interpreter][crate::Interpreter]'s abstraction and execution
//! process.
//!
//! ## Setting values
//!
//! When data is stored in a context, it can be accessed by dust source code.
//! This allows you to insert values and type definitions before any code is
//! interpreted.
//!
//! ```
//! # use dust_lang::*;
//! let context = Context::default();
//!
//! context.set_value(
//! "foobar".into(),
//! Value::String("FOOBAR".to_string())
//! ).unwrap();
//!
//! interpret_with_context("output foobar", context);
//!
//! // Stdout: "FOOBAR"
//! ```
//!
//! ## Built-in values and type definitions
//!
//! When looking up values and definitions, the Context will try to use one that
//! has been explicitly set. If nothing is found, it will then check the built-
//! in values and type definitions for a match. This means that the user can
//! override the built-ins.
//!
//! ## Garbage Collection
//!
//! To disable garbage collection, run a Context in AllowGarbage mode.
//!
//! ```
//! # use dust_lang::*;
//! let context = Context::new(ContextMode::AllowGarbage);
//! ```
//!
//!
//! Every item stored in a Context has a counter attached to it. You must use
//! [Context::add_allowance][] to let the Context know not to drop the value.
//! Every time you use [Context::get_value][] it checks the number of times it
//! has been used and compares it to the number of allowances. If the limit
//! has been reached, the value will be removed from the context and can no
//! longer be found.
mod usage_counter;
mod value_data;
pub use usage_counter::UsageCounter;
pub use value_data::ValueData;
use std::{
cmp::Ordering,
collections::BTreeMap,
fmt::Display,
sync::{Arc, RwLock, RwLockReadGuard},
};
use crate::{
built_in_type_definitions::all_built_in_type_definitions, built_in_values::all_built_in_values,
error::rw_lock_error::RwLockError, Identifier, Type, TypeDefinition, Value,
};
#[derive(Clone, Debug, PartialEq)]
pub enum ContextMode {
AllowGarbage,
RemoveGarbage,
}
/// An execution context stores that variable and type data during the
/// [Interpreter]'s abstraction and execution process.
///
/// See the [module-level docs][self] for more info.
#[derive(Clone, Debug)]
pub struct Context {
mode: ContextMode,
inner: Arc<RwLock<BTreeMap<Identifier, (ValueData, UsageCounter)>>>,
}
impl Context {
/// Return a new, empty Context.
pub fn new(mode: ContextMode) -> Self {
Self {
mode,
inner: Arc::new(RwLock::new(BTreeMap::new())),
}
}
/// Return a lock guard to the inner BTreeMap.
pub fn inner(
&self,
) -> Result<RwLockReadGuard<BTreeMap<Identifier, (ValueData, UsageCounter)>>, RwLockError> {
Ok(self.inner.read()?)
}
/// Create a new context with all of the data from an existing context.
pub fn with_variables_from(other: &Context) -> Result<Context, RwLockError> {
let mut new_variables = BTreeMap::new();
for (identifier, (value_data, counter)) in other.inner.read()?.iter() {
let (allowances, _runtime_uses) = counter.get_counts()?;
let new_counter = UsageCounter::with_counts(allowances, 0);
new_variables.insert(identifier.clone(), (value_data.clone(), new_counter));
}
Ok(Context {
mode: other.mode.clone(),
inner: Arc::new(RwLock::new(new_variables)),
})
}
/// Modify a context to take the functions and type definitions of another.
///
/// In the case of the conflict, the inherited value will override the previous
/// value.
pub fn inherit_from(&self, other: &Context) -> Result<(), RwLockError> {
let mut self_variables = self.inner.write()?;
for (identifier, (value_data, counter)) in other.inner.read()?.iter() {
let (allowances, _runtime_uses) = counter.get_counts()?;
let new_counter = UsageCounter::with_counts(allowances, 0);
if let ValueData::Value(value) = value_data {
if value.is_function() {
self_variables.insert(identifier.clone(), (value_data.clone(), new_counter));
}
} else if let ValueData::TypeHint(r#type) = value_data {
if r#type.is_function() {
self_variables.insert(identifier.clone(), (value_data.clone(), new_counter));
}
} else if let ValueData::TypeDefinition(_) = value_data {
self_variables.insert(identifier.clone(), (value_data.clone(), new_counter));
}
}
Ok(())
}
/// Modify a context to take all the information of another.
///
/// In the case of the conflict, the inherited value will override the previous
/// value.
///
/// ```
/// # use dust_lang::*;
/// let first_context = Context::default();
/// let second_context = Context::default();
///
/// second_context.set_value(
/// "Foo".into(),
/// Value::String("Bar".to_string())
/// );
///
/// first_context.inherit_all_from(&second_context).unwrap();
///
/// assert_eq!(first_context, second_context);
/// ```
pub fn inherit_all_from(&self, other: &Context) -> Result<(), RwLockError> {
let mut self_variables = self.inner.write()?;
for (identifier, (value_data, _counter)) in other.inner.read()?.iter() {
self_variables.insert(
identifier.clone(),
(value_data.clone(), UsageCounter::new()),
);
}
Ok(())
}
/// Increment the number of allowances a variable has. Return a boolean
/// representing whether or not the variable was found.
pub fn add_allowance(&self, identifier: &Identifier) -> Result<bool, RwLockError> {
if let Some((_value_data, counter)) = self.inner.read()?.get(identifier) {
log::debug!("Adding allowance for {identifier}.");
counter.add_allowance()?;
Ok(true)
} else {
Ok(false)
}
}
/// Get a [Value] from the context.
pub fn get_value(&self, identifier: &Identifier) -> Result<Option<Value>, RwLockError> {
let (value, counter) =
if let Some((value_data, counter)) = self.inner.read()?.get(identifier) {
if let ValueData::Value(value) = value_data {
(value.clone(), counter.clone())
} else {
return Ok(None);
}
} else {
for built_in_value in all_built_in_values() {
if built_in_value.name() == identifier.inner().as_ref() {
return Ok(Some(built_in_value.get().clone()));
}
}
return Ok(None);
};
counter.add_runtime_use()?;
log::debug!("Adding runtime use for {identifier}.");
let (allowances, runtime_uses) = counter.get_counts()?;
if self.mode == ContextMode::RemoveGarbage && allowances == runtime_uses {
self.unset(identifier)?;
}
Ok(Some(value))
}
/// Get a [Type] from the context.
///
/// If the key matches a stored [Value], its type will be returned. It if
/// matches a type hint, the type hint will be returned.
pub fn get_type(&self, identifier: &Identifier) -> Result<Option<Type>, RwLockError> {
if let Some((value_data, _counter)) = self.inner.read()?.get(identifier) {
match value_data {
ValueData::Value(value) => return Ok(Some(value.r#type()?)),
ValueData::TypeHint(r#type) => return Ok(Some(r#type.clone())),
ValueData::TypeDefinition(_) => todo!(),
}
}
for built_in_value in all_built_in_values() {
if built_in_value.name() == identifier.inner().as_ref() {
return Ok(Some(built_in_value.get().r#type()?));
}
}
Ok(None)
}
/// Get a [TypeDefinition] from the context.
///
/// This will also return a built-in type definition if one matches the key.
/// See the [module-level docs][self] for more info.
pub fn get_definition(
&self,
identifier: &Identifier,
) -> Result<Option<TypeDefinition>, RwLockError> {
if let Some((value_data, _counter)) = self.inner.read()?.get(identifier) {
if let ValueData::TypeDefinition(definition) = value_data {
return Ok(Some(definition.clone()));
}
}
for built_in_definition in all_built_in_type_definitions() {
if built_in_definition.name() == identifier.inner().as_ref() {
return Ok(Some(built_in_definition.get(self).clone()?));
}
}
Ok(None)
}
/// Set a value to a key.
pub fn set_value(&self, key: Identifier, value: Value) -> Result<(), RwLockError> {
let mut map = self.inner.write()?;
let old_data = map.remove(&key);
if let Some((_, old_counter)) = old_data {
map.insert(key, (ValueData::Value(value), old_counter));
} else {
map.insert(key, (ValueData::Value(value), UsageCounter::new()));
}
Ok(())
}
/// Set a type hint.
///
/// This allows the interpreter to check a value's type before the value
/// actually exists by predicting what the abstract tree will produce.
pub fn set_type(&self, key: Identifier, r#type: Type) -> Result<(), RwLockError> {
self.inner
.write()?
.insert(key, (ValueData::TypeHint(r#type), UsageCounter::new()));
Ok(())
}
/// Set a type definition.
///
/// This allows defined types (i.e. structs and enums) to be instantiated
/// later while running the interpreter using this context.
pub fn set_definition(
&self,
key: Identifier,
definition: TypeDefinition,
) -> Result<(), RwLockError> {
self.inner.write()?.insert(
key,
(ValueData::TypeDefinition(definition), UsageCounter::new()),
);
Ok(())
}
/// Remove a key-value pair.
pub fn unset(&self, key: &Identifier) -> Result<(), RwLockError> {
log::debug!("Dropping variable {key}.");
self.inner.write()?.remove(key);
Ok(())
}
}
impl Default for Context {
fn default() -> Self {
Context::new(ContextMode::RemoveGarbage)
}
}
impl Eq for Context {}
impl PartialEq for Context {
fn eq(&self, other: &Self) -> bool {
let self_variables = self.inner().unwrap();
let other_variables = other.inner().unwrap();
if self_variables.len() != other_variables.len() {
return false;
}
for ((left_key, left_value_data), (right_key, right_value_data)) in
self_variables.iter().zip(other_variables.iter())
{
if left_key != right_key || left_value_data != right_value_data {
return false;
}
}
true
}
}
impl PartialOrd for Context {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Context {
fn cmp(&self, other: &Self) -> Ordering {
let left = self.inner().unwrap();
let right = other.inner().unwrap();
left.cmp(&right)
}
}
impl Display for Context {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{{")?;
for (identifier, value_data) in self.inner.read().unwrap().iter() {
writeln!(f, "{identifier} {value_data:?}")?;
}
writeln!(f, "}}")
}
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
fn drops_variables() {
let context = Context::default();
interpret_with_context(
"
x = 1
y = 2
z = x + y
",
context.clone(),
)
.unwrap();
assert_eq!(context.inner.read().unwrap().len(), 1);
}
}

View File

@ -1,74 +0,0 @@
use std::{
cmp::Ordering,
sync::{Arc, RwLock},
};
use crate::error::rw_lock_error::RwLockError;
#[derive(Clone, Debug)]
pub struct UsageCounter(Arc<RwLock<UsageCounterInner>>);
impl UsageCounter {
pub fn new() -> UsageCounter {
UsageCounter(Arc::new(RwLock::new(UsageCounterInner {
allowances: 0,
runtime_uses: 0,
})))
}
pub fn with_counts(allowances: usize, runtime_uses: usize) -> UsageCounter {
UsageCounter(Arc::new(RwLock::new(UsageCounterInner {
allowances,
runtime_uses,
})))
}
pub fn get_counts(&self) -> Result<(usize, usize), RwLockError> {
let inner = self.0.read()?;
Ok((inner.allowances, inner.runtime_uses))
}
pub fn add_allowance(&self) -> Result<(), RwLockError> {
self.0.write()?.allowances += 1;
Ok(())
}
pub fn add_runtime_use(&self) -> Result<(), RwLockError> {
self.0.write()?.runtime_uses += 1;
Ok(())
}
}
impl Eq for UsageCounter {}
impl PartialEq for UsageCounter {
fn eq(&self, other: &Self) -> bool {
let left = self.0.read().unwrap();
let right = other.0.read().unwrap();
*left == *right
}
}
impl PartialOrd for UsageCounter {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for UsageCounter {
fn cmp(&self, other: &Self) -> Ordering {
let left = self.0.read().unwrap();
let right = other.0.read().unwrap();
left.cmp(&right)
}
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
struct UsageCounterInner {
pub allowances: usize,
pub runtime_uses: usize,
}

View File

@ -1,8 +0,0 @@
use crate::{Type, TypeDefinition, Value};
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum ValueData {
Value(Value),
TypeHint(Type),
TypeDefinition(TypeDefinition),
}

550
src/error.rs Normal file
View File

@ -0,0 +1,550 @@
//! Error and Result types.
//!
//! To deal with errors from dependencies, either create a new error variant
//! or use the MacroFailure variant if the error can only occur inside a macro.
use crate::{value::value_type::ValueType, value::Value};
use std::{fmt, io, time::SystemTimeError};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Error {
UnexpectedSyntax {
expected: &'static str,
actual: &'static str,
location: tree_sitter::Point,
},
ExpectedFieldName,
ExpectedChildNode {
empty_node_sexp: 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.
ExpectedFunctionArgumentAmount {
identifier: String,
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,
},
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,
},
/// Tried to append a child to a node such that the precedence of the child
/// is not higher. This error should never occur. If it does, please file a
/// bug report.
PrecedenceViolation,
/// 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(String),
/// A value has the wrong type.
/// Only use this if there is no other error that describes the expected and
/// provided types in more detail.
TypeError {
/// The expected types.
expected: &'static [ValueType],
/// The actual value.
actual: Value,
},
/// An operator is used with a wrong combination of types.
WrongTypeCombination {
/// The operator that whose evaluation caused the error.
expected: ValueType,
/// The types that were used in the operator causing it to fail.
actual: ValueType,
},
/// An opening brace without a matching closing brace was found.
UnmatchedLBrace,
/// A closing brace without a matching opening brace was found.
UnmatchedRBrace,
/// Left of an opening brace or right of a closing brace is a token that does not expect the brace next to it.
/// For example, writing `4(5)` would yield this error, as the `4` does not have any operands.
MissingOperatorOutsideOfBrace,
/// An addition operation performed by Rust failed.
AdditionError {
/// The first argument of the addition.
augend: Value,
/// The second argument of the addition.
addend: Value,
},
/// A subtraction operation performed by Rust failed.
SubtractionError {
/// The first argument of the subtraction.
minuend: Value,
/// The second argument of the subtraction.
subtrahend: Value,
},
/// A negation operation performed by Rust failed.
NegationError {
/// The argument of the negation.
argument: Value,
},
/// A multiplication operation performed by Rust failed.
MultiplicationError {
/// The first argument of the multiplication.
multiplicand: Value,
/// The second argument of the multiplication.
multiplier: Value,
},
/// A division operation performed by Rust failed.
DivisionError {
/// The first argument of the division.
dividend: Value,
/// The second argument of the division.
divisor: Value,
},
/// A modulation operation performed by Rust failed.
ModulationError {
/// The first argument of the modulation.
dividend: Value,
/// The second argument of the modulation.
divisor: Value,
},
/// A regular expression could not be parsed
InvalidRegex {
/// The invalid regular expression
regex: String,
/// Failure message from the regex engine
message: String,
},
/// A modification was attempted on a `Context` that does not allow modifications.
ContextNotMutable,
/// An escape sequence within a string literal is illegal.
IllegalEscapeSequence(String),
/// This context does not allow enabling builtin functions.
BuiltinFunctionsCannotBeEnabled,
/// This context does not allow disabling builtin functions.
BuiltinFunctionsCannotBeDisabled,
/// The function failed due to an external error.
MacroFailure(String),
/// A custom error explained by its message.
CustomMessage(String),
}
impl From<csv::Error> for Error {
fn from(value: csv::Error) -> Self {
Error::MacroFailure(value.to_string())
}
}
impl From<json::Error> for Error {
fn from(value: json::Error) -> Self {
Error::MacroFailure(value.to_string())
}
}
impl From<io::Error> for Error {
fn from(value: std::io::Error) -> Self {
Error::MacroFailure(value.to_string())
}
}
impl From<git2::Error> for Error {
fn from(value: git2::Error) -> Self {
Error::MacroFailure(value.to_string())
}
}
impl From<reqwest::Error> for Error {
fn from(value: reqwest::Error) -> Self {
Error::MacroFailure(value.to_string())
}
}
impl From<serde_json::Error> for Error {
fn from(value: serde_json::Error) -> Self {
Error::MacroFailure(value.to_string())
}
}
impl From<SystemTimeError> for Error {
fn from(value: SystemTimeError) -> Self {
Error::MacroFailure(value.to_string())
}
}
impl From<trash::Error> for Error {
fn from(value: trash::Error) -> Self {
Error::MacroFailure(value.to_string())
}
}
impl From<toml::de::Error> for Error {
fn from(value: toml::de::Error) -> Self {
Error::MacroFailure(value.to_string())
}
}
impl Error {
pub(crate) fn expect_function_argument_amount(
identifier: &str,
actual: usize,
expected: usize,
) -> Result<()> {
if actual == expected {
Ok(())
} else {
Err(Error::ExpectedFunctionArgumentAmount {
identifier: identifier.to_string(),
expected,
actual,
})
}
}
pub(crate) fn expected_minimum_function_argument_amount(
identifier: &str,
actual: usize,
minimum: usize,
) -> Result<()> {
if actual >= minimum {
Ok(())
} else {
Err(Error::ExpectedAtLeastFunctionArgumentAmount {
identifier: identifier.to_string(),
minimum,
actual,
})
}
}
pub fn type_error(actual: Value, expected: &'static [ValueType]) -> Self {
Error::TypeError { actual, expected }
}
pub fn expected_string(actual: Value) -> Self {
Error::ExpectedString { actual }
}
pub fn expected_int(actual: Value) -> Self {
Error::ExpectedInt { actual }
}
pub fn expected_float(actual: Value) -> Self {
Error::ExpectedFloat { actual }
}
pub fn expected_number(actual: Value) -> Self {
Error::ExpectedNumber { actual }
}
pub fn expected_number_or_string(actual: Value) -> Self {
Error::ExpectedNumberOrString { actual }
}
pub fn expected_boolean(actual: Value) -> Self {
Error::ExpectedBoolean { actual }
}
pub fn expected_list(actual: Value) -> Self {
Error::ExpectedList { actual }
}
pub fn expected_fixed_len_list(expected_len: usize, actual: Value) -> Self {
Error::ExpectedFixedLenList {
expected_len,
actual,
}
}
pub fn expected_empty(actual: Value) -> Self {
Error::ExpectedEmpty { actual }
}
pub fn expected_map(actual: Value) -> Self {
Error::ExpectedMap { actual }
}
pub fn expected_table(actual: Value) -> Self {
Error::ExpectedTable { actual }
}
pub fn expected_function(actual: Value) -> Self {
Error::ExpectedFunction { actual }
}
pub fn expected_collection(actual: Value) -> Self {
Error::ExpectedCollection { actual }
}
/// Constructs `EvalexprError::InvalidRegex(regex)`
pub fn invalid_regex(regex: String, message: String) -> Self {
Error::InvalidRegex { regex, message }
}
}
/// Returns `Ok(())` if the given value is a string or a numeric.
pub fn expect_number_or_string(actual: &Value) -> Result<()> {
match actual {
Value::String(_) | Value::Float(_) | Value::Integer(_) => Ok(()),
_ => Err(Error::expected_number_or_string(actual.clone())),
}
}
impl std::error::Error for Error {}
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. {expected} does not equal {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
),
ExpectedFunctionArgumentAmount {
expected,
actual,
identifier,
} => write!(
f,
"{identifier} 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::Tuple, but got {:?}.", actual),
ExpectedFixedLenList {
expected_len,
actual,
} => write!(
f,
"Expected a Value::Tuple 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
)
}
PrecedenceViolation => write!(
f,
"Tried to append a node to another node with higher precedence."
),
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
),
TypeError { expected, actual } => {
write!(
f,
"Type Error. The value {actual} is not one of the following: {expected:?}.",
)
}
UnmatchedLBrace => write!(f, "Found an unmatched opening parenthesis '('."),
UnmatchedRBrace => write!(f, "Found an unmatched closing parenthesis ')'."),
MissingOperatorOutsideOfBrace { .. } => write!(
f,
"Found an opening parenthesis that is preceded by something that does not take \
any arguments on the right, or found a closing parenthesis that is succeeded by \
something that does not take any arguments on the left."
),
AdditionError { augend, addend } => write!(f, "Error adding {} + {}", augend, addend),
SubtractionError {
minuend,
subtrahend,
} => write!(f, "Error subtracting {} - {}", minuend, subtrahend),
NegationError { argument } => write!(f, "Error negating -{}", argument),
MultiplicationError {
multiplicand,
multiplier,
} => write!(f, "Error multiplying {} * {}", multiplicand, multiplier),
DivisionError { dividend, divisor } => {
write!(f, "Error dividing {} / {}", dividend, divisor)
}
ModulationError { dividend, divisor } => {
write!(f, "Error modulating {} % {}", dividend, divisor)
}
InvalidRegex { regex, message } => write!(
f,
"Regular expression {:?} is invalid: {:?}",
regex, message
),
ContextNotMutable => write!(f, "Cannot manipulate context"),
BuiltinFunctionsCannotBeEnabled => {
write!(f, "This context does not allow enabling builtin functions")
}
BuiltinFunctionsCannotBeDisabled => {
write!(f, "This context does not allow disabling builtin functions")
}
IllegalEscapeSequence(string) => write!(f, "Illegal escape sequence: {}", string),
MacroFailure(message) => write!(f, "Function failure: {}", message),
CustomMessage(message) => write!(f, "Error: {}", message),
WrongColumnAmount { expected, actual } => write!(
f,
"Wrong number of columns for this table. Expected {expected}, found {actual}."
),
UnexpectedSyntax {
expected,
actual,
location,
} => write!(
f,
"Unexpected syntax at {location}. Expected {expected}, but found {actual}."
),
ExpectedFieldName => write!(
f,
"Expected a field name for this node, but none was found."
),
WrongTypeCombination { expected, actual } => write!(
f,
"Wrong type combination. Expected {expected}, found {actual}."
),
ExpectedChildNode { empty_node_sexp } => {
write!(f, "Expected this node to have a child, {empty_node_sexp}.")
}
}
}
}

View File

@ -1,110 +0,0 @@
//! 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.
mod runtime_error;
pub(crate) mod rw_lock_error;
mod syntax_error;
mod validation_error;
use colored::Colorize;
pub use runtime_error::RuntimeError;
pub use syntax_error::SyntaxError;
pub use validation_error::ValidationError;
use tree_sitter::LanguageError;
use std::fmt::{self, Formatter};
#[derive(Debug, PartialEq)]
pub enum Error {
Syntax(SyntaxError),
Validation(ValidationError),
Runtime(RuntimeError),
ParserCancelled,
Language(LanguageError),
}
impl Error {
/// Create a pretty error report with `lyneate`.
///
/// The `source` argument should be the full source code document that was
/// used to create this error.
pub fn create_report(&self, source: &str) -> String {
match self {
Error::Syntax(syntax_error) => {
let report = syntax_error.create_report(source);
format!(
"{}\n{}\n{report}",
"Syntax Error".bold().yellow().underline(),
"Dust does not recognize this syntax.".dimmed()
)
}
Error::Validation(validation_error) => {
let report = validation_error.create_report(source);
format!(
"{}\n{}\n{report}",
"Validation Error".bold().yellow().underline(),
"Dust prevented the program from running.".dimmed()
)
}
Error::Runtime(runtime_error) => {
let report = runtime_error.create_report(source);
format!(
"{}\n{}\n{report}",
"Runtime Error".bold().red().underline(),
"This error occured while the program was running.".dimmed()
)
}
Error::ParserCancelled => todo!(),
Error::Language(_) => todo!(),
}
}
}
impl From<SyntaxError> for Error {
fn from(error: SyntaxError) -> Self {
Error::Syntax(error)
}
}
impl From<ValidationError> for Error {
fn from(error: ValidationError) -> Self {
Error::Validation(error)
}
}
impl From<RuntimeError> for Error {
fn from(error: RuntimeError) -> Self {
Error::Runtime(error)
}
}
impl From<LanguageError> for Error {
fn from(error: LanguageError) -> Self {
Error::Language(error)
}
}
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
use Error::*;
match self {
Syntax(error) => write!(f, "{error}"),
Validation(error) => write!(f, "{error}"),
Runtime(error) => write!(f, "{error}"),
ParserCancelled => write!(f, "Parsing was cancelled because the parser took too long."),
Language(_error) => write!(f, "Parser failed to load language grammar."),
}
}
}

View File

@ -1,193 +0,0 @@
use std::{
fmt::{self, Debug, Display, Formatter},
io,
num::ParseFloatError,
string::FromUtf8Error,
sync::PoisonError,
time,
};
use lyneate::Report;
use crate::{SourcePosition, Type, Value};
use super::{rw_lock_error::RwLockError, ValidationError};
#[derive(Debug, PartialEq)]
pub enum RuntimeError {
/// The 'assert' macro did not resolve successfully.
AssertEqualFailed {
left: Value,
right: Value,
},
/// The 'assert' macro did not resolve successfully.
AssertFailed {
assertion: Value,
},
/// The attempted conversion is impossible.
ConversionImpossible {
from: Type,
to: Type,
position: SourcePosition,
},
Csv(String),
Io(String),
Reqwest(String),
Json(String),
SystemTime(String),
Toml(toml::de::Error),
/// Failed to read or write a map.
///
/// See the [MapError] docs for more info.
RwLock(RwLockError),
ParseFloat(ParseFloatError),
Utf8(FromUtf8Error),
/// A built-in function was called with the wrong amount of arguments.
ExpectedBuiltInFunctionArgumentAmount {
function_name: String,
expected: usize,
actual: usize,
},
ValidationFailure(ValidationError),
}
impl RuntimeError {
pub fn create_report(&self, source: &str) -> String {
let messages = match self {
RuntimeError::AssertEqualFailed {
left: expected,
right: actual,
} => {
vec![(
0..source.len(),
format!("\"assert_equal\" failed. {} != {}", expected, actual),
(200, 0, 0),
)]
}
RuntimeError::AssertFailed { assertion: _ } => todo!(),
RuntimeError::ConversionImpossible { from, to, position } => vec![(
position.start_byte..position.end_byte,
format!("Cannot convert from {from} to {to}."),
(255, 64, 112),
)],
RuntimeError::Csv(_) => todo!(),
RuntimeError::Io(_) => todo!(),
RuntimeError::Reqwest(_) => todo!(),
RuntimeError::Json(_) => todo!(),
RuntimeError::SystemTime(_) => todo!(),
RuntimeError::Toml(_) => todo!(),
RuntimeError::RwLock(_) => todo!(),
RuntimeError::ParseFloat(_) => todo!(),
RuntimeError::Utf8(_) => todo!(),
RuntimeError::ExpectedBuiltInFunctionArgumentAmount {
function_name: _,
expected: _,
actual: _,
} => todo!(),
RuntimeError::ValidationFailure(_) => todo!(),
};
Report::new_byte_spanned(source, messages).display_str()
}
pub fn expect_argument_amount(
function_name: &str,
expected: usize,
actual: usize,
) -> Result<(), Self> {
if expected == actual {
Ok(())
} else {
Err(RuntimeError::ExpectedBuiltInFunctionArgumentAmount {
function_name: function_name.to_string(),
expected,
actual,
})
}
}
}
impl From<ValidationError> for RuntimeError {
fn from(error: ValidationError) -> Self {
RuntimeError::ValidationFailure(error)
}
}
impl From<csv::Error> for RuntimeError {
fn from(error: csv::Error) -> Self {
RuntimeError::Csv(error.to_string())
}
}
impl From<io::Error> for RuntimeError {
fn from(error: std::io::Error) -> Self {
RuntimeError::Io(error.to_string())
}
}
impl From<reqwest::Error> for RuntimeError {
fn from(error: reqwest::Error) -> Self {
RuntimeError::Reqwest(error.to_string())
}
}
impl From<serde_json::Error> for RuntimeError {
fn from(error: serde_json::Error) -> Self {
RuntimeError::Json(error.to_string())
}
}
impl From<time::SystemTimeError> for RuntimeError {
fn from(error: time::SystemTimeError) -> Self {
RuntimeError::SystemTime(error.to_string())
}
}
impl From<toml::de::Error> for RuntimeError {
fn from(error: toml::de::Error) -> Self {
RuntimeError::Toml(error)
}
}
impl From<ParseFloatError> for RuntimeError {
fn from(error: ParseFloatError) -> Self {
RuntimeError::ParseFloat(error)
}
}
impl From<FromUtf8Error> for RuntimeError {
fn from(error: FromUtf8Error) -> Self {
RuntimeError::Utf8(error)
}
}
impl From<RwLockError> for RuntimeError {
fn from(error: RwLockError) -> Self {
RuntimeError::RwLock(error)
}
}
impl<T> From<PoisonError<T>> for RuntimeError {
fn from(_: PoisonError<T>) -> Self {
RuntimeError::RwLock(RwLockError)
}
}
impl Display for RuntimeError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}

View File

@ -1,30 +0,0 @@
use std::{
fmt::{self, Debug, Display, Formatter},
sync::PoisonError,
};
use serde::{Deserialize, Serialize};
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct RwLockError;
impl Display for RwLockError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"Map error: failed to acquire a read/write lock because another thread has panicked."
)
}
}
impl Debug for RwLockError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{self}")
}
}
impl<T> From<PoisonError<T>> for RwLockError {
fn from(_: PoisonError<T>) -> Self {
RwLockError
}
}

View File

@ -1,126 +0,0 @@
use std::fmt::{self, Display, Formatter};
use colored::Colorize;
use lyneate::Report;
use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::SourcePosition;
use super::rw_lock_error::RwLockError;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum SyntaxError {
/// Invalid user input.
InvalidSource {
expected: String,
actual: String,
position: SourcePosition,
},
RwLock(RwLockError),
UnexpectedSyntaxNode {
expected: String,
actual: String,
position: SourcePosition,
},
}
impl SyntaxError {
pub fn create_report(&self, source: &str) -> String {
let messages = match self {
SyntaxError::InvalidSource { position, .. } => self
.to_string()
.split_inclusive(".")
.map(|message_part| {
(
position.start_byte..position.end_byte,
message_part.to_string(),
(255, 200, 100),
)
})
.collect(),
SyntaxError::RwLock(_) => todo!(),
SyntaxError::UnexpectedSyntaxNode { position, .. } => {
vec![(
position.start_byte..position.end_byte,
self.to_string(),
(255, 200, 100),
)]
}
};
Report::new_byte_spanned(source, messages).display_str()
}
pub fn expect_syntax_node(expected: &str, actual: SyntaxNode) -> Result<(), SyntaxError> {
log::trace!("Converting {} to abstract node", actual.kind());
if expected == actual.kind() {
Ok(())
} else if actual.is_error() {
Err(SyntaxError::InvalidSource {
expected: expected.to_owned(),
actual: actual.kind().to_string(),
position: SourcePosition::from(actual.range()),
})
} else {
Err(SyntaxError::UnexpectedSyntaxNode {
expected: expected.to_string(),
actual: actual.kind().to_string(),
position: SourcePosition::from(actual.range()),
})
}
}
}
impl From<RwLockError> for SyntaxError {
fn from(error: RwLockError) -> Self {
SyntaxError::RwLock(error)
}
}
impl Display for SyntaxError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
SyntaxError::InvalidSource {
expected,
actual,
position,
} => {
let actual = if actual == "ERROR" {
"unrecognized characters"
} else {
actual
};
write!(
f,
"Invalid syntax from ({}, {}) to ({}, {}). Exected {} but found {}.",
position.start_row,
position.start_column,
position.end_row,
position.end_column,
expected.bold().green(),
actual.bold().red(),
)
}
SyntaxError::RwLock(_) => todo!(),
SyntaxError::UnexpectedSyntaxNode {
expected,
actual,
position,
} => {
write!(
f,
"Interpreter Error. Tried to parse {actual} as {expected} from ({}, {}) to ({}, {}).",
position.start_row,
position.start_column,
position.end_row,
position.end_column,
)
}
}
}
}

View File

@ -1,274 +0,0 @@
use std::fmt::{self, Display, Formatter};
use colored::Colorize;
use lyneate::Report;
use serde::{Deserialize, Serialize};
use crate::{Identifier, SourcePosition, Type, TypeDefinition, Value};
use super::rw_lock_error::RwLockError;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum ValidationError {
/// Two value are incompatible for addition.
CannotAdd {
left: Value,
right: Value,
position: SourcePosition,
},
/// Two value are incompatible for subtraction.
CannotSubtract {
left: Value,
right: Value,
position: SourcePosition,
},
/// Two value are incompatible for multiplication.
CannotMultiply {
left: Value,
right: Value,
position: SourcePosition,
},
/// Two value are incompatible for dividing.
CannotDivide {
left: Value,
right: Value,
position: SourcePosition,
},
/// The attempted conversion is impossible.
ConversionImpossible {
initial_type: Type,
target_type: Type,
},
ExpectedString {
actual: Value,
},
ExpectedInteger {
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,
},
ExpectedMap {
actual: Value,
},
ExpectedFunction {
actual: Value,
},
/// A string, list, map or table value was expected.
ExpectedCollection {
actual: Value,
},
/// A built-in function was called with the wrong amount of arguments.
ExpectedBuiltInFunctionArgumentAmount {
function_name: String,
expected: usize,
actual: usize,
},
/// A function was called with the wrong amount of arguments.
ExpectedFunctionArgumentAmount {
expected: usize,
actual: usize,
position: SourcePosition,
},
/// A function was called with the wrong amount of arguments.
ExpectedFunctionArgumentMinimum {
minumum_expected: usize,
actual: usize,
position: SourcePosition,
},
/// Failed to read or write a map.
///
/// See the [MapError] docs for more info.
RwLock(RwLockError),
TypeCheck {
expected: Type,
actual: Type,
position: SourcePosition,
},
TypeCheckExpectedFunction {
actual: Type,
position: SourcePosition,
},
/// Failed to find a value with this key.
VariableIdentifierNotFound(Identifier),
/// Failed to find a type definition with this key.
TypeDefinitionNotFound(Identifier),
/// Failed to find an enum definition with this key.
ExpectedEnumDefintion {
actual: TypeDefinition,
},
/// Failed to find a struct definition with this key.
ExpectedStructDefintion {
actual: TypeDefinition,
},
}
impl ValidationError {
pub fn create_report(&self, source: &str) -> String {
let messages = match self {
ValidationError::CannotAdd {
left: _,
right: _,
position,
} => vec![
((
position.start_byte..position.end_byte,
format!(""),
(255, 159, 64),
)),
],
ValidationError::CannotSubtract {
left: _,
right: _,
position: _,
} => todo!(),
ValidationError::CannotMultiply {
left: _,
right: _,
position: _,
} => todo!(),
ValidationError::CannotDivide {
left: _,
right: _,
position: _,
} => todo!(),
ValidationError::ConversionImpossible {
initial_type: _,
target_type: _,
} => todo!(),
ValidationError::ExpectedString { actual: _ } => todo!(),
ValidationError::ExpectedInteger { actual: _ } => todo!(),
ValidationError::ExpectedFloat { actual: _ } => todo!(),
ValidationError::ExpectedNumber { actual: _ } => todo!(),
ValidationError::ExpectedNumberOrString { actual: _ } => todo!(),
ValidationError::ExpectedBoolean { actual: _ } => todo!(),
ValidationError::ExpectedList { actual: _ } => todo!(),
ValidationError::ExpectedMinLengthList {
minimum_len: _,
actual_len: _,
} => todo!(),
ValidationError::ExpectedFixedLenList {
expected_len: _,
actual: _,
} => todo!(),
ValidationError::ExpectedMap { actual: _ } => todo!(),
ValidationError::ExpectedFunction { actual: _ } => todo!(),
ValidationError::ExpectedCollection { actual: _ } => todo!(),
ValidationError::ExpectedBuiltInFunctionArgumentAmount {
function_name: _,
expected: _,
actual: _,
} => todo!(),
ValidationError::ExpectedFunctionArgumentAmount {
expected: _,
actual: _,
position: _,
} => todo!(),
ValidationError::ExpectedFunctionArgumentMinimum {
minumum_expected: _,
actual: _,
position: _,
} => todo!(),
ValidationError::RwLock(_) => todo!(),
ValidationError::TypeCheck {
expected,
actual,
position,
} => vec![(
position.start_byte..position.end_byte,
format!(
"Type {} is incompatible with {}.",
actual.to_string().bold().red(),
expected.to_string().bold().green()
),
(200, 200, 200),
)],
ValidationError::TypeCheckExpectedFunction {
actual: _,
position: _,
} => todo!(),
ValidationError::VariableIdentifierNotFound(_) => todo!(),
ValidationError::TypeDefinitionNotFound(_) => todo!(),
ValidationError::ExpectedEnumDefintion { actual: _ } => todo!(),
ValidationError::ExpectedStructDefintion { actual: _ } => todo!(),
};
Report::new_byte_spanned(source, messages).display_str()
}
pub fn expect_argument_amount(
function_name: &str,
expected: usize,
actual: usize,
) -> Result<(), Self> {
if expected == actual {
Ok(())
} else {
Err(ValidationError::ExpectedBuiltInFunctionArgumentAmount {
function_name: function_name.to_string(),
expected,
actual,
})
}
}
}
impl From<RwLockError> for ValidationError {
fn from(_error: RwLockError) -> Self {
ValidationError::RwLock(RwLockError)
}
}
impl Display for ValidationError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}

517
src/evaluator.rs Normal file
View File

@ -0,0 +1,517 @@
/// This trait is implemented by the Evaluator's internal types.
pub trait EvaluatorTree: 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(node: Node, source: &str) -> Result<Self>;
/// Execute dust code by traversing the tree
fn run(&self, context: &mut VariableMap) -> Result<Value>;
}
/// 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<'context, 'code> {
_parser: Parser,
context: &'context mut VariableMap,
source: &'code str,
tree: TSTree,
}
impl Debug for Evaluator<'_, '_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Evaluator context: {}", self.context)
}
}
impl<'context, 'code> Evaluator<'context, 'code> {
fn new(mut parser: Parser, context: &'context mut VariableMap, source: &'code str) -> Self {
let tree = parser.parse(source, None).unwrap();
Evaluator {
_parser: parser,
context,
source,
tree,
}
}
fn run(self) -> Vec<Result<Value>> {
let mut cursor = self.tree.walk();
let root_node = cursor.node();
let item_count = root_node.child_count();
let mut results = Vec::with_capacity(item_count);
println!("{}", root_node.to_sexp());
for item_node in root_node.children(&mut cursor) {
let item_result = Item::from_syntax_node(item_node, self.source);
match item_result {
Ok(item) => {
let eval_result = item.run(self.context);
results.push(eval_result);
}
Err(error) => results.push(Err(error)),
}
}
results
}
}
/// An abstractiton of an independent unit of source code.
///
/// Items are either comments, which do nothing, or statements, which can be run
/// to produce a single value or interact with a context by creating or
/// referencing variables.
#[derive(Debug)]
pub enum Item {
Comment(String),
Statement(Statement),
}
impl EvaluatorTree for Item {
fn from_syntax_node(node: Node, source: &str) -> Result<Self> {
debug_assert_eq!(node.kind(), "item");
let child = node.child(0).unwrap();
if child.kind() == "comment" {
let byte_range = child.byte_range();
let comment_text = &source[byte_range];
Ok(Item::Comment(comment_text.to_string()))
} else if child.kind() == "statement" {
Ok(Item::Statement(Statement::from_syntax_node(child, source)?))
} else {
Err(Error::UnexpectedSyntax {
expected: "comment or statement",
actual: child.kind(),
location: child.start_position(),
})
}
}
fn run(&self, context: &mut VariableMap) -> Result<Value> {
match self {
Item::Comment(text) => Ok(Value::String(text.clone())),
Item::Statement(statement) => statement.run(context),
}
}
}
/// Abstract representation of a statement.
///
/// Items are either comments, which do nothing, or statements, which can be run
/// to produce a single value or interact with a context by creating or
/// referencing variables.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum Statement {
Expression(Expression),
}
impl EvaluatorTree for Statement {
fn from_syntax_node(node: Node, source: &str) -> Result<Self> {
debug_assert_eq!(node.kind(), "statement");
let child = node.child(0).unwrap();
match child.kind() {
"expression" => Ok(Self::Expression(Expression::from_syntax_node(
child, source,
)?)),
_ => Err(Error::UnexpectedSyntax {
expected: "expression",
actual: child.kind(),
location: child.start_position(),
}),
}
}
fn run(&self, context: &mut VariableMap) -> Result<Value> {
match self {
Statement::Expression(expression) => expression.run(context),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum Expression {
Identifier(Identifier),
Value(Value),
ControlFlow(Box<ControlFlow>),
Assignment(Box<Assignment>),
Math(Box<Math>),
FunctionCall(FunctionCall),
}
impl EvaluatorTree for Expression {
fn from_syntax_node(node: Node, source: &str) -> Result<Self> {
debug_assert_eq!(node.kind(), "expression");
let child = node.child(0).unwrap();
let expression = match child.kind() {
"identifier" => Self::Identifier(Identifier::from_syntax_node(child, source)?),
"value" => Expression::Value(Value::from_syntax_node(child, source)?),
"control_flow" => {
Expression::ControlFlow(Box::new(ControlFlow::from_syntax_node(child, source)?))
}
"assignment" => {
Expression::Assignment(Box::new(Assignment::from_syntax_node(child, source)?))
}
"math" => Expression::Math(Box::new(Math::from_syntax_node(child, source)?)),
"function_call" => {
Expression::FunctionCall(FunctionCall::from_syntax_node(child, source)?)
}
_ => return Err(Error::UnexpectedSyntax {
expected:
"identifier, operation, control_flow, assignment, math, function_call or value",
actual: child.kind(),
location: child.start_position(),
}),
};
Ok(expression)
}
fn run(&self, context: &mut VariableMap) -> Result<Value> {
match self {
Expression::Value(value) => Ok(value.clone()),
Expression::Identifier(identifier) => identifier.run(context),
Expression::ControlFlow(control_flow) => control_flow.run(context),
Expression::Assignment(assignment) => assignment.run(context),
Expression::Math(math) => math.run(context),
Expression::FunctionCall(function_call) => function_call.run(context),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Identifier(String);
impl Identifier {
pub fn take_inner(self) -> String {
self.0
}
pub fn inner(&self) -> &String {
&self.0
}
}
impl EvaluatorTree for Identifier {
fn from_syntax_node(node: Node, source: &str) -> Result<Self> {
assert_eq!(node.kind(), "identifier");
let identifier = &source[node.byte_range()];
Ok(Identifier(identifier.to_string()))
}
fn run(&self, context: &mut VariableMap) -> Result<Value> {
let value = context.get_value(&self.0)?.unwrap_or_default();
Ok(value)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct ControlFlow {
if_expression: Expression,
then_statement: Statement,
else_statement: Option<Statement>,
}
impl EvaluatorTree for ControlFlow {
fn from_syntax_node(node: Node, source: &str) -> Result<Self> {
assert_eq!(node.kind(), "control_flow");
let if_node = node.child_by_field_name("if_expression").unwrap();
let if_expression = Expression::from_syntax_node(if_node, source)?;
let then_node = node.child_by_field_name("then_statement").unwrap();
let then_statement = Statement::from_syntax_node(then_node, source)?;
let else_node = node.child_by_field_name("else_statement");
let else_statement = if let Some(node) = else_node {
Some(Statement::from_syntax_node(node, source)?)
} else {
None
};
Ok(ControlFlow {
if_expression,
then_statement,
else_statement,
})
}
fn run(&self, context: &mut VariableMap) -> Result<Value> {
let if_boolean = self.if_expression.run(context)?.as_boolean()?;
if if_boolean {
self.then_statement.run(context)
} else if let Some(statement) = &self.else_statement {
statement.run(context)
} else {
Ok(Value::Empty)
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Assignment {
identifier: Identifier,
statement: Statement,
}
impl EvaluatorTree for Assignment {
fn from_syntax_node(node: Node, source: &str) -> Result<Self> {
assert_eq!(node.kind(), "assignment");
let identifier_node = node.child(0).unwrap();
let identifier = Identifier::from_syntax_node(identifier_node, source)?;
let statement_node = node.child(2).unwrap();
let statement = Statement::from_syntax_node(statement_node, source)?;
Ok(Assignment {
identifier,
statement,
})
}
fn run(&self, context: &mut VariableMap) -> Result<Value> {
let key = self.identifier.clone().take_inner();
let value = self.statement.run(context)?;
context.set_value(key, value)?;
Ok(Value::Empty)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Math {
left: Expression,
operator: MathOperator,
right: Expression,
}
impl EvaluatorTree for Math {
fn from_syntax_node(node: Node, source: &str) -> Result<Self> {
assert_eq!(node.kind(), "math");
let left_node = node.child(0).unwrap();
let left = Expression::from_syntax_node(left_node, source)?;
let operator_node = left_node.next_sibling().unwrap();
let operator = match operator_node.kind() {
"+" => MathOperator::Add,
"-" => MathOperator::Subtract,
"*" => MathOperator::Multiply,
"/" => MathOperator::Divide,
"%" => MathOperator::Modulo,
_ => {
return Err(Error::UnexpectedSyntax {
expected: "+, -, *, / or %",
actual: operator_node.kind(),
location: operator_node.start_position(),
})
}
};
let right_node = operator_node.next_sibling().unwrap();
let right = Expression::from_syntax_node(right_node, source)?;
Ok(Math {
left,
operator,
right,
})
}
fn run(&self, context: &mut VariableMap) -> Result<Value> {
let left_value = self.left.run(context)?.as_number()?;
let right_value = self.right.run(context)?.as_number()?;
let outcome = match self.operator {
MathOperator::Add => left_value + right_value,
MathOperator::Subtract => left_value - right_value,
MathOperator::Multiply => left_value * right_value,
MathOperator::Divide => left_value / right_value,
MathOperator::Modulo => left_value % right_value,
};
Ok(Value::Float(outcome))
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum MathOperator {
Add,
Subtract,
Multiply,
Divide,
Modulo,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct FunctionCall {
identifier: Identifier,
expressions: Vec<Expression>,
}
impl EvaluatorTree for FunctionCall {
fn from_syntax_node(node: Node, source: &str) -> Result<Self> {
assert_eq!(node.kind(), "function_call");
let identifier_node = node.child(0).unwrap();
let identifier = Identifier::from_syntax_node(identifier_node, source)?;
let mut expressions = Vec::new();
todo!();
Ok(FunctionCall {
identifier,
expressions,
})
}
fn run(&self, context: &mut VariableMap) -> Result<Value> {
let mut arguments = Vec::with_capacity(self.expressions.len());
for expression in &self.expressions {
let value = expression.run(context)?;
arguments.push(value);
}
context.call_function(self.identifier.inner(), &Value::List(arguments))
}
}
#[cfg(test)]
mod tests {
use crate::Table;
use super::*;
#[test]
fn evaluate_empty() {
assert_eq!(eval("x = 9"), vec![Ok(Value::Empty)]);
assert_eq!(eval("x = 'foo' + 'bar'"), vec![Ok(Value::Empty)]);
}
#[test]
fn evaluate_integer() {
assert_eq!(eval("1"), vec![Ok(Value::Integer(1))]);
assert_eq!(eval("123"), vec![Ok(Value::Integer(123))]);
assert_eq!(eval("-666"), vec![Ok(Value::Integer(-666))]);
}
#[test]
fn evaluate_float() {
assert_eq!(eval("0.1"), vec![Ok(Value::Float(0.1))]);
assert_eq!(eval("12.3"), vec![Ok(Value::Float(12.3))]);
assert_eq!(eval("-6.66"), vec![Ok(Value::Float(-6.66))]);
}
#[test]
fn evaluate_string() {
assert_eq!(eval("\"one\""), vec![Ok(Value::String("one".to_string()))]);
assert_eq!(eval("'one'"), vec![Ok(Value::String("one".to_string()))]);
assert_eq!(eval("`one`"), vec![Ok(Value::String("one".to_string()))]);
assert_eq!(
eval("`'one'`"),
vec![Ok(Value::String("'one'".to_string()))]
);
assert_eq!(
eval("'`one`'"),
vec![Ok(Value::String("`one`".to_string()))]
);
assert_eq!(
eval("\"'one'\""),
vec![Ok(Value::String("'one'".to_string()))]
);
}
#[test]
fn evaluate_list() {
assert_eq!(
eval("[1, 2, 'foobar']"),
vec![Ok(Value::List(vec![
Value::Integer(1),
Value::Integer(2),
Value::String("foobar".to_string()),
]))]
);
}
#[test]
fn evaluate_map() {
let mut map = VariableMap::new();
map.set_value("x".to_string(), Value::Integer(1)).unwrap();
map.set_value("foo".to_string(), Value::String("bar".to_string()))
.unwrap();
assert_eq!(eval("{ x = 1 foo = 'bar' }"), vec![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!(
eval(
"
table <messages, numbers> {
['hiya', 42]
['foo', 57]
['bar', 99.99]
}
"
),
vec![Ok(Value::Table(table))]
);
}
#[test]
fn if_then() {
assert_eq!(
eval("if true then 'true'"),
vec![Ok(Value::String("true".to_string()))]
);
}
#[test]
fn if_then_else() {
assert_eq!(eval("if false then 1 else 2"), vec![Ok(Value::Integer(2))]);
assert_eq!(
eval("if true then 1.0 else 42.0"),
vec![Ok(Value::Float(1.0))]
);
}
}

View File

@ -1,157 +0,0 @@
//! Tools to interpret dust source code.
//!
//! This module has three tools to run Dust code.
//!
//! - [interpret] is the simplest way to run Dust code inside of an application or library
//! - [interpret_with_context] allows you to set variables on the execution context
//! - [Interpreter] is an advanced tool that can parse, validate, run and format Dust code
//!
//! # Examples
//!
//! Run some Dust and get the result.
//!
//! ```rust
//! # use dust_lang::*;
//! assert_eq!(
//! interpret("1 + 2 + 3"),
//! Ok(Value::Integer(6))
//! );
//! ```
//!
//! Create a custom context with variables you can use in your code.
//!
//! ```rust
//! # use dust_lang::*;
//! let context = Context::default();
//!
//! context.set_value("one".into(), 1.into()).unwrap();
//! context.set_value("two".into(), 2.into()).unwrap();
//! context.set_value("three".into(), 3.into()).unwrap();
//!
//! let dust_code = "four = 4; one + two + three + four";
//!
//! assert_eq!(
//! interpret_with_context(dust_code, context),
//! Ok(Value::Integer(10))
//! );
//! ```
use tree_sitter::{Parser, Tree as SyntaxTree};
use crate::{language, AbstractTree, Context, ContextMode, Error, Format, Root, Value};
/// Interpret the given source code. Returns the value of last statement or the
/// first error encountered.
///
/// See the [module-level docs][self] for more info.
pub fn interpret(source: &str) -> Result<Value, Error> {
interpret_with_context(source, Context::new(ContextMode::RemoveGarbage))
}
/// Interpret the given source code with the given context.
///
/// See the [module-level docs][self] for more info.
pub fn interpret_with_context(source: &str, context: Context) -> Result<Value, Error> {
let mut interpreter = Interpreter::new(context);
let value = interpreter.run(source)?;
Ok(value)
}
/// A source code interpreter for the Dust language.
///
/// The interpreter's most important functions are used to parse dust source
/// code, verify it is safe and run it. They are written in a way that forces
/// them to be used safely: each step in this process contains the prior
/// steps, meaning that the same code is always used to create the syntax tree,
/// abstract tree and final evaluation. This avoids a critical logic error.
///
/// ```
/// # use dust_lang::*;
/// let context = Context::default();
/// let mut interpreter = Interpreter::new(context);
/// let result = interpreter.run("2 + 2");
///
/// assert_eq!(result, Ok(Value::Integer(4)));
/// ```
pub struct Interpreter {
parser: Parser,
context: Context,
}
impl Interpreter {
/// Create a new interpreter with the given context.
pub fn new(context: Context) -> Self {
let mut parser = Parser::new();
parser
.set_language(language())
.expect("Language version is incompatible with tree sitter version.");
parser.set_logger(Some(Box::new(|_log_type, message| {
log::trace!("{}", message)
})));
Interpreter { parser, context }
}
/// Generate a syntax tree from the source. Returns an error if the the
/// parser is cancelled for taking too long. The syntax tree may contain
/// error nodes, which represent syntax errors.
///
/// Tree sitter is designed to be run on every keystroke, so this is
/// generally a lightweight function to call.
pub fn parse(&mut self, source: &str) -> Result<SyntaxTree, Error> {
if let Some(tree) = self.parser.parse(source, None) {
Ok(tree)
} else {
Err(Error::ParserCancelled)
}
}
/// Check the source for errors and generate an abstract tree.
///
/// The order in which this function works is:
///
/// - parse the source into a syntax tree
/// - generate an abstract tree from the source and syntax tree
/// - check the abstract tree for errors
pub fn validate(&mut self, source: &str) -> Result<Root, Error> {
let syntax_tree = self.parse(source)?;
let abstract_tree = Root::from_syntax(syntax_tree.root_node(), source, &self.context)?;
abstract_tree.validate(source, &self.context)?;
Ok(abstract_tree)
}
/// Run the source, returning the final statement's value or first error.
///
/// This function [parses][Self::parse], [validates][Self::validate] and
/// [runs][Root::run] using the same source code.
pub fn run(&mut self, source: &str) -> Result<Value, Error> {
let final_value = self.validate(source)?.run(source, &self.context)?;
Ok(final_value)
}
/// Return an s-expression displaying a syntax tree of the source or an
/// error.
pub fn syntax_tree(&mut self, source: &str) -> Result<String, Error> {
Ok(self.parse(source)?.root_node().to_sexp())
}
/// Return a formatted version of the source.
pub fn format(&mut self, source: &str) -> Result<String, Error> {
let mut formatted_output = String::new();
self.validate(source)?.format(&mut formatted_output, 0);
Ok(formatted_output)
}
}
impl Default for Interpreter {
fn default() -> Self {
Interpreter::new(Context::default())
}
}

View File

@ -1,26 +1,22 @@
//! The Dust library is used to parse, format and run dust source code.
//! The Dust library is used to implement the Dust language, `src/main.rs` implements the command
//! line binary.
//!
//! See the [interpret] module for more information.
//!
//! You can use this library externally by calling either of the "interpret"
//! functions or by constructing your own Interpreter.
//! 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::*, built_in_functions::BuiltInFunction, context::*, error::Error, interpret::*,
value::*,
error::*,
interface::*,
value::{
function::Function, table::Table, time::Time, value_type::ValueType,
variable_map::VariableMap, Value,
},
};
pub use tree_sitter::Node as SyntaxNode;
pub mod abstract_tree;
pub mod built_in_functions;
pub mod built_in_identifiers;
pub mod built_in_type_definitions;
pub mod built_in_types;
pub mod built_in_values;
pub mod context;
pub mod error;
pub mod interpret;
pub mod value;
mod error;
mod evaluator;
mod interface;
mod value;
use tree_sitter::Language;
@ -35,10 +31,61 @@ 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: &'static str = include_str!("../../../src/node-types.json");
/// Evaluate the given source code.
///
/// Returns a vector of results from evaluating the source code. Each comment
/// and statemtent will have its own result.
///
/// ```rust
/// # use dust_lib::*;
/// assert_eq!(eval("1 + 2 + 3"), vec![Ok(Value::from(6))]);
/// ```
pub fn eval(source: &str) -> Vec<Result<Value>> {
let mut context = VariableMap::new();
eval_with_context(source, &mut context)
}
/// Evaluate the given source code with the given context.
///
/// ```rust
/// # use dust_lib::*;
/// let mut context = VariableMap::new();
///
/// context.set_value("one".into(), 1.into());
/// context.set_value("two".into(), 2.into());
/// context.set_value("three".into(), 3.into());
///
/// let dust_code = "one + two + three";
///
/// assert_eq!(
/// eval_with_context(dust_code, &mut context),
/// vec![Ok(Value::Empty), Ok(Value::from(6))]
/// );
/// ```
pub fn eval_with_context(source: &str, context: &mut VariableMap) -> Vec<Result<Value>> {
let mut parser = Parser::new();
parser.set_language(language()).unwrap();
Evaluator::new(parser, context, source).run()
}
// 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() {
fn load_grammar() {
let mut parser = tree_sitter::Parser::new();
parser
.set_language(super::language())

View File

@ -1,407 +1,166 @@
//! Command line interface for the dust programming language.
use clap::{Parser, Subcommand};
use colored::Colorize;
use crossterm::event::{KeyCode, KeyModifiers};
use nu_ansi_term::{Color, Style};
use reedline::{
default_emacs_keybindings, ColumnarMenu, Completer, DefaultHinter, EditCommand, Emacs, Prompt,
Reedline, ReedlineEvent, ReedlineMenu, Signal, Span, SqliteBackedHistory, Suggestion,
//! Command line interface for the whale programming language.
use clap::Parser;
use rustyline::{
completion::FilenameCompleter,
error::ReadlineError,
highlight::Highlighter,
hint::{Hint, Hinter, HistoryHinter},
history::DefaultHistory,
Completer, Context, Editor, Helper, Validator,
};
use std::{borrow::Cow, fs::read_to_string, io::Write, path::PathBuf, process::Command};
use std::{borrow::Cow, fs::read_to_string};
use dust_lang::{
built_in_values::all_built_in_values, Context, ContextMode, Error, Interpreter, Value,
ValueData,
};
use dust_lib::{eval, eval_with_context, Value, VariableMap};
/// Command-line arguments to be parsed.
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Dust source code to evaluate.
/// Whale source code to evaluate.
#[arg(short, long)]
command: Option<String>,
/// Command for alternate functionality besides running the source.
#[command(subcommand)]
cli_command: Option<CliCommand>,
/// Location of the file to run.
path: Option<String>,
}
#[derive(Subcommand, Debug)]
pub enum CliCommand {
/// Output a formatted version of the input.
Format,
/// Output a concrete syntax tree of the input.
Syntax { path: String },
}
fn main() {
env_logger::Builder::from_env("DUST_LOG")
.format(|buffer, record| {
let args = record.args();
let log_level = record.level().to_string().bold();
let timestamp = buffer.timestamp_seconds().to_string().dimmed();
writeln!(buffer, "[{log_level} {timestamp}] {args}")
})
.init();
let args = Args::parse();
let context = Context::new(ContextMode::AllowGarbage);
if args.path.is_none() && args.command.is_none() {
let run_shell_result = run_shell(context);
match run_shell_result {
Ok(_) => {}
Err(error) => eprintln!("{error}"),
}
return;
return run_cli_shell();
}
let source = if let Some(path) = &args.path {
read_to_string(path).unwrap()
let eval_results = if let Some(path) = args.path {
let file_contents = read_to_string(path).unwrap();
eval(&file_contents)
} else if let Some(command) = args.command {
command
eval(&command)
} else {
String::with_capacity(0)
vec![Ok(Value::Empty)]
};
let mut interpreter = Interpreter::new(context);
if let Some(CliCommand::Syntax { path }) = args.cli_command {
let source = read_to_string(path).unwrap();
let syntax_tree_sexp = interpreter.syntax_tree(&source).unwrap();
println!("{syntax_tree_sexp}");
return;
}
if let Some(CliCommand::Format) = args.cli_command {
let formatted = interpreter.format(&source).unwrap();
println!("{formatted}");
return;
}
let eval_result = interpreter.run(&source);
match eval_result {
Ok(value) => {
if !value.is_none() {
println!("{value}")
}
for result in eval_results {
match result {
Ok(value) => println!("{value}"),
Err(error) => eprintln!("{error}"),
}
Err(error) => eprintln!("{}", error.create_report(&source)),
}
}
// struct DustHighlighter {
// context: Context,
// }
#[derive(Helper, Completer, Validator)]
struct DustReadline {
#[rustyline(Completer)]
completer: FilenameCompleter,
// impl DustHighlighter {
// fn new(context: Context) -> Self {
// Self { context }
// }
// }
tool_hints: Vec<ToolHint>,
// const HIGHLIGHT_TERMINATORS: [char; 8] = [' ', ':', '(', ')', '{', '}', '[', ']'];
// impl Highlighter for DustHighlighter {
// fn highlight(&self, line: &str, _cursor: usize) -> reedline::StyledText {
// let mut styled = StyledText::new();
// for word in line.split_inclusive(&HIGHLIGHT_TERMINATORS) {
// let mut word_is_highlighted = false;
// for key in self.context.inner().unwrap().keys() {
// if key == &word {
// styled.push((Style::new().bold(), word.to_string()));
// }
// word_is_highlighted = true;
// }
// for built_in_value in built_in_values() {
// if built_in_value.name() == word {
// styled.push((Style::new().bold(), word.to_string()));
// }
// word_is_highlighted = true;
// }
// if word_is_highlighted {
// let final_char = word.chars().last().unwrap();
// if HIGHLIGHT_TERMINATORS.contains(&final_char) {
// let mut terminator_style = Style::new();
// terminator_style.foreground = Some(Color::Cyan);
// styled.push((terminator_style, final_char.to_string()));
// }
// } else {
// styled.push((Style::new(), word.to_string()));
// }
// }
// styled
// }
// }
struct StarshipPrompt {
left: String,
right: String,
#[rustyline(Hinter)]
_hinter: HistoryHinter,
}
impl StarshipPrompt {
impl DustReadline {
fn new() -> Self {
Self {
left: String::new(),
right: String::new(),
completer: FilenameCompleter::new(),
_hinter: HistoryHinter {},
tool_hints: Vec::new(),
}
}
}
fn reload(&mut self) {
let run_starship_left = Command::new("starship").arg("prompt").output();
let run_starship_right = Command::new("starship")
.args(["prompt", "--right"])
.output();
let left_prompt = if let Ok(output) = &run_starship_left {
String::from_utf8_lossy(&output.stdout).trim().to_string()
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 {
">".to_string()
};
let right_prompt = if let Ok(output) = &run_starship_right {
String::from_utf8_lossy(&output.stdout).trim().to_string()
} else {
"".to_string()
};
self.left = left_prompt;
self.right = right_prompt;
None
}
}
}
impl Prompt for StarshipPrompt {
fn render_prompt_left(&self) -> Cow<str> {
Cow::Borrowed(&self.left)
}
fn render_prompt_right(&self) -> Cow<str> {
Cow::Borrowed(&self.right)
}
fn render_prompt_indicator(&self, _prompt_mode: reedline::PromptEditMode) -> Cow<str> {
Cow::Borrowed(" ")
}
fn render_prompt_multiline_indicator(&self) -> Cow<str> {
Cow::Borrowed("")
}
fn render_prompt_history_search_indicator(
&self,
_history_search: reedline::PromptHistorySearch,
) -> Cow<str> {
Cow::Borrowed("")
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),
}
}
}
pub struct DustCompleter {
context: Context,
}
impl Hinter for DustReadline {
type Hint = ToolHint;
impl DustCompleter {
fn new(context: Context) -> Self {
DustCompleter { context }
}
}
impl Completer for DustCompleter {
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
let mut suggestions = Vec::new();
let last_word = if let Some(word) = line.rsplit([' ', ':']).next() {
word
} else {
line
};
if let Ok(path) = PathBuf::try_from(last_word) {
if let Ok(read_dir) = path.read_dir() {
for entry in read_dir {
if let Ok(entry) = entry {
let description = if let Ok(file_type) = entry.file_type() {
if file_type.is_dir() {
"directory"
} else if file_type.is_file() {
"file"
} else if file_type.is_symlink() {
"symlink"
} else {
"unknown"
}
} else {
"unknown"
};
suggestions.push(Suggestion {
value: entry.path().to_string_lossy().to_string(),
description: Some(description.to_string()),
extra: None,
span: Span::new(pos - last_word.len(), pos),
append_whitespace: false,
});
}
}
}
fn hint(&self, line: &str, pos: usize, _ctx: &Context<'_>) -> Option<Self::Hint> {
if line.is_empty() || pos < line.len() {
return None;
}
for built_in_value in all_built_in_values() {
let name = built_in_value.name();
let description = built_in_value.description();
if built_in_value.name().contains(last_word) {
suggestions.push(Suggestion {
value: name.to_string(),
description: Some(description.to_string()),
extra: None,
span: Span::new(pos - last_word.len(), pos),
append_whitespace: false,
});
self.tool_hints.iter().find_map(|tool_hint| {
if tool_hint.display.starts_with(line) {
Some(tool_hint.suffix(pos))
} else {
None
}
if let Value::Map(map) = built_in_value.get() {
for (key, value) in map.inner() {
if key.contains(last_word) {
suggestions.push(Suggestion {
value: format!("{name}:{key}"),
description: Some(value.to_string()),
extra: None,
span: Span::new(pos - last_word.len(), pos),
append_whitespace: false,
});
}
}
}
}
for (key, (value_data, _counter)) in self.context.inner().unwrap().iter() {
let value = match value_data {
ValueData::Value(value) => value,
ValueData::TypeHint(_) => continue,
ValueData::TypeDefinition(_) => continue,
};
if key.contains(last_word) {
suggestions.push(Suggestion {
value: key.to_string(),
description: Some(value.to_string()),
extra: None,
span: Span::new(pos - last_word.len(), pos),
append_whitespace: false,
});
}
}
suggestions
})
}
}
fn run_shell(context: Context) -> Result<(), Error> {
let mut interpreter = Interpreter::new(context.clone());
let mut keybindings = default_emacs_keybindings();
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();
keybindings.add_binding(
KeyModifiers::CONTROL,
KeyCode::Char(' '),
ReedlineEvent::Edit(vec![EditCommand::InsertNewline]),
);
keybindings.add_binding(
KeyModifiers::NONE,
KeyCode::Enter,
ReedlineEvent::SubmitOrNewline,
);
keybindings.add_binding(
KeyModifiers::NONE,
KeyCode::Tab,
ReedlineEvent::Edit(vec![EditCommand::InsertString(" ".to_string())]),
);
keybindings.add_binding(
KeyModifiers::NONE,
KeyCode::Tab,
ReedlineEvent::Multiple(vec![
ReedlineEvent::Menu("context menu".to_string()),
ReedlineEvent::MenuNext,
]),
);
Cow::Owned(highlighted)
}
}
let edit_mode = Box::new(Emacs::new(keybindings));
let history = Box::new(
SqliteBackedHistory::with_file(PathBuf::from("target/history"), None, None)
.expect("Error loading history."),
);
let hinter = Box::new(DefaultHinter::default().with_style(Style::new().dimmed()));
let completer = DustCompleter::new(context.clone());
fn run_cli_shell() {
let mut context = VariableMap::new();
let mut rl: Editor<DustReadline, DefaultHistory> = Editor::new().unwrap();
let mut line_editor = Reedline::create()
.with_edit_mode(edit_mode)
.with_history(history)
.with_hinter(hinter)
.use_kitty_keyboard_enhancement(true)
.with_completer(Box::new(completer))
.with_menu(ReedlineMenu::EngineCompleter(Box::new(
ColumnarMenu::default()
.with_name("context menu")
.with_text_style(Style::new().fg(Color::White))
.with_columns(1)
.with_column_padding(10),
)));
let mut prompt = StarshipPrompt::new();
rl.set_helper(Some(DustReadline::new()));
prompt.reload();
if rl.load_history("target/history.txt").is_err() {
println!("No previous history.");
}
loop {
let sig = line_editor.read_line(&prompt);
let readline = rl.readline("* ");
match readline {
Ok(line) => {
let line = line.as_str();
match sig {
Ok(Signal::Success(buffer)) => {
if buffer.trim().is_empty() {
continue;
}
rl.add_history_entry(line).unwrap();
let run_result = interpreter.run(&buffer);
let eval_results = eval_with_context(line, &mut context);
match run_result {
Ok(value) => {
if !value.is_none() {
println!("{value}")
}
for result in eval_results {
match result {
Ok(value) => println!("{value}"),
Err(error) => eprintln!("{error}"),
}
Err(error) => println!("{error}"),
}
prompt.reload();
}
Ok(Signal::CtrlD) | Ok(Signal::CtrlC) => {
println!("\nLeaving the Dust shell.");
Err(ReadlineError::Interrupted) => {
break;
}
x => {
println!("Unknown event: {:?}", x);
Err(ReadlineError::Eof) => {
break;
}
Err(error) => eprintln!("{error}"),
}
}
Ok(())
rl.save_history("target/history.txt").unwrap();
}

416
src/tools/collections.rs Normal file
View File

@ -0,0 +1,416 @@
//! Macros for collection values: strings, lists, maps and tables.
//!
//! Tests for this module are written in Dust and can be found at tests/collections.ds.
use crate::{Error, Result, Table, Tool, ToolInfo, Value, ValueType, VariableMap};
pub struct Sort;
impl Tool for Sort {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "sort",
description: "Apply default ordering to a list or table.",
group: "collections",
inputs: vec![ValueType::List, ValueType::Table],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
if let Ok(mut list) = argument.as_list().cloned() {
list.sort();
Ok(Value::List(list))
} else if let Ok(mut table) = argument.as_table().cloned() {
table.sort();
Ok(Value::Table(table))
} else {
Err(crate::Error::ExpectedList {
actual: argument.clone(),
})
}
}
}
pub struct Transform;
impl Tool for Transform {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "transform",
description: "Alter a list by calling a function on each value.",
group: "collections",
inputs: vec![ValueType::ListExact(vec![
ValueType::List,
ValueType::Function,
])],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = self.check_type(argument)?.as_list()?;
let list = argument[0].as_list()?;
let function = argument[1].as_function()?;
let mut mapped_list = Vec::with_capacity(list.len());
for value in list {
let mut context = VariableMap::new();
context.set_value("input".to_string(), value.clone())?;
let mapped_value = function.run_with_context(&mut context)?;
mapped_list.push(mapped_value);
}
Ok(Value::List(mapped_list))
}
}
pub struct String;
impl Tool for String {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "string",
description: "Stringify a value.",
group: "collections",
inputs: vec![
ValueType::String,
ValueType::Function,
ValueType::Float,
ValueType::Integer,
ValueType::Boolean,
],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = self.check_type(argument)?;
let string = match argument {
Value::String(string) => string.clone(),
Value::Function(function) => function.to_string(),
Value::Float(float) => float.to_string(),
Value::Integer(integer) => integer.to_string(),
Value::Boolean(boolean) => boolean.to_string(),
_ => return self.fail(argument),
};
Ok(Value::String(string))
}
}
pub struct Replace;
impl Tool for Replace {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "replace",
description: "Replace all occurences of a substring in a string.",
group: "collections",
inputs: vec![ValueType::ListExact(vec![
ValueType::String,
ValueType::String,
ValueType::String,
])],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = self.check_type(argument)?.as_list()?;
let target = argument[0].as_string()?;
let to_remove = argument[1].as_string()?;
let replacement = argument[2].as_string()?;
let result = target.replace(to_remove, replacement);
Ok(Value::String(result))
}
}
pub struct Count;
impl Tool for Count {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "count",
description: "Return the number of items in a collection.",
group: "collections",
inputs: vec![
ValueType::String,
ValueType::List,
ValueType::Map,
ValueType::Table,
],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = self.check_type(argument)?;
let len = match argument {
Value::String(string) => string.chars().count(),
Value::List(list) => list.len(),
Value::Map(map) => map.len(),
Value::Table(table) => table.len(),
Value::Function(_)
| Value::Float(_)
| Value::Integer(_)
| Value::Boolean(_)
| Value::Time(_)
| Value::Empty => return self.fail(argument),
};
Ok(Value::Integer(len as i64))
}
}
pub struct CreateTable;
impl Tool for CreateTable {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "create_table",
description: "Define a new table with a list of column names and list of rows.",
group: "collections",
inputs: vec![ValueType::ListExact(vec![
ValueType::ListOf(Box::new(ValueType::String)),
ValueType::ListOf(Box::new(ValueType::List)),
])],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = self.check_type(argument)?.as_list()?;
let column_name_inputs = argument[0].as_list()?;
let mut column_names = Vec::with_capacity(column_name_inputs.len());
for name in column_name_inputs {
column_names.push(name.as_string()?.clone());
}
let column_count = column_names.len();
let rows = argument[1].as_list()?;
let mut table = Table::new(column_names);
for row in rows {
let row = row.as_fixed_len_list(column_count)?;
table.insert(row.clone()).unwrap();
}
Ok(Value::Table(table))
}
}
pub struct Rows;
impl Tool for Rows {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "rows",
description: "Extract a table's rows as a list.",
group: "collections",
inputs: vec![ValueType::Table],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = self.check_type(argument)?;
if let Value::Table(table) = argument {
let rows = table
.rows()
.iter()
.map(|row| Value::List(row.clone()))
.collect();
Ok(Value::List(rows))
} else {
self.fail(argument)
}
}
}
pub struct Insert;
impl Tool for Insert {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "insert",
description: "Add new rows to a table.",
group: "collections",
inputs: vec![ValueType::ListExact(vec![
ValueType::Table,
ValueType::ListOf(Box::new(ValueType::List)),
])],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_list()?;
let new_rows = argument[1].as_list()?;
let mut table = argument[0].as_table()?.clone();
table.reserve(new_rows.len());
for row in new_rows {
let row = row.as_list()?.clone();
table.insert(row)?;
}
Ok(Value::Table(table))
}
}
pub struct Select;
impl Tool for Select {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "select",
description: "Extract one or more values based on their key.",
group: "collections",
inputs: vec![
ValueType::ListExact(vec![ValueType::Table, ValueType::String]),
ValueType::ListExact(vec![
ValueType::Table,
ValueType::ListOf(Box::new(ValueType::String)),
]),
],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let arguments = argument.as_fixed_len_list(2)?;
let collection = &arguments[0];
if let Value::List(list) = collection {
let mut selected = Vec::new();
let index = arguments[1].as_int()?;
let value = list.get(index as usize);
if let Some(value) = value {
selected.push(value.clone());
return Ok(Value::List(selected));
} else {
return Ok(Value::List(selected));
}
}
let mut column_names = Vec::new();
if let Value::List(columns) = &arguments[1] {
for column in columns {
let name = column.as_string()?;
column_names.push(name.clone());
}
} else if let Value::String(column) = &arguments[1] {
column_names.push(column.clone());
} else {
todo!()
};
if let Value::Map(map) = collection {
let mut selected = VariableMap::new();
for (key, value) in map.inner() {
if column_names.contains(key) {
selected.set_value(key.to_string(), value.clone())?;
}
}
return Ok(Value::Map(selected));
}
if let Value::Table(table) = collection {
let selected = table.select(&column_names);
return Ok(Value::Table(selected));
}
todo!()
}
}
pub struct ForEach;
impl Tool for ForEach {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "for_each",
description: "Run an operation on every item in a collection.",
group: "collections",
inputs: vec![],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_list()?;
Error::expected_minimum_function_argument_amount(
self.info().identifier,
2,
argument.len(),
)?;
let table = argument[0].as_table()?;
let columns = argument[1].as_list()?;
let mut column_names = Vec::new();
for column in columns {
let name = column.as_string()?;
column_names.push(name.clone());
}
let selected = table.select(&column_names);
Ok(Value::Table(selected))
}
}
pub struct Where;
impl Tool for Where {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "where",
description: "Keep rows matching a predicate.",
group: "collections",
inputs: vec![ValueType::ListExact(vec![
ValueType::Table,
ValueType::Function,
])],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = self.check_type(argument)?.as_list()?;
let table = &argument[0].as_table()?;
let function = argument[1].as_function()?;
let mut context = VariableMap::new();
let mut new_table = Table::new(table.column_names().clone());
for row in table.rows() {
for (column_index, cell) in row.iter().enumerate() {
let column_name = table.column_names().get(column_index).unwrap();
context.set_value(column_name.to_string(), cell.clone())?;
}
let keep_row = function.run_with_context(&mut context)?.as_boolean()?;
if keep_row {
new_table.insert(row.clone())?;
}
}
Ok(Value::Table(new_table))
}
}

119
src/tools/command.rs Normal file
View File

@ -0,0 +1,119 @@
use std::process::Command;
use crate::{Result, Tool, ToolInfo, Value, ValueType};
pub struct Sh;
impl Tool for Sh {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "sh",
description: "Pass input to the Bourne Shell.",
group: "command",
inputs: vec![ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_string()?;
Command::new("sh").arg("-c").arg(argument).spawn()?.wait()?;
Ok(Value::Empty)
}
}
pub struct Bash;
impl Tool for Bash {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "bash",
description: "Pass input to the Bourne Again Shell.",
group: "command",
inputs: vec![ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_string()?;
Command::new("bash")
.arg("-c")
.arg(argument)
.spawn()?
.wait()?;
Ok(Value::Empty)
}
}
pub struct Fish;
impl Tool for Fish {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "fish",
description: "Pass input to the fish shell.",
group: "command",
inputs: vec![ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_string()?;
Command::new("fish")
.arg("-c")
.arg(argument)
.spawn()?
.wait()?;
Ok(Value::Empty)
}
}
pub struct Zsh;
impl Tool for Zsh {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "zsh",
description: "Pass input to the Z shell.",
group: "command",
inputs: vec![ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_string()?;
Command::new("zsh")
.arg("-c")
.arg(argument)
.spawn()?
.wait()?;
Ok(Value::Empty)
}
}
pub struct Raw;
impl Tool for Raw {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "raw",
description: "Run input as a command without a shell",
group: "command",
inputs: vec![ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_string()?;
Command::new(argument).spawn()?.wait()?;
Ok(Value::Empty)
}
}

171
src/tools/data_formats.rs Normal file
View File

@ -0,0 +1,171 @@
//! Convert values to and from data formats like JSON and TOML.
use crate::{Result, Table, Tool, ToolInfo, Value, ValueType};
pub struct FromToml;
impl Tool for FromToml {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "from_toml",
description: "Create a value from a TOML string.",
group: "data",
inputs: vec![ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_string()?;
let value = toml::from_str(&argument)?;
Ok(value)
}
}
pub struct FromJson;
impl Tool for FromJson {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "from_json",
description: "Get a whale value from a JSON string.",
group: "data",
inputs: vec![ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_string()?;
let value = serde_json::from_str(argument)?;
Ok(value)
}
}
pub struct ToJson;
impl Tool for ToJson {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "to_json",
description: "Create a JSON string from a whale value.",
group: "data",
inputs: vec![ValueType::Any],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let json = serde_json::to_string(argument)?;
Ok(Value::String(json))
}
}
pub struct FromCsv;
impl Tool for FromCsv {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "from_csv",
description: "Create a whale value from a CSV string.",
group: "data",
inputs: vec![ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let csv = argument.as_string()?;
let mut reader = csv::Reader::from_reader(csv.as_bytes());
let headers = reader
.headers()?
.iter()
.map(|header| header.trim().trim_matches('"').to_string())
.collect();
let mut table = Table::new(headers);
for result in reader.records() {
let row = result?
.iter()
.map(|column| {
let column = column.trim().trim_matches('"').trim_matches('\'');
if let Ok(integer) = column.parse::<i64>() {
Value::Integer(integer)
} else if let Ok(float) = column.parse::<f64>() {
Value::Float(float)
} else {
Value::String(column.to_string())
}
})
.collect();
table.insert(row)?;
}
Ok(Value::Table(table))
}
}
pub struct ToCsv;
impl Tool for ToCsv {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "to_csv",
description: "Convert a value to a string of comma-separated values.",
group: "data",
inputs: vec![ValueType::Any],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let mut buffer = Vec::new();
let mut writer = csv::Writer::from_writer(&mut buffer);
match argument {
Value::String(string) => {
writer.write_record([string])?;
}
Value::Float(float) => {
writer.write_record(&[float.to_string()])?;
}
Value::Integer(integer) => {
writer.write_record(&[integer.to_string()])?;
}
Value::Boolean(boolean) => {
writer.write_record(&[boolean.to_string()])?;
}
Value::List(list) => {
let string_list = list.iter().map(|value| value.to_string());
writer.write_record(string_list)?;
}
Value::Empty => {}
Value::Map(map) => {
writer.write_record(map.inner().keys())?;
writer.write_record(map.inner().values().map(|value| value.to_string()))?;
}
Value::Table(table) => {
writer.write_record(table.column_names())?;
for row in table.rows() {
let row_string = row.iter().map(|value| value.to_string());
writer.write_record(row_string)?;
}
}
Value::Function(_) => todo!(),
Value::Time(time) => {
writer.write_record(&[time.to_string()])?;
}
}
writer.flush()?;
Ok(Value::String(
String::from_utf8_lossy(writer.get_ref()).to_string(),
))
}
}

123
src/tools/disks.rs Normal file
View File

@ -0,0 +1,123 @@
use std::process::Command;
use sysinfo::{DiskExt, System, SystemExt};
use crate::{Result, Table, Tool, ToolInfo, Value, ValueType};
pub struct ListDisks;
impl Tool for ListDisks {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "list_disks",
description: "List all block devices.",
group: "disks",
inputs: vec![ValueType::Empty],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
argument.as_empty()?;
let mut sys = System::new_all();
sys.refresh_all();
let mut disk_table = Table::new(vec![
"name".to_string(),
"kind".to_string(),
"file system".to_string(),
"mount point".to_string(),
"total space".to_string(),
"available space".to_string(),
"is removable".to_string(),
]);
for disk in sys.disks() {
let name = disk.name().to_string_lossy().to_string();
let kind = disk.kind();
let file_system = String::from_utf8_lossy(disk.file_system()).to_string();
let mount_point = disk.mount_point().to_str().unwrap().to_string();
let total_space = disk.total_space() as i64;
let available_space = disk.available_space() as i64;
let is_removable = disk.is_removable();
let row = vec![
Value::String(name),
Value::String(format!("{kind:?}")),
Value::String(file_system),
Value::String(mount_point),
Value::Integer(total_space),
Value::Integer(available_space),
Value::Boolean(is_removable),
];
disk_table.insert(row)?;
}
Ok(Value::Table(disk_table))
}
}
pub struct Partition;
impl Tool for Partition {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "partition",
description: "Partition a disk, clearing its content.",
group: "disks",
inputs: vec![ValueType::Map],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_map()?;
let path = argument
.get_value("path")?
.unwrap_or(Value::Empty)
.as_string()?
.clone();
let label = argument
.get_value("label")?
.unwrap_or(Value::Empty)
.as_string()?
.clone();
let name = argument
.get_value("name")?
.unwrap_or(Value::Empty)
.as_string()?
.clone();
let filesystem = argument
.get_value("filesystem")?
.unwrap_or(Value::Empty)
.as_string()?
.clone();
let range = argument
.get_value("range")?
.unwrap_or(Value::Empty)
.as_list()?
.clone();
if range.len() != 2 {
return Err(crate::Error::ExpectedFixedLenList {
expected_len: 2,
actual: Value::List(range),
});
}
let range_start = range[0].as_string()?;
let range_end = range[1].as_string()?;
let script = format!(
"sudo parted {path} mklabel {label} mkpart {name} {filesystem} {range_start} {range_end}"
);
Command::new("fish")
.arg("-c")
.arg(&script)
.spawn()?
.wait()?;
Ok(Value::Empty)
}
}

487
src/tools/filesystem.rs Normal file
View File

@ -0,0 +1,487 @@
//! Dust commands for managing files and directories.
use std::{
fs::{self, OpenOptions},
io::{Read, Write as IoWrite},
path::PathBuf,
};
use crate::{Error, Result, Table, Time, Tool, ToolInfo, Value, ValueType};
pub struct Append;
impl Tool for Append {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "append",
description: "Append data to a file.",
group: "filesystem",
inputs: vec![ValueType::ListExact(vec![
ValueType::String,
ValueType::Any,
])],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let arguments = argument.as_fixed_len_list(2)?;
let path = arguments[0].as_string()?;
let content = arguments[1].as_string()?;
let mut file = OpenOptions::new().append(true).open(path)?;
file.write_all(content.as_bytes())?;
Ok(Value::Empty)
}
}
pub struct CreateDir;
impl Tool for CreateDir {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "create_dir",
description: "Create one or more directories.",
group: "filesystem",
inputs: vec![
ValueType::String,
ValueType::ListOf(Box::new(ValueType::String)),
],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let path = argument.as_string()?;
fs::create_dir_all(path)?;
Ok(Value::Empty)
}
}
pub struct FileMetadata;
impl Tool for FileMetadata {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "file_metadata",
description: "Get metadata for files.",
group: "filesystem",
inputs: vec![
ValueType::String,
ValueType::ListOf(Box::new(ValueType::String)),
],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let path_string = argument.as_string()?;
let metadata = PathBuf::from(path_string).metadata()?;
let created = metadata.accessed()?.elapsed()?.as_secs() / 60;
let accessed = metadata.accessed()?.elapsed()?.as_secs() / 60;
let modified = metadata.modified()?.elapsed()?.as_secs() / 60;
let read_only = metadata.permissions().readonly();
let size = metadata.len();
let mut file_table = Table::new(vec![
"path".to_string(),
"size".to_string(),
"created".to_string(),
"accessed".to_string(),
"modified".to_string(),
"read only".to_string(),
]);
file_table.insert(vec![
Value::String(path_string.clone()),
Value::Integer(size as i64),
Value::Integer(created as i64),
Value::Integer(accessed as i64),
Value::Integer(modified as i64),
Value::Boolean(read_only),
])?;
Ok(Value::Table(file_table))
}
}
pub struct ReadDir;
impl Tool for ReadDir {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "read_dir",
description: "Read the content of a directory.",
group: "filesystem",
inputs: vec![ValueType::String, ValueType::Empty],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let path = if let Ok(path) = argument.as_string() {
path
} else if argument.is_empty() {
"."
} else {
return Err(Error::TypeError {
expected: &[ValueType::Empty, ValueType::String],
actual: argument.clone(),
});
};
let dir = fs::read_dir(path)?;
let mut file_table = Table::new(vec![
"path".to_string(),
"size".to_string(),
"created".to_string(),
"accessed".to_string(),
"modified".to_string(),
"read only".to_string(),
]);
for entry in dir {
let entry = entry?;
let file_type = entry.file_type()?;
let file_name = if file_type.is_dir() {
let name = entry.file_name().into_string().unwrap_or_default();
format!("{name}/")
} else {
entry.file_name().into_string().unwrap_or_default()
};
let metadata = entry.path().metadata()?;
let created_timestamp = metadata.accessed()?;
let created = Time::from(created_timestamp);
let accessed_timestamp = metadata.accessed()?;
let accessed = Time::from(accessed_timestamp);
let modified_timestamp = metadata.modified()?;
let modified = Time::from(modified_timestamp);
let read_only = metadata.permissions().readonly();
let size = metadata.len();
file_table.insert(vec![
Value::String(file_name),
Value::Integer(size as i64),
Value::Time(created),
Value::Time(accessed),
Value::Time(modified),
Value::Boolean(read_only),
])?;
}
Ok(Value::Table(file_table))
}
}
pub struct ReadFile;
impl Tool for ReadFile {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "read_file",
description: "Read file contents.",
group: "filesystem",
inputs: vec![ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let path = argument.as_string()?;
let mut contents = String::new();
OpenOptions::new()
.read(true)
.create(false)
.open(path)?
.read_to_string(&mut contents)?;
Ok(Value::String(contents))
}
}
pub struct RemoveDir;
impl Tool for RemoveDir {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "remove_dir",
description: "Remove directories.",
group: "filesystem",
inputs: vec![
ValueType::String,
ValueType::ListOf(Box::new(ValueType::String)),
],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let path = argument.as_string()?;
fs::remove_dir(path)?;
Ok(Value::Empty)
}
}
pub struct MoveDir;
impl Tool for MoveDir {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "move_dir",
description: "Move a directory to a new path.",
group: "filesystem",
inputs: vec![ValueType::ListExact(vec![
ValueType::String,
ValueType::String,
])],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_list()?;
Error::expect_function_argument_amount(self.info().identifier, argument.len(), 2)?;
let current_path = argument[0].as_string()?;
let target_path = argument[1].as_string()?;
let file_list = ReadDir.run(&Value::String(current_path.clone()))?;
for path in file_list.as_list()? {
let path = PathBuf::from(path.as_string()?);
let new_path = PathBuf::from(&target_path).join(&path);
if path.is_file() {
fs::copy(&path, target_path)?;
}
if path.is_symlink() && path.symlink_metadata()?.is_file() {
fs::copy(&path, new_path)?;
}
}
Ok(Value::Empty)
}
}
pub struct Trash;
impl Tool for Trash {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "trash",
description: "Move a file or directory to the trash.",
group: "filesystem",
inputs: vec![
ValueType::String,
ValueType::ListOf(Box::new(ValueType::String)),
],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let path = argument.as_string()?;
trash::delete(path)?;
Ok(Value::Empty)
}
}
pub struct Write;
impl Tool for Write {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "write",
description: "Write data to a file.",
group: "filesystem",
inputs: vec![ValueType::ListExact(vec![
ValueType::String,
ValueType::Any,
])],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let strings = argument.as_list()?;
Error::expect_function_argument_amount(self.info().identifier, strings.len(), 2)?;
let path = strings.first().unwrap().as_string()?;
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(path)?;
for content in &strings[1..] {
let content = content.to_string();
file.write_all(content.as_bytes())?;
}
Ok(Value::Empty)
}
}
pub struct RemoveFile;
impl Tool for RemoveFile {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "remove_file",
description: "Permanently delete a file.",
group: "filesystem",
inputs: vec![
ValueType::String,
ValueType::ListOf(Box::new(ValueType::String)),
],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
if let Ok(path) = argument.as_string() {
fs::remove_file(path)?;
return Ok(Value::Empty);
}
if let Ok(path_list) = argument.as_list() {
for path in path_list {
let path = path.as_string()?;
fs::remove_file(path)?;
}
return Ok(Value::Empty);
}
Err(Error::expected_string(argument.clone()))
}
}
pub struct Watch;
impl Tool for Watch {
fn info(&self) -> crate::ToolInfo<'static> {
crate::ToolInfo {
identifier: "watch",
description: "Wait until a file changes.",
group: "filesystem",
inputs: vec![ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_string()?;
let path = PathBuf::from(argument);
let modified_old = path.metadata()?.modified()?;
let wait_time = loop {
let modified_new = path.metadata()?.modified()?;
if modified_old != modified_new {
break modified_new
.duration_since(modified_old)
.unwrap_or_default()
.as_millis() as i64;
}
};
Ok(Value::Integer(wait_time))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create_dir() {
let path = PathBuf::from("./target/create_dir/");
let path_value = Value::String(path.to_string_lossy().to_string());
let _ = std::fs::remove_file(&path);
CreateDir.run(&path_value).unwrap();
assert!(path.is_dir());
}
#[test]
fn create_dir_nested() {
let path = PathBuf::from("./target/create_dir/nested");
let path_value = Value::String(path.to_string_lossy().to_string());
let _ = std::fs::remove_file(&path);
CreateDir.run(&path_value).unwrap();
assert!(path.is_dir());
}
#[test]
fn write() {
let path = PathBuf::from("./target/write.txt");
let path_value = Value::String(path.to_string_lossy().to_string());
let message = "hiya".to_string();
let message_value = Value::String(message.clone());
let _ = std::fs::remove_file(&path);
Write
.run(&Value::List(vec![path_value, message_value]))
.unwrap();
assert!(path.is_file());
}
#[test]
fn append() {
let path = PathBuf::from("./target/append.txt");
let path_value = Value::String(path.to_string_lossy().to_string());
let message = "hiya".to_string();
let message_value = Value::String(message.clone());
let _ = std::fs::remove_file(&path);
Write
.run(&Value::List(vec![
path_value.clone(),
message_value.clone(),
]))
.unwrap();
Append
.run(&Value::List(vec![path_value, message_value]))
.unwrap();
let read = fs::read_to_string(&path).unwrap();
assert_eq!("hiyahiya", read);
}
#[test]
fn read_file() {
let path = PathBuf::from("./target/read_file.txt");
let path_value = Value::String(path.to_string_lossy().to_string());
let message = "hiya".to_string();
let message_value = Value::String(message.clone());
let _ = std::fs::remove_file(&path);
Write
.run(&Value::List(vec![path_value.clone(), message_value]))
.unwrap();
let test = ReadFile.run(&path_value).unwrap();
let read = fs::read_to_string(&path).unwrap();
assert_eq!(test, Value::String(read));
}
#[test]
fn remove_file() {
let path = PathBuf::from("./target/remove_file.txt");
let path_value = Value::String(path.to_string_lossy().to_string());
let _ = std::fs::File::create(&path);
RemoveFile.run(&path_value).unwrap();
assert!(!path.exists());
}
}

160
src/tools/general.rs Normal file
View File

@ -0,0 +1,160 @@
use std::{thread::sleep, time::Duration};
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
use crate::{Result, Table, Tool, ToolInfo, Value, ValueType, TOOL_LIST};
pub struct Help;
impl Tool for Help {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "help",
description: "Get help using dust.",
group: "general",
inputs: vec![ValueType::Empty, ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
self.check_type(argument)?;
let mut table = Table::new(vec![
"tool".to_string(),
"description".to_string(),
"group".to_string(),
"inputs".to_string(),
]);
for tool in TOOL_LIST {
let tool_group = tool.info().group.to_string();
if let Ok(group) = argument.as_string() {
if &tool_group != group {
continue;
}
}
let row = vec![
Value::String(tool.info().identifier.to_string()),
Value::String(tool.info().description.to_string()),
Value::String(tool_group),
Value::List(
tool.info()
.inputs
.iter()
.map(|value_type| Value::String(value_type.to_string()))
.collect(),
),
];
table.insert(row)?;
}
Ok(Value::Table(table))
}
}
pub struct Output;
impl Tool for Output {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "output",
description: "Print a value.",
group: "general",
inputs: vec![ValueType::Any],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
println!("{argument}");
Ok(Value::Empty)
}
}
pub struct Repeat;
impl Tool for Repeat {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "repeat",
description: "Run a function the given number of times.",
group: "general",
inputs: vec![ValueType::ListExact(vec![
ValueType::Function,
ValueType::Integer,
])],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_list()?;
let function = argument[0].as_function()?;
let count = argument[1].as_int()?;
let mut result_list = Vec::with_capacity(count as usize);
for _ in 0..count {
let result = function.run()?;
result_list.push(result);
}
Ok(Value::List(result_list))
}
}
pub struct Run;
impl Tool for Run {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "run",
description: "Run functions in parallel.",
group: "general",
inputs: vec![ValueType::ListOf(Box::new(ValueType::Function))],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument_list = argument.as_list()?;
let results = argument_list
.par_iter()
.map(|value| {
let function = if let Ok(function) = value.as_function() {
function
} else {
return value.clone();
};
match function.run() {
Ok(value) => value,
Err(error) => Value::String(error.to_string()),
}
})
.collect();
Ok(Value::List(results))
}
}
pub struct Wait;
impl Tool for Wait {
fn info(&self) -> crate::ToolInfo<'static> {
ToolInfo {
identifier: "wait",
description: "Wait for the given number of milliseconds.",
group: "general",
inputs: vec![ValueType::Integer],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_int()?;
sleep(Duration::from_millis(argument as u64));
Ok(Value::Empty)
}
}

324
src/tools/gui.rs Normal file
View File

@ -0,0 +1,324 @@
use eframe::{
egui::{
plot::{Bar, BarChart, Line, Plot as EguiPlot, PlotPoints},
CentralPanel, Context, Direction, Layout, RichText, ScrollArea, Ui,
},
emath::Align,
epaint::{Color32, Stroke},
run_native, NativeOptions,
};
use egui_extras::{Column, StripBuilder, TableBuilder};
use crate::{Error, Result, Table, Tool, ToolInfo, Value, ValueType, VariableMap};
pub struct BarGraph;
impl Tool for BarGraph {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "bar_graph",
description: "Render a list of values as a bar graph.",
group: "gui",
inputs: vec![ValueType::ListOf(Box::new(ValueType::List))],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_list()?;
let mut bars = Vec::new();
for (index, value) in argument.iter().enumerate() {
let list = value.as_list()?;
let mut name = None;
let mut height = None;
for value in list {
match value {
Value::Float(float) => {
if height.is_none() {
height = Some(float);
}
}
Value::Integer(_integer) => {}
Value::String(string) => name = Some(string),
Value::Boolean(_)
| Value::List(_)
| Value::Map(_)
| Value::Table(_)
| Value::Time(_)
| Value::Function(_)
| Value::Empty => continue,
}
}
let height =
match height {
Some(height) => *height,
None => return Err(Error::CustomMessage(
"Could not create bar graph. No float value was found to use as a height."
.to_string(),
)),
};
let bar = Bar::new(index as f64, height).name(name.unwrap_or(&"".to_string()));
bars.push(bar);
}
run_native(
"bar_graph",
NativeOptions::default(),
Box::new(|_cc| Box::new(BarGraphGui::new(bars))),
)
.unwrap();
Ok(Value::Empty)
}
}
struct BarGraphGui {
bars: Vec<Bar>,
}
impl BarGraphGui {
fn new(data: Vec<Bar>) -> Self {
Self { bars: data }
}
}
impl eframe::App for BarGraphGui {
fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) {
CentralPanel::default().show(ctx, |ui| {
EguiPlot::new("bar_graph").show(ui, |plot_ui| {
plot_ui.bar_chart(BarChart::new(self.bars.clone()).color(Color32::RED));
});
});
}
}
pub struct Plot;
impl Tool for Plot {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "plot",
description: "Render a list of numbers as a scatter plot graph.",
group: "gui",
inputs: vec![
ValueType::ListOf(Box::new(ValueType::Float)),
ValueType::ListOf(Box::new(ValueType::Integer)),
],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_list()?;
let mut floats = Vec::new();
for value in argument {
if let Ok(float) = value.as_float() {
floats.push(float);
} else if let Ok(integer) = value.as_int() {
floats.push(integer as f64);
} else {
return Err(Error::expected_number(value.clone()));
}
}
run_native(
"plot",
NativeOptions {
resizable: true,
centered: true,
..Default::default()
},
Box::new(|_cc| Box::new(PlotGui::new(floats))),
)
.unwrap();
Ok(Value::Empty)
}
}
struct PlotGui {
data: Vec<f64>,
}
impl PlotGui {
fn new(data: Vec<f64>) -> Self {
Self { data }
}
}
impl eframe::App for PlotGui {
fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) {
CentralPanel::default().show(ctx, |ui| {
EguiPlot::new("plot").show(ui, |plot_ui| {
let points = self
.data
.iter()
.enumerate()
.map(|(index, value)| [index as f64, *value])
.collect::<PlotPoints>();
let line = Line::new(points);
plot_ui.line(line);
})
});
}
}
pub struct GuiApp {
text_edit_buffer: String,
_whale_context: VariableMap,
eval_result: Result<Value>,
}
impl GuiApp {
pub fn new(result: Result<Value>) -> Self {
GuiApp {
text_edit_buffer: String::new(),
_whale_context: VariableMap::new(),
eval_result: result,
}
}
fn _table_ui(&mut self, table: &Table, ui: &mut Ui) {
TableBuilder::new(ui)
.resizable(true)
.striped(true)
.columns(Column::remainder(), table.column_names().len())
.header(30.0, |mut row| {
for name in table.column_names() {
row.col(|ui| {
ui.label(name);
});
}
})
.body(|body| {
body.rows(20.0, table.rows().len(), |index, mut row| {
let row_data = table.rows().get(index).unwrap();
for cell_data in row_data {
row.col(|ui| {
ui.label(cell_data.to_string());
});
}
});
});
}
}
impl eframe::App for GuiApp {
fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) {
CentralPanel::default().show(ctx, |ui| {
ui.with_layout(
Layout {
main_dir: Direction::TopDown,
main_wrap: false,
main_align: Align::Center,
main_justify: false,
cross_align: Align::Center,
cross_justify: true,
},
|ui| {
ui.text_edit_multiline(&mut self.text_edit_buffer);
ui.horizontal(|ui| {
let clear = ui.button("clear");
let submit = ui.button("submit");
if clear.clicked() {
self.text_edit_buffer.clear();
}
if submit.clicked() {
todo!()
}
});
},
);
ui.separator();
StripBuilder::new(ui)
.sizes(egui_extras::Size::remainder(), 1)
.vertical(|mut strip| {
strip.cell(|ui| {
let rectangle = ui.available_rect_before_wrap();
let corner = 5.0;
let border = Stroke::new(1.0, Color32::DARK_GREEN);
let item_size = 20.0;
ui.painter().rect_stroke(rectangle, corner, border);
match &self.eval_result {
Ok(value) => match value {
Value::String(string) => {
ui.label(RichText::new(string).size(item_size));
}
Value::Float(float) => {
ui.label(RichText::new(float.to_string()).size(item_size));
}
Value::Integer(integer) => {
ui.label(RichText::new(integer.to_string()).size(item_size));
}
Value::Boolean(boolean) => {
ui.label(RichText::new(boolean.to_string()).size(item_size));
}
Value::List(list) => {
for value in list {
ui.label(RichText::new(value.to_string()).size(item_size));
}
}
Value::Map(_) => todo!(),
Value::Table(table) => {
ScrollArea::both().show(ui, |ui| {
TableBuilder::new(ui)
.resizable(true)
.striped(true)
.vscroll(true)
.columns(
Column::remainder(),
table.column_names().len(),
)
.header(20.0, |mut row| {
for name in table.column_names() {
row.col(|ui| {
ui.label(name);
});
}
})
.body(|body| {
body.rows(
20.0,
table.rows().len(),
|index, mut row| {
let row_data =
table.rows().get(index).unwrap();
for value in row_data {
row.col(|ui| {
ui.label(value.to_string());
});
}
},
);
});
});
}
Value::Function(_) => todo!(),
Value::Empty => {}
Value::Time(_) => todo!(),
},
Err(error) => {
let rectangle = ui.available_rect_before_wrap();
let corner = 5.0;
let border = Stroke::new(1.0, Color32::DARK_RED);
ui.painter().rect_stroke(rectangle, corner, border);
ui.label(error.to_string());
}
}
});
});
});
}
}

162
src/tools/logic.rs Normal file
View File

@ -0,0 +1,162 @@
use crate::{Error, Tool, ToolInfo, Result, Value, ValueType };
pub struct Assert;
impl Tool for Assert {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "assert",
description: "Panic if a boolean is false.",
group: "test",
inputs: vec![
ValueType::Boolean,
ValueType::Function
],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let boolean = argument.as_boolean()?;
if boolean {
Ok(Value::Empty)
} else {
Err(Error::AssertFailed)
}
}
}
pub struct AssertEqual;
impl Tool for AssertEqual {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "assert_equal",
description: "Panic if two values do not match.",
group: "test",
inputs: vec![ValueType::ListExact(vec![ValueType::Any, ValueType::Any])],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let arguments = argument.as_fixed_len_list(2)?;
if arguments[0] == arguments[1] {
Ok(Value::Empty)
} else {
Err(Error::AssertEqualFailed {
expected: arguments[0].clone(),
actual: arguments[1].clone()
})
}
}
}
pub struct If;
impl Tool for If {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "if",
description: "Evaluates the first argument. If true, it does the second argument.",
group: "logic",
inputs: vec![
ValueType::ListExact(vec![
ValueType::Boolean,
ValueType::Any,
]),
ValueType::ListExact(vec![
ValueType::Function,
ValueType::Any,
])],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_fixed_len_list(2)?;
let (condition, if_true) = (&argument[0], &argument[1]);
let condition = if let Ok(function) = condition.as_function() {
function.run()?.as_boolean()?
} else {
condition.as_boolean()?
};
if condition {
Ok(if_true.clone())
} else {
Ok(Value::Empty)
}
}
}
pub struct IfElse;
impl Tool for IfElse {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "if_else",
description: "Evaluates the first argument. If true, it does the second argument. If false, it does the third argument",
group: "logic",
inputs: vec![
ValueType::ListExact(vec![
ValueType::Boolean,
ValueType::Any,
ValueType::Any,
]),
ValueType::ListExact(vec![
ValueType::Function,
ValueType::Any,
ValueType::Any,
])],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_fixed_len_list(3)?;
let (condition, if_true, if_false) = (&argument[0], &argument[1], &argument[2]);
let condition_is_true = if let Ok(boolean) = condition.as_boolean() {
boolean
} else if let Ok(function) = condition.as_function() {
function.run()?.as_boolean()?
} else {
return Err(Error::TypeError {
expected: &[ValueType::Boolean, ValueType::Function],
actual: condition.clone(),
});
};
let should_yield = if condition_is_true { if_true } else { if_false };
if let Ok(function) = should_yield.as_function() {
function.run()
} else {
Ok(should_yield.clone())
}
}
}
pub struct Loop;
impl Tool for Loop {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "loop",
description: "Repeats a function until the program ends.",
group: "general",
inputs: vec![ValueType::Function],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let function = argument.as_function()?;
function.run()?;
Loop.run(argument)
}
}

377
src/tools/mod.rs Normal file
View File

@ -0,0 +1,377 @@
//! Dust's built-in commands.
//!
//! When a tool in invoked in Dust, the input is checked against the inputs listed in its ToolInfo.
//! The input should then be double-checked by `Tool::check_input` when you implement `run`. The
//! purpose of the second check is to weed out mistakes in how the inputs were described in the
//! ToolInfo. The errors from the second check should only come up during development and should not //! be seen by the user.
//!
//! ## Writing macros
//!
//! - Snake case identifier, this is enforced by a test
//! - The description should be brief, it will display in the shell
//! - Recycle code that is already written and tested
//! - Write non-trivial tests, do not write tests just for the sake of writing them
//!
//! ## Usage
//!
//! Commands can be used in Rust by passing a Value to the run method.
//!
//! ```
//! # use dust_lib::{tools::collections::Count, Tool, Value};
//! let value = Value::List(vec![
//! Value::Integer(1),
//! Value::Integer(2),
//! Value::Integer(3),
//! ]);
//! let count = Count
//! .run(&value)
//! .unwrap()
//! .as_int()
//! .unwrap();
//!
//! assert_eq!(count, 3);
//! ```
use crate::{Result, Value, ValueType};
pub mod collections;
pub mod command;
pub mod data_formats;
pub mod disks;
pub mod filesystem;
pub mod general;
pub mod gui;
pub mod logic;
pub mod network;
pub mod random;
pub mod system;
pub mod time;
/// Master list of all tools.
///
/// This list is used to match identifiers with tools and to provide info to the shell.
pub const TOOL_LIST: [&'static dyn Tool; 52] = [
&collections::Count,
&collections::CreateTable,
&collections::Insert,
&collections::Rows,
&collections::Select,
&collections::String,
&collections::Sort,
&collections::Replace,
&collections::Transform,
&collections::Where,
&command::Bash,
&command::Fish,
&command::Raw,
&command::Sh,
&command::Zsh,
&data_formats::FromCsv,
&data_formats::ToCsv,
&data_formats::FromJson,
&data_formats::ToJson,
&disks::ListDisks,
&disks::Partition,
&filesystem::Append,
&filesystem::CreateDir,
&filesystem::FileMetadata,
&filesystem::MoveDir,
&filesystem::ReadDir,
&filesystem::ReadFile,
&filesystem::RemoveDir,
&filesystem::Trash,
&filesystem::Watch,
&filesystem::Write,
&general::Help,
&general::Run,
&general::Output,
&general::Repeat,
&general::Wait,
&gui::BarGraph,
&gui::Plot,
&logic::If,
&logic::IfElse,
&logic::Loop,
&network::Download,
&random::Random,
&random::RandomBoolean,
&random::RandomFloat,
&random::RandomInteger,
&random::RandomString,
&system::Users,
&logic::Assert,
&logic::AssertEqual,
&time::Local,
&time::Now,
];
/// A whale macro function.
pub trait Tool: Sync + Send {
fn info(&self) -> ToolInfo<'static>;
fn run(&self, argument: &Value) -> Result<Value>;
fn check_type<'a>(&self, argument: &'a Value) -> Result<&'a Value> {
if self
.info()
.inputs
.iter()
.any(|value_type| &argument.value_type() == value_type)
{
Ok(argument)
} else {
Err(crate::Error::TypeCheckFailure {
tool_info: self.info(),
argument: argument.clone(),
})
}
}
fn fail(&self, argument: &Value) -> Result<Value> {
Err(crate::Error::TypeCheckFailure {
tool_info: self.info(),
argument: argument.clone(),
})
}
}
/// Information needed for each macro.
#[derive(Clone, Debug, PartialEq)]
pub struct ToolInfo<'a> {
/// Text pattern that triggers this macro.
pub identifier: &'a str,
/// User-facing information about how the macro works.
pub description: &'a str,
/// Category used to sort macros in the shell.
pub group: &'a str,
pub inputs: Vec<ValueType>,
}
// pub struct SystemInfo;
// impl Macro for SystemInfo {
// fn info(&self) -> MacroInfo<'static> {
// MacroInfo {
// identifier: "system_info",
// description: "Get information on the system.",
// }
// }
// fn run(&self, argument: &Value) -> crate::Result<Value> {
// argument.as_empty()?;
// let mut map = VariableMap::new();
// map.set_value("hostname", Value::String(hostname()?))?;
// Ok(Value::Map(map))
// }
// }
// pub struct Map;
// impl Macro for Map {
// fn info(&self) -> MacroInfo<'static> {
// MacroInfo {
// identifier: "map",
// description: "Create a map from a value.",
// }
// }
// fn run(&self, argument: &Value) -> Result<Value> {
// match argument {
// Value::String(_) => todo!(),
// Value::Float(_) => todo!(),
// Value::Integer(_) => todo!(),
// Value::Boolean(_) => todo!(),
// Value::List(_) => todo!(),
// Value::Map(_) => todo!(),
// Value::Table(table) => Ok(Value::Map(VariableMap::from(table))),
// Value::Function(_) => todo!(),
// Value::Empty => todo!(),
// }
// }
// }
// pub struct Status;
// impl Macro for Status {
// fn info(&self) -> MacroInfo<'static> {
// MacroInfo {
// identifier: "git_status",
// description: "Get the repository status for the current directory.",
// }
// }
// fn run(&self, argument: &Value) -> Result<Value> {
// argument.as_empty()?;
// let repo = Repository::open(".")?;
// let mut table = Table::new(vec![
// "path".to_string(),
// "status".to_string(),
// "staged".to_string(),
// ]);
// for entry in repo.statuses(None)?.into_iter() {
// let (status, staged) = {
// if entry.status().is_wt_new() {
// ("created".to_string(), false)
// } else if entry.status().is_wt_deleted() {
// ("deleted".to_string(), false)
// } else if entry.status().is_wt_modified() {
// ("modified".to_string(), false)
// } else if entry.status().is_index_new() {
// ("created".to_string(), true)
// } else if entry.status().is_index_deleted() {
// ("deleted".to_string(), true)
// } else if entry.status().is_index_modified() {
// ("modified".to_string(), true)
// } else if entry.status().is_ignored() {
// continue;
// } else {
// ("".to_string(), false)
// }
// };
// let path = entry.path().unwrap().to_string();
// table.insert(vec![
// Value::String(path),
// Value::String(status),
// Value::Boolean(staged),
// ])?;
// }
// Ok(Value::Table(table))
// }
// }
// pub struct DocumentConvert;
// impl Macro for DocumentConvert {
// fn info(&self) -> MacroInfo<'static> {
// MacroInfo {
// identifier: "convert_document",
// description: "Convert a file's contents to a format and set the extension.",
// }
// }
// fn run(&self, argument: &Value) -> Result<Value> {
// let argument = argument.as_list()?;
// if argument.len() != 3 {
// return Err(Error::WrongFunctionArgumentAmount {
// expected: 3,
// actual: argument.len(),
// });
// }
// let (path, from, to) = (
// argument[0].as_string()?,
// argument[1].as_string()?,
// argument[2].as_string()?,
// );
// let mut file_name = PathBuf::from(&path);
// file_name.set_extension(to);
// let new_file_name = file_name.to_str().unwrap();
// let script = format!("pandoc --from {from} --to {to} --output {new_file_name} {path}");
// Command::new("fish").arg("-c").arg(script).spawn()?.wait()?;
// Ok(Value::Empty)
// }
// }
// pub struct Trash;
// impl Macro for Trash {
// fn info(&self) -> MacroInfo<'static> {
// MacroInfo {
// identifier: "trash_dir",
// description: "Move a directory to the trash.",
// }
// }
// fn run(&self, argument: &Value) -> Result<Value> {
// let path = argument.as_string()?;
// trash::delete(path)?;
// Ok(Value::Empty)
// }
// }
// pub struct Get;
// impl Macro for Get {
// fn info(&self) -> MacroInfo<'static> {
// MacroInfo {
// identifier: "get",
// description: "Extract a value from a collection.",
// }
// }
// fn run(&self, argument: &Value) -> Result<Value> {
// let argument_list = argument.as_list()?;
// let collection = &argument_list[0];
// let index = &argument_list[1];
// if let Ok(list) = collection.as_list() {
// let index = index.as_int()?;
// let value = list.get(index as usize).unwrap_or(&Value::Empty);
// return Ok(value.clone());
// }
// if let Ok(table) = collection.as_table() {
// let index = index.as_int()?;
// let get_row = table.get(index as usize);
// if let Some(row) = get_row {
// return Ok(Value::List(row.clone()));
// }
// }
// Err(Error::TypeError {
// expected: &[
// ValueType::List,
// ValueType::Map,
// ValueType::Table,
// ValueType::String,
// ],
// actual: collection.clone(),
// })
// }
// }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tool_identifier_formatting() {
for function in TOOL_LIST {
let identifier = function.info().identifier;
assert_eq!(identifier.to_lowercase(), identifier);
assert!(identifier.is_ascii());
assert!(!identifier.is_empty());
assert!(!identifier.contains(' '));
assert!(!identifier.contains(':'));
assert!(!identifier.contains('.'));
assert!(!identifier.contains('-'));
}
}
#[test]
fn tool_inputs_exist() {
for function in TOOL_LIST {
let identifier = function.info().identifier;
let input_count = function.info().inputs.len();
assert!(input_count > 0, "{} has no inputs declared", identifier);
}
}
}

23
src/tools/network.rs Normal file
View File

@ -0,0 +1,23 @@
//! Macros for network access.
use crate::{Result, Tool, ToolInfo, Value, ValueType};
pub struct Download;
impl Tool for Download {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "download",
description: "Fetch a network resource.",
group: "network",
inputs: vec![ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = argument.as_string()?;
let output = reqwest::blocking::get(argument)?.text()?;
Ok(Value::String(output))
}
}

161
src/tools/random.rs Normal file
View File

@ -0,0 +1,161 @@
use std::convert::TryInto;
use rand::{random, thread_rng, Rng};
use crate::{Error, Result, Tool, ToolInfo, Value, ValueType};
pub struct RandomBoolean;
impl Tool for RandomBoolean {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "random_boolean",
description: "Create a random boolean.",
group: "random",
inputs: vec![ValueType::Empty],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = self.check_type(argument)?;
if let Value::Empty = argument {
let boolean = rand::thread_rng().gen();
Ok(Value::Boolean(boolean))
} else {
self.fail(argument)
}
}
}
pub struct RandomInteger;
impl Tool for RandomInteger {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "random_integer",
description: "Create a random integer.",
group: "random",
inputs: vec![
ValueType::Empty,
ValueType::Integer,
ValueType::ListExact(vec![ValueType::Integer, ValueType::Integer]),
],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
match argument {
Value::Integer(max) => {
let integer = rand::thread_rng().gen_range(0..*max);
Ok(Value::Integer(integer))
}
Value::List(min_max) => {
Error::expect_function_argument_amount(self.info().identifier, min_max.len(), 2)?;
let min = min_max[0].as_int()?;
let max = min_max[1].as_int()? + 1;
let integer = rand::thread_rng().gen_range(min..max);
Ok(Value::Integer(integer))
}
Value::Empty => Ok(crate::Value::Integer(random())),
_ => self.fail(argument),
}
}
}
pub struct RandomString;
impl Tool for RandomString {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "random_string",
description: "Generate a random string.",
group: "random",
inputs: vec![ValueType::Empty, ValueType::Integer],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = self.check_type(argument)?;
if let Value::Integer(length) = argument {
let length: usize = length.unsigned_abs().try_into().unwrap_or(0);
let mut random = String::with_capacity(length);
for _ in 0..length {
let random_char = thread_rng().gen_range('A'..='z').to_string();
random.push_str(&random_char);
}
return Ok(Value::String(random));
}
if let Value::Empty = argument {
let mut random = String::with_capacity(10);
for _ in 0..10 {
let random_char = thread_rng().gen_range('A'..='z').to_string();
random.push_str(&random_char);
}
return Ok(Value::String(random));
}
self.fail(argument)
}
}
pub struct RandomFloat;
impl Tool for RandomFloat {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "random_float",
description: "Generate a random floating point value between 0 and 1.",
group: "random",
inputs: vec![ValueType::Empty],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = self.check_type(argument)?;
if argument.is_empty() {
Ok(Value::Float(random()))
} else {
self.fail(argument)
}
}
}
pub struct Random;
impl Tool for Random {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "random",
description: "Select a random item from a list.",
group: "random",
inputs: vec![ValueType::List],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
let argument = self.check_type(argument)?;
if let Value::List(list) = argument {
let random_index = thread_rng().gen_range(0..list.len());
let random_item = list.get(random_index).unwrap();
Ok(random_item.clone())
} else {
self.fail(argument)
}
}
}

28
src/tools/system.rs Normal file
View File

@ -0,0 +1,28 @@
use sysinfo::{RefreshKind, System, SystemExt, UserExt};
use crate::{Result, Tool, ToolInfo, Value, ValueType};
pub struct Users;
impl Tool for Users {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "users",
description: "Get a list of the system's users.",
group: "system",
inputs: vec![ValueType::Empty],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
argument.as_empty()?;
let users = System::new_with_specifics(RefreshKind::new().with_users_list())
.users()
.iter()
.map(|user| Value::String(user.name().to_string()))
.collect();
Ok(Value::List(users))
}
}

43
src/tools/time.rs Normal file
View File

@ -0,0 +1,43 @@
use std::time::Instant;
use crate::{Result, Time, Tool, ToolInfo, Value, ValueType};
pub struct Now;
impl Tool for Now {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "now",
description: "Return the current time.",
group: "time",
inputs: vec![ValueType::Empty],
}
}
fn run(&self, argument: &crate::Value) -> Result<Value> {
argument.as_empty()?;
let time = Time::monotonic(Instant::now());
Ok(Value::Time(time))
}
}
pub struct Local;
impl Tool for Local {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "local",
description: "Show a time value adjusted for the current time zone.",
group: "time",
inputs: vec![ValueType::Time],
}
}
fn run(&self, argument: &crate::Value) -> Result<Value> {
let argument = argument.as_time()?;
Ok(Value::String(argument.as_local()))
}
}

View File

@ -1,40 +0,0 @@
use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
use crate::{Identifier, Value};
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct EnumInstance {
name: Identifier,
variant: Identifier,
value: Option<Box<Value>>,
}
impl EnumInstance {
pub fn new(name: Identifier, variant_name: Identifier, value: Option<Value>) -> Self {
Self {
name,
variant: variant_name,
value: value.map(|value| Box::new(value)),
}
}
pub fn name(&self) -> &Identifier {
&self.name
}
pub fn variant(&self) -> &Identifier {
&self.variant
}
pub fn value(&self) -> &Option<Box<Value>> {
&self.value
}
}
impl Display for EnumInstance {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}::{}({:?})", self.name, self.variant, self.value)
}
}

View File

@ -2,49 +2,37 @@ use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
use crate::{
built_in_functions::Callable, BuiltInFunction, Format, FunctionNode, Identifier, Type,
};
use crate::{Identifier, Result, Statement, Value, VariableMap};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Function {
BuiltIn(BuiltInFunction),
ContextDefined(FunctionNode),
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Function {
identifiers: Vec<Identifier>,
statements: Vec<Statement>,
}
impl Function {
pub fn r#type(&self) -> Type {
match self {
Function::BuiltIn(built_in_function) => built_in_function.r#type(),
Function::ContextDefined(context_defined_function) => {
context_defined_function.r#type().clone()
}
pub fn new(identifiers: Vec<Identifier>, statements: Vec<Statement>) -> Self {
Function {
identifiers,
statements,
}
}
pub fn parameters(&self) -> Option<&Vec<Identifier>> {
if let Function::ContextDefined(function) = self {
Some(function.parameters())
} else {
None
}
pub fn run(&self) -> Result<Value> {
todo!()
}
}
impl Format for Function {
fn format(&self, output: &mut String, indent_level: u8) {
match self {
Function::BuiltIn(built_in_function) => built_in_function.format(output, indent_level),
Function::ContextDefined(function_node) => function_node.format(output, indent_level),
}
pub fn run_with_context(&self, _context: &mut VariableMap) -> Result<Value> {
todo!()
}
}
impl Display for Function {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Function::BuiltIn(built_in_function) => write!(f, "{built_in_function}"),
Function::ContextDefined(function_node) => write!(f, "{function_node}"),
}
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"function < {:?} > {{ {:?} }}",
self.identifiers, self.statements
)
}
}

21
src/value/iter.rs Normal file
View File

@ -0,0 +1,21 @@
use crate::Value;
pub struct Iter(Value);
impl IntoIterator for Value {
type Item = Value;
type IntoIter = Iter;
fn into_iter(self) -> Self::IntoIter {
Iter(self)
}
}
impl Iterator for Iter {
type Item = Value;
fn next(&mut self) -> Option<Self::Item> {
todo!()
}
}

View File

@ -1,119 +0,0 @@
use std::{
cmp::Ordering,
fmt::{self, Display, Formatter},
sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard},
};
use stanza::{
renderer::{console::Console, Renderer},
style::Styles,
table::{Cell, Content, Row, Table},
};
use crate::{error::rw_lock_error::RwLockError, Value};
#[derive(Debug, Clone)]
pub struct List(Arc<RwLock<Vec<Value>>>);
impl Default for List {
fn default() -> Self {
Self::new()
}
}
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) -> Result<RwLockReadGuard<Vec<Value>>, RwLockError> {
Ok(self.0.read()?)
}
pub fn items_mut(&self) -> Result<RwLockWriteGuard<Vec<Value>>, RwLockError> {
Ok(self.0.write()?)
}
pub fn as_text_table(&self) -> Table {
let cells: Vec<Cell> = self
.items()
.unwrap()
.iter()
.map(|value| {
if let Value::List(list) = value {
Cell::new(Styles::default(), Content::Nested(list.as_text_table()))
} else if let Value::Map(map) = value {
Cell::new(Styles::default(), Content::Nested(map.as_text_table()))
} else {
Cell::new(Styles::default(), Content::Label(value.to_string()))
}
})
.collect();
let row = if cells.is_empty() {
Row::new(
Styles::default(),
vec![Cell::new(
Styles::default(),
Content::Label("empty list".to_string()),
)],
)
} else {
Row::new(Styles::default(), cells)
};
Table::default().with_row(row)
}
}
impl Display for List {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let renderer = Console::default();
f.write_str(&renderer.render(&self.as_text_table()))
}
}
impl Eq for List {}
impl PartialEq for List {
fn eq(&self, other: &Self) -> bool {
if let (Ok(left), Ok(right)) = (self.items(), other.items()) {
if left.len() != right.len() {
return false;
} else {
for (i, j) in left.iter().zip(right.iter()) {
if i != j {
return false;
}
}
}
}
true
}
}
impl PartialOrd for List {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for List {
fn cmp(&self, other: &Self) -> Ordering {
if let (Ok(left), Ok(right)) = (self.items(), other.items()) {
left.cmp(&right)
} else {
Ordering::Equal
}
}
}

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