Compare commits
217 Commits
custom_typ
...
main
Author | SHA1 | Date | |
---|---|---|---|
25e3941315 | |||
900de8ca4b | |||
c4b51a1ef9 | |||
939b7464c6 | |||
64fb20c45e | |||
e3b55092b3 | |||
bd4983b821 | |||
a1500bf262 | |||
1585145ff4 | |||
fb3cd6e6da | |||
69347ad435 | |||
ca72fe04f1 | |||
eaf26fec5e | |||
0eac67eb3a | |||
37fd722fa6 | |||
255843cb3b | |||
927a2cfbf9 | |||
88d05f0dc9 | |||
0805b96809 | |||
a5f3127bcf | |||
01bdaa308d | |||
dbf9ab0d00 | |||
2bbbfa34a4 | |||
979335f497 | |||
ef12d97895 | |||
14eedc6a2a | |||
5559860699 | |||
3a63d4973d | |||
52027db6c3 | |||
4afc8face8 | |||
5450f00174 | |||
a52eadc5ad | |||
d4a5424ad5 | |||
f835f2817f | |||
86ce1dc3af | |||
ca04103372 | |||
dab3d2de8e | |||
6c699ec900 | |||
4f5ad1e4aa | |||
d05b5a8628 | |||
a46d5bb4ea | |||
1094a5662c | |||
fd33f330f7 | |||
bda217135e | |||
ee4f37080e | |||
7003c37aac | |||
a53f83f03a | |||
4b0910a545 | |||
c82f631524 | |||
d27c98e393 | |||
97640c1b9b | |||
d2e0de0483 | |||
7eecb7b070 | |||
1819c7e646 | |||
ee692b360e | |||
8c4b2c9eef | |||
122d81f252 | |||
d8705c5d50 | |||
c466096c8d | |||
9f2b0461df | |||
1ce2178af5 | |||
172a6fa860 | |||
51869f04b6 | |||
50a7a7aca1 | |||
edded5043d | |||
5e105177cf | |||
ec074177d5 | |||
c2ba519240 | |||
91e94a5adc | |||
e7f5d66297 | |||
e1c3e8bc0d | |||
a6e52e4ee6 | |||
85cb641af8 | |||
933ab3900b | |||
d9f065fbb6 | |||
540f59e6d8 | |||
b8f2fe7eb4 | |||
ed1f139595 | |||
4c68bc0260 | |||
d3601be44c | |||
fc3dfc0e03 | |||
97319d28b2 | |||
89a4c09307 | |||
b8c54ea8bd | |||
4323c50d32 | |||
390d1aa504 | |||
5e685d6641 | |||
c2f0ec28ba | |||
a23688803c | |||
85419c47be | |||
1f5dacad7d | |||
52493a0b73 | |||
18508fa217 | |||
e9bc16af0d | |||
3f4c4ff464 | |||
3c72e4f988 | |||
41a268389c | |||
bbab728ce9 | |||
daf78919da | |||
1e665a6f13 | |||
b7e0828ced | |||
924b388f2c | |||
d243c030e8 | |||
b1266df835 | |||
b5b317df95 | |||
f2049225fe | |||
90c0304af5 | |||
d997bbd08a | |||
ddd5912248 | |||
4479f340d7 | |||
9ef82df3a7 | |||
e486413aca | |||
9a465cb42d | |||
88ca9c5ea4 | |||
7f849f13a3 | |||
cd4296c941 | |||
699c37d860 | |||
820c863f7f | |||
65afe9dd29 | |||
9c87d70659 | |||
c5241bb0af | |||
4cbfdde4a3 | |||
7c9be2151d | |||
0ba3ed51e0 | |||
ba0d154962 | |||
93e2a24a25 | |||
f85fed941a | |||
9762112b3c | |||
4e8799e750 | |||
7b101c944a | |||
61e7079a00 | |||
e756c7eac1 | |||
36dd23a7ab | |||
b182e3b945 | |||
281732924c | |||
5adfa716fd | |||
ce0ca17beb | |||
8a5efa054b | |||
2588715f98 | |||
7f30097d45 | |||
da11431edf | |||
fb1399ab0d | |||
ae278e42ad | |||
df34c10d47 | |||
7f9d43e377 | |||
ed6fad9843 | |||
0752ebedf2 | |||
df5cf93e58 | |||
bd6ca6a6c1 | |||
34173c261b | |||
6a9ce76007 | |||
433306b3e1 | |||
80428a3dd7 | |||
7b9913309d | |||
f67ef7f26f | |||
3bb9090afa | |||
a0b38d329b | |||
0eee9f0936 | |||
3d21196768 | |||
f6a1e641c9 | |||
c2fc3362c8 | |||
54790bc0db | |||
9a65afa083 | |||
70ad08128c | |||
5bdb9f116f | |||
fe1f007692 | |||
58bbbb749e | |||
8f3d36fc8d | |||
784e4b309d | |||
ac29f0210f | |||
12f82f7bfd | |||
52c6c3a507 | |||
f2e7badf4b | |||
848a0cadb6 | |||
363ecf444b | |||
270c2fd1dc | |||
9299131024 | |||
e4bd0a51d6 | |||
6c997c837d | |||
42ec57bf82 | |||
bdef5db051 | |||
6c4efadb10 | |||
ed6e4cfd1a | |||
8224f7fe3c | |||
4e61c6dd6e | |||
7d0cce6fcb | |||
44dc6db377 | |||
fc7cffcb70 | |||
1ae9dd67a7 | |||
7642b23553 | |||
f04adfc661 | |||
4a42f51580 | |||
5fada12165 | |||
74dd455ae4 | |||
c736e3be8f | |||
3e1765e810 | |||
9538caf330 | |||
c75538c064 | |||
c4908dc00d | |||
b7db177bd2 | |||
d8850b2d3c | |||
e6acb8cbb9 | |||
f89e94cc33 | |||
7d7b96d76f | |||
14d967b659 | |||
8737175df0 | |||
731bf1cb98 | |||
a52b17930e | |||
9cee46cfe5 | |||
dcb0133a4b | |||
9417d0d160 | |||
a4013fa26d | |||
86d2e6aaf4 | |||
3cbd3bbf3c | |||
45384fb394 | |||
bb53331b65 | |||
d4487117eb |
2406
Cargo.lock
generated
2406
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
20
Cargo.toml
20
Cargo.toml
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "dust-lang"
|
||||
description = "General purpose programming language"
|
||||
version = "0.4.1"
|
||||
version = "0.4.2"
|
||||
repository = "https://git.jeffa.io/jeff/dust.git"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
@ -18,16 +18,8 @@ opt-level = 1
|
||||
opt-level = 3
|
||||
|
||||
[dependencies]
|
||||
ansi_term = "0.12.1"
|
||||
clap = { version = "4.4.4", features = ["derive"] }
|
||||
csv = "1.2.2"
|
||||
egui = "0.24.1"
|
||||
eframe = { version = "0.24.1", default-features = false, features = [
|
||||
"accesskit",
|
||||
"default_fonts",
|
||||
"glow",
|
||||
"persistence",
|
||||
] }
|
||||
libc = "0.2.148"
|
||||
log = "0.4.20"
|
||||
rand = "0.8.5"
|
||||
@ -37,12 +29,18 @@ serde = { version = "1.0.188", features = ["derive"] }
|
||||
serde_json = "1.0.107"
|
||||
toml = "0.8.1"
|
||||
tree-sitter = "0.20.10"
|
||||
egui_extras = "0.24.2"
|
||||
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"
|
||||
rustyline = { version = "12.0.0", features = ["derive", "with-file-history"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
|
363
README.md
363
README.md
@ -1,303 +1,100 @@
|
||||
# Dust
|
||||
|
||||
Dust is a general purpose programming language that emphasises concurrency and correctness.
|
||||
High-level programming language with effortless concurrency, automatic memory management, type safety and strict error handling.
|
||||
|
||||
A basic dust program:
|
||||
|
||||
```dust
|
||||
output("Hello world!")
|
||||
```
|
||||
|
||||
Dust can do two (or more) things at the same time with effortless concurrency:
|
||||
|
||||
```dust
|
||||
async {
|
||||
output('will this one finish first?')
|
||||
output('or will this one?')
|
||||
}
|
||||
```
|
||||
|
||||
You can make *any* block, i.e. `{}`, run its statements in parallel by changing it to `async {}`.
|
||||
|
||||
```dust
|
||||
if random_boolean() {
|
||||
output("Do something...")
|
||||
} else async {
|
||||
output("Do something else instead...")
|
||||
output("And another thing at the same time...")
|
||||
}
|
||||
```
|
||||
|
||||
Dust is an interpreted, strictly typed language with first class functions. It emphasises concurrency by allowing any group of statements to be executed in parallel. Dust includes built-in tooling to import and export data in a variety of formats, including JSON, TOML, YAML and CSV.
|
||||
![Dust version of an example from The Rust Programming Language.](https://git.jeffa.io/jeff/dust/raw/branch/main/docs/assets/example_0.png)
|
||||
|
||||
<!--toc:start-->
|
||||
- [Dust](#dust)
|
||||
- [Features](#features)
|
||||
- [Usage](#usage)
|
||||
- [Installation](#installation)
|
||||
- [Benchmarks](#benchmarks)
|
||||
- [Implementation](#implementation)
|
||||
- [The Dust Programming Language](#the-dust-programming-language)
|
||||
- [Declaring Variables](#declaring-variables)
|
||||
- [Lists](#lists)
|
||||
- [Maps](#maps)
|
||||
- [Loops](#loops)
|
||||
- [Functions](#functions)
|
||||
- [Option](#option)
|
||||
- [Concurrency](#concurrency)
|
||||
- [Acknowledgements](#acknowledgements)
|
||||
- [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)
|
||||
<!--toc:end-->
|
||||
|
||||
## Features
|
||||
|
||||
- Simplicity: Dust is designed to be easy to learn.
|
||||
- Speed: Dust is built on [Tree Sitter] and [Rust] to prioritize performance and correctness. See [Benchmarks] below.
|
||||
- Concurrency: Safe, effortless parallel code using thread pools.
|
||||
- Safety: Written in safe, stable Rust.
|
||||
- Correctness: Type checking makes it easy to write good code.
|
||||
### Easy to Read and Write
|
||||
|
||||
## Usage
|
||||
Dust has simple, easy-to-learn syntax.
|
||||
|
||||
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.
|
||||
```js
|
||||
output('Hello world!')
|
||||
```
|
||||
|
||||
```sh
|
||||
### 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.
|
||||
|
||||
```dust
|
||||
match io:stdin() {
|
||||
Result::Ok(input) -> output("We read this input: " + input)
|
||||
Result::Error(message) -> output("We got this error: " + message)
|
||||
}
|
||||
```
|
||||
|
||||
## Installation and Usage
|
||||
|
||||
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.
|
||||
|
||||
To install from the git repository:
|
||||
|
||||
```fish
|
||||
git clone https://git.jeffa.io/jeff/dust
|
||||
cd dust
|
||||
cargo run --release
|
||||
```
|
||||
|
||||
To install with cargo:
|
||||
|
||||
```fish
|
||||
cargo install dust-lang
|
||||
dust -c "output('Hello world!')"
|
||||
dust
|
||||
```
|
||||
|
||||
```txt
|
||||
General purpose programming language
|
||||
|
||||
Usage: dust [OPTIONS] [PATH]
|
||||
|
||||
Arguments:
|
||||
[PATH] Location of the file to run
|
||||
|
||||
Options:
|
||||
-c, --command <COMMAND> Dust source code to evaluate
|
||||
-i, --input <INPUT> Data to assign to the "input" variable
|
||||
-p, --input-path <INPUT_PATH> A path to file whose contents will be assigned to the "input" variable
|
||||
-t, --tree Show the syntax tree
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
You must have the default rust toolchain installed and up-to-date. Install [rustup] if it is not already installed. Run `cargo install dust-lang` then run `dust` to start the interactive shell. Use `dust --help` to see the full command line options.
|
||||
|
||||
To build from source, clone the repository and build the parser. To do so, enter the `tree-sitter-dust` directory and run `tree-sitter-generate`. In the project root, run `cargo run` to start the shell. To see other command line options, use `cargo run -- --help`.
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Dust is at a very early development stage but performs strongly in preliminary benchmarks. The examples given were tested using [Hyperfine] on a single-core cloud instance with 1024 MB RAM. Each test was run 1000 times. The test script is shown below. Each test asks the program to read a JSON file and count the objects. Dust is a command line shell, programming language and data manipulation tool so three appropriate targets were chosen for comparison: nushell, NodeJS and jq. The programs produced identical output with the exception that NodeJS printed in color.
|
||||
## Development Status
|
||||
|
||||
For the first test, a file with four entries was used.
|
||||
|
||||
| Command | Mean [ms] | Min [ms] | Max [ms]
|
||||
|:---|---:|---:|---:|
|
||||
| Dust | 3.1 ± 0.5 | 2.4 | 8.4 |
|
||||
| jq | 33.7 ± 2.2 | 30.0 | 61.8 |
|
||||
| NodeJS | 226.4 ± 13.1 | 197.6 | 346.2 |
|
||||
| Nushell | 51.6 ± 3.7 | 45.4 | 104.3 |
|
||||
|
||||
The second set of data is from the GitHub API, it consists of 100 commits from the jq GitHub repo.
|
||||
|
||||
| Command | Mean [ms] | Min [ms] | Max [ms] |
|
||||
|:---|---:|---:|---:|
|
||||
| Dust | 6.8 ± 0.6 | 5.7 | 12.0 | 2.20 ± 0.40 |
|
||||
| jq | 43.3 ± 3.6 | 37.6 | 81.6 | 13.95 ± 2.49 |
|
||||
| NodeJS | 224.9 ± 12.3 | 194.8 | 298.5 |
|
||||
| Nushell | 59.2 ± 5.7 | 49.7 | 125.0 | 19.11 ± 3.55 |
|
||||
|
||||
This data came from CERN, it is a massive file of 100,000 entries.
|
||||
|
||||
| Command | Mean [ms] | Min [ms] | Max [ms] |
|
||||
|:---|---:|---:|---:|
|
||||
| Dust | 1080.8 ± 38.7 | 975.3 | 1326.6 |
|
||||
| jq | 1305.3 ± 64.3 | 1159.7 | 1925.1 |
|
||||
| NodeJS | 1850.5 ± 72.5 | 1641.9 | 2395.1 |
|
||||
| Nushell | 1850.5 ± 86.2 | 1625.5 | 2400.7 |
|
||||
|
||||
The tests were run after 5 warmup runs and the cache was cleared before each run.
|
||||
|
||||
```sh
|
||||
hyperfine \
|
||||
--shell none \
|
||||
--warmup 5 \
|
||||
--prepare "rm -rf /root/.cache" \
|
||||
--runs 1000 \
|
||||
--parameter-list data_path seaCreatures.json,jq_data.json,dielectron.json \
|
||||
--export-markdown test_output.md \
|
||||
"dust -c '(length (from_json input))' -p {data_path}" \
|
||||
"jq 'length' {data_path}" \
|
||||
"node --eval \"require('node:fs').readFile('{data_path}',(err,data)=>{console.log(JSON.parse(data).length)})\"" \
|
||||
"nu -c 'open {data_path} | length'"
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
Dust is formally defined as a Tree Sitter grammar in the tree-sitter-dust directory. Tree sitter generates a parser, written in C, from a set of rules defined in JavaScript. Dust itself is a rust binary that calls the C parser using FFI.
|
||||
|
||||
Tests are written in three places: in the Rust library, in Dust as examples and in the Tree Sitter test format. Generally, features are added by implementing and testing the syntax in the tree-sitter-dust repository, then writing library tests to evaluate the new syntax. Implementation tests run the Dust files in the "examples" directory and should be used to demonstrate and verify that features work together.
|
||||
|
||||
Tree Sitter generates a concrete syntax tree, which Dust traverses to create an abstract syntax tree that can run the Dust code. The CST generation is an extra step but it allows easy testing of the parser, defining the language in one file and makes the syntax easy to modify and expand. Because it uses Tree Sitter, developer-friendly features like syntax highlighting and code navigation are already available in any text editor that supports Tree Sitter.
|
||||
|
||||
## The Dust Programming Language
|
||||
|
||||
Dust is easy to learn. Aside from this guide, the best way to learn Dust is to read the examples and tests to get a better idea of what it can do.
|
||||
|
||||
### 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
|
||||
- float
|
||||
- boolean
|
||||
- list
|
||||
- map
|
||||
- option
|
||||
- function
|
||||
|
||||
Here are some examples of variables in dust.
|
||||
|
||||
```dust
|
||||
string = "foobar"
|
||||
integer = 42
|
||||
float = 42.42
|
||||
list = [1 2 string integer float] # Commas are optional when writing lists.
|
||||
map = {
|
||||
key = 'value'
|
||||
}
|
||||
```
|
||||
|
||||
Note that strings can be wrapped with any kind of quote: single, double or backticks. Numbers are always integers by default. Floats are declared by adding a decimal. If you divide integers or do any kind of math with a float, you will create a float value.
|
||||
|
||||
Dust enforces strict type checking, but you don't usually need to write the type, dust can figure it out on its own. The **number** and **any** types are special types that allow you to relax the type bounds.
|
||||
|
||||
```dust
|
||||
string <str> = "foobar"
|
||||
integer <int> = 42
|
||||
float <float> = 42.42
|
||||
|
||||
numbers <[number]> = [integer float]
|
||||
|
||||
stuff <[any]> = [string integer float]
|
||||
```
|
||||
|
||||
### 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 using a colon `:` followed by 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) # You can use commas when passing values a function.
|
||||
```
|
||||
|
||||
### Maps
|
||||
|
||||
Maps are flexible collections with arbitrary key-value pairs, similar to JSON objects. A map is created with a pair of curly braces and its entries are variables declared inside those braces. Map contents can be accessed using a colon `:`.
|
||||
|
||||
```dust
|
||||
reminder = {
|
||||
message = "Buy milk"
|
||||
tags = ["groceries", "home"]
|
||||
}
|
||||
|
||||
output(reminder:message)
|
||||
```
|
||||
|
||||
### Loops
|
||||
|
||||
A **while** loop continues until a predicate is false.
|
||||
|
||||
```dust
|
||||
i = 0
|
||||
while i < 10 {
|
||||
output(i)
|
||||
i += 1
|
||||
}
|
||||
```
|
||||
|
||||
A **for** loop operates on a list without mutating it or the items inside. It does not return a value.
|
||||
|
||||
```dust
|
||||
list = [ 1, 2, 3 ]
|
||||
|
||||
for number in list {
|
||||
output(number + 1)
|
||||
}
|
||||
```
|
||||
|
||||
### Functions
|
||||
|
||||
Functions are first-class values in dust, so they are assigned to variables like any other value.
|
||||
|
||||
```dust
|
||||
# This simple function has no arguments and no return value.
|
||||
say_hi = () <none> {
|
||||
output("hi")
|
||||
}
|
||||
|
||||
# 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)
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
### Option
|
||||
|
||||
An **option** represents a value that may not be present. It has two variants: **some** and **none**. Dust includes built-in functions to work with option values: `is_none`, `is_some` and `either_or`.
|
||||
|
||||
```dust
|
||||
say_something = (message <option(str)>) <str> {
|
||||
either_or(message, "hiya")
|
||||
}
|
||||
|
||||
say_something(some("goodbye"))
|
||||
# goodbye
|
||||
|
||||
say_something(none)
|
||||
# hiya
|
||||
```
|
||||
|
||||
### Concurrency
|
||||
|
||||
Dust features effortless concurrency anywhere in your code. Any block of code can be made to run its contents asynchronously. Dust's concurrency is written in safe Rust and uses a thread pool whose size depends on the number of cores available.
|
||||
|
||||
```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")
|
||||
}
|
||||
```
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
Dust began as a fork of [evalexpr]. Some of the original code is still in place but the project has dramatically changed and no longer uses any of its parsing or interpreting.
|
||||
|
||||
[Tree Sitter]: https://tree-sitter.github.io/tree-sitter/
|
||||
[Rust]: https://rust-lang.org
|
||||
[evalexpr]: https://github.com/ISibboI/evalexpr
|
||||
[rustup]: https://rustup.rs
|
||||
[Hyperfine]: https://github.com/sharkdp/hyperfine
|
||||
Currently, Dust is being prepared for version 1.0. Until then, there may be breaking changes to the language and CLI.
|
||||
|
@ -1,14 +0,0 @@
|
||||
{
|
||||
"keybindings": {
|
||||
"Home": {
|
||||
"<q>": "Quit", // Quit the application
|
||||
"<Ctrl-d>": "Quit", // Another way to quit
|
||||
"<Ctrl-c>": "Quit", // Yet another way to quit
|
||||
"<Ctrl-z>": "Suspend", // Suspend the application
|
||||
"<up>": "Up",
|
||||
"<down>": "Down",
|
||||
"<left>": "Left",
|
||||
"<right>": "Right",
|
||||
},
|
||||
}
|
||||
}
|
8
build.rs
8
build.rs
@ -1,15 +1,17 @@
|
||||
fn main() {
|
||||
let src_dir = std::path::Path::new("tree-sitter-dust/src");
|
||||
|
||||
let mut c_config = cc::Build::new();
|
||||
|
||||
c_config.include(src_dir);
|
||||
c_config
|
||||
.flag_if_supported("-Wno-unused-parameter")
|
||||
.flag_if_supported("-Wno-unused-but-set-variable")
|
||||
.flag_if_supported("-Wno-trigraphs");
|
||||
let parser_path = src_dir.join("parser.c");
|
||||
c_config.file(&parser_path);
|
||||
|
||||
let parser_path = src_dir.join("parser.c");
|
||||
|
||||
c_config.file(&parser_path);
|
||||
c_config.compile("parser");
|
||||
|
||||
println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap());
|
||||
}
|
||||
|
BIN
docs/assets/debugging.png
Normal file
BIN
docs/assets/debugging.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
BIN
docs/assets/example_0.png
Normal file
BIN
docs/assets/example_0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
BIN
docs/assets/syntax_error.png
Normal file
BIN
docs/assets/syntax_error.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
BIN
docs/assets/type_error.png
Normal file
BIN
docs/assets/type_error.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
515
docs/language.md
Normal file
515
docs/language.md
Normal file
@ -0,0 +1,515 @@
|
||||
# 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
|
@ -1,6 +0,0 @@
|
||||
const fs = require('node:fs');
|
||||
const data = fs.readFileSync("examples/assets/jq_data.json");
|
||||
const items = JSON.parse(data);
|
||||
const output = items.map((item) => item:commit:committer:name);
|
||||
|
||||
console.log(output)
|
17
examples/async_commands.ds
Normal file
17
examples/async_commands.ds
Normal file
@ -0,0 +1,17 @@
|
||||
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.'
|
||||
}
|
||||
}
|
@ -1,11 +1,20 @@
|
||||
cast = download("https://api.sampleapis.com/futurama/cast")
|
||||
characters = download("https://api.sampleapis.com/futurama/characters")
|
||||
episodes = download("https://api.sampleapis.com/futurama/episodes")
|
||||
cast_len = 0
|
||||
characters_len = 0
|
||||
episodes_len = 0
|
||||
|
||||
async {
|
||||
cast_len = length(from_json(cast))
|
||||
characters_len = length(from_json(characters))
|
||||
episodes_len = length(from_json(episodes))
|
||||
{
|
||||
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])
|
||||
|
@ -51,4 +51,3 @@ take_turn(cards 'Conservatory' 'Kitchen')
|
||||
take_turn(cards 'White' 'Kitchen')
|
||||
take_turn(cards 'Green' 'Kitchen')
|
||||
take_turn(cards 'Knife' 'Kitchen')
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
cast = (download "https://api.sampleapis.com/futurama/cast")
|
||||
characters = (download "https://api.sampleapis.com/futurama/characters")
|
||||
episodes = (download "https://api.sampleapis.com/futurama/episodes")
|
||||
raw_data = download("https://api.sampleapis.com/futurama/cast")
|
||||
cast_data = from_json(raw_data)
|
||||
|
||||
cast_len = (length (from_json cast))
|
||||
characters_len = (length (from_json characters))
|
||||
episodes_len = (length (from_json episodes))
|
||||
names = []
|
||||
|
||||
(output [cast_len, characters_len, episodes_len])
|
||||
for cast_member in cast_data {
|
||||
names += cast_member:name
|
||||
}
|
||||
|
||||
assert_equal("Billy West", names:0)
|
||||
|
@ -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)
|
@ -1,6 +0,0 @@
|
||||
list = [1 2 3]
|
||||
|
||||
for i in list {
|
||||
i += 1
|
||||
output(i)
|
||||
}
|
26
examples/guessing_game.ds
Normal file
26
examples/guessing_game.ds
Normal file
@ -0,0 +1,26 @@
|
||||
# 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
data = json:parse(fs:read('examples/assets/jq_data.json'))
|
||||
data = json:parse(fs:read_file('examples/assets/jq_data.json'))
|
||||
|
||||
new_data = []
|
||||
|
||||
for commit_data in data {
|
||||
for commit_data in data as collection {
|
||||
new_data += {
|
||||
message = commit_data:commit:message
|
||||
name = commit_data:commit:committer:name
|
||||
|
@ -1,9 +0,0 @@
|
||||
numbers = [1, 2, 3]
|
||||
|
||||
x = numbers:0
|
||||
y = numbers:1
|
||||
z = numbers:2
|
||||
|
||||
assert_equal(x + y, z)
|
||||
|
||||
numbers
|
@ -1,11 +0,0 @@
|
||||
dictionary = {
|
||||
dust = "awesome"
|
||||
answer = 42
|
||||
}
|
||||
|
||||
output(
|
||||
'Dust is '
|
||||
+ dictionary:dust
|
||||
+ '! The answer is '
|
||||
+ dictionary:answer
|
||||
)
|
@ -1,12 +0,0 @@
|
||||
foo_or_bar = match random:boolean() {
|
||||
true => "foo"
|
||||
false => "bar"
|
||||
}
|
||||
|
||||
num = match random:integer() {
|
||||
1 => "one",
|
||||
2 => { "two" },
|
||||
* => "neither",
|
||||
}
|
||||
|
||||
[foo_or_bar, num]
|
@ -1,22 +0,0 @@
|
||||
create_user = (fn email <str>, name <option(str)>) <map> {
|
||||
{
|
||||
email = email
|
||||
username = (either_or name email)
|
||||
}
|
||||
}
|
||||
|
||||
(assert_equal
|
||||
{
|
||||
email = "bob@example.com"
|
||||
username = "bob"
|
||||
},
|
||||
(create_user "bob@example.com" some("bob"))
|
||||
)
|
||||
|
||||
(assert_equal
|
||||
{
|
||||
email = "sue@example.com"
|
||||
username = "sue@example.com"
|
||||
},
|
||||
(create_user "sue@example.com" none)
|
||||
)
|
@ -1,4 +1,4 @@
|
||||
raw_data = fs:read('examples/assets/seaCreatures.json')
|
||||
raw_data = fs:read_file('examples/assets/seaCreatures.json')
|
||||
sea_creatures = json:parse(raw_data)
|
||||
|
||||
data = {
|
||||
|
@ -1,3 +0,0 @@
|
||||
handle = thread:spawn("my_thread", {
|
||||
|
||||
})
|
@ -1,10 +0,0 @@
|
||||
x = 1
|
||||
y = "hello dust!"
|
||||
z = 42.0
|
||||
list = [3, 2, x]
|
||||
big_list = [x, y, z, list]
|
||||
foo = {
|
||||
x = "bar"
|
||||
y = 42
|
||||
z = 0
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
i = 0
|
||||
|
||||
while i < 10 {
|
||||
output(i)
|
||||
i += 1
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
add_one = (numbers <[int]>) <[int]> {
|
||||
new_numbers = []
|
||||
|
||||
for number in numbers {
|
||||
new_numbers += number + 1
|
||||
}
|
||||
|
||||
new_numbers
|
||||
}
|
||||
|
||||
foo = [1, 2, 3] -> add_one
|
||||
|
||||
assert_equal([2 3 4] foo)
|
@ -9,9 +9,27 @@
|
||||
|
||||
hyperfine \
|
||||
--shell none \
|
||||
--parameter-list data_path examples/assets/seaCreatures.json,examples/assets/jq_data.json,dielectron.json \
|
||||
--parameter-list data_path examples/assets/seaCreatures.json \
|
||||
--warmup 3 \
|
||||
"dust -c 'length(from_json(input))' -p {data_path}" \
|
||||
"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'"
|
||||
|
131
src/abstract_tree/as.rs
Normal file
131
src/abstract_tree/as.rs
Normal file
@ -0,0 +1,131 @@
|
||||
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!()
|
||||
}
|
||||
}
|
@ -1,94 +1,106 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{AbstractTree, Error, Identifier, Map, Result, Statement, Type, TypeDefinition, Value};
|
||||
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_definition: Option<TypeDefinition>,
|
||||
type_specification: Option<TypeSpecification>,
|
||||
operator: AssignmentOperator,
|
||||
statement: Statement,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub enum AssignmentOperator {
|
||||
Equal,
|
||||
PlusEqual,
|
||||
MinusEqual,
|
||||
syntax_position: SourcePosition,
|
||||
}
|
||||
|
||||
impl AbstractTree for Assignment {
|
||||
fn from_syntax_node(source: &str, node: Node, context: &Map) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "assignment", node)?;
|
||||
fn from_syntax(
|
||||
syntax_node: SyntaxNode,
|
||||
source: &str,
|
||||
context: &Context,
|
||||
) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node("assignment", syntax_node)?;
|
||||
|
||||
let child_count = node.child_count();
|
||||
let child_count = syntax_node.child_count();
|
||||
|
||||
let identifier_node = node.child(0).unwrap();
|
||||
let identifier = Identifier::from_syntax_node(source, identifier_node, context)?;
|
||||
let identifier_node = syntax_node.child(0).unwrap();
|
||||
let identifier = Identifier::from_syntax(identifier_node, source, context)?;
|
||||
|
||||
let type_node = node.child(1).unwrap();
|
||||
let type_definition = if type_node.kind() == "type_definition" {
|
||||
Some(TypeDefinition::from_syntax_node(
|
||||
source, type_node, 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 = node.child(child_count - 2).unwrap().child(0).unwrap();
|
||||
let operator = match operator_node.kind() {
|
||||
"=" => AssignmentOperator::Equal,
|
||||
"+=" => AssignmentOperator::PlusEqual,
|
||||
"-=" => AssignmentOperator::MinusEqual,
|
||||
_ => {
|
||||
return Err(Error::UnexpectedSyntaxNode {
|
||||
expected: "=, += or -=".to_string(),
|
||||
actual: operator_node.kind().to_string(),
|
||||
location: operator_node.start_position(),
|
||||
relevant_source: source[operator_node.byte_range()].to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
let operator_node = syntax_node.child(child_count - 2).unwrap();
|
||||
let operator = AssignmentOperator::from_syntax(operator_node, source, context)?;
|
||||
|
||||
let statement_node = node.child(child_count - 1).unwrap();
|
||||
let statement = Statement::from_syntax_node(source, statement_node, context)?;
|
||||
let statement_type = statement.expected_type(context)?;
|
||||
|
||||
let variable_key = identifier.inner().clone();
|
||||
let variable_type = if let Some(definition) = &type_definition {
|
||||
definition.inner().clone()
|
||||
} else if let Some((_, r#type)) = context.variables()?.get(identifier.inner()) {
|
||||
r#type.clone()
|
||||
} else {
|
||||
statement_type
|
||||
};
|
||||
|
||||
context.set(variable_key, Value::none(), Some(variable_type))?;
|
||||
let statement_node = syntax_node.child(child_count - 1).unwrap();
|
||||
let statement = Statement::from_syntax(statement_node, source, context)?;
|
||||
|
||||
Ok(Assignment {
|
||||
identifier,
|
||||
type_definition,
|
||||
type_specification,
|
||||
operator,
|
||||
statement,
|
||||
syntax_position: syntax_node.range().into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn check_type(&self, context: &Map) -> Result<()> {
|
||||
let statement_type = self.statement.expected_type(context)?;
|
||||
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)?
|
||||
};
|
||||
|
||||
if let Some(type_definition) = &self.type_definition {
|
||||
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 => {
|
||||
type_definition.inner().check(&statement_type)?;
|
||||
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::List(item_type) = type_definition.inner() {
|
||||
item_type.check(&statement_type)?;
|
||||
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 {
|
||||
type_definition
|
||||
.inner()
|
||||
.check(&self.identifier.expected_type(context)?)?;
|
||||
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!(),
|
||||
@ -97,53 +109,78 @@ impl AbstractTree for Assignment {
|
||||
match self.operator {
|
||||
AssignmentOperator::Equal => {}
|
||||
AssignmentOperator::PlusEqual => {
|
||||
if let Type::List(item_type) = self.identifier.expected_type(context)? {
|
||||
item_type.check(&statement_type)?;
|
||||
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.check_type(context)?;
|
||||
self.statement.validate(source, context)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value> {
|
||||
let key = self.identifier.inner();
|
||||
let value = self.statement.run(source, context)?;
|
||||
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 => {
|
||||
if let Some((mut previous_value, _)) = context.variables()?.get(key).cloned() {
|
||||
previous_value += value;
|
||||
previous_value
|
||||
} else {
|
||||
return Err(Error::VariableIdentifierNotFound(key.clone()));
|
||||
}
|
||||
let left = self.identifier.run(source, context)?;
|
||||
|
||||
left.add(right, self.syntax_position)?
|
||||
}
|
||||
AssignmentOperator::MinusEqual => {
|
||||
if let Some((mut previous_value, _)) = context.variables()?.get(key).cloned() {
|
||||
previous_value -= value;
|
||||
previous_value
|
||||
if let Some(left) = context.get_value(&self.identifier)? {
|
||||
left.subtract(right, self.syntax_position)?
|
||||
} else {
|
||||
return Err(Error::VariableIdentifierNotFound(key.clone()));
|
||||
return Err(RuntimeError::ValidationFailure(
|
||||
ValidationError::VariableIdentifierNotFound(self.identifier.clone()),
|
||||
));
|
||||
}
|
||||
}
|
||||
AssignmentOperator::Equal => value,
|
||||
AssignmentOperator::Equal => right,
|
||||
};
|
||||
|
||||
if let Some(type_defintion) = &self.type_definition {
|
||||
context.set(key.clone(), new_value, Some(type_defintion.inner().clone()))?;
|
||||
} else {
|
||||
context.set(key.clone(), new_value, None)?;
|
||||
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: &Map) -> Result<Type> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
62
src/abstract_tree/assignment_operator.rs
Normal file
62
src/abstract_tree/assignment_operator.rs
Normal file
@ -0,0 +1,62 @@
|
||||
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("-="),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,15 @@
|
||||
use std::sync::RwLock;
|
||||
use std::{
|
||||
fmt::{self, Formatter},
|
||||
sync::RwLock,
|
||||
};
|
||||
|
||||
use rayon::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{AbstractTree, Error, Map, Result, Statement, Type, Value};
|
||||
use crate::{
|
||||
error::{rw_lock_error::RwLockError, RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Context, Format, Statement, SyntaxNode, Type, Value,
|
||||
};
|
||||
|
||||
/// Abstract representation of a block.
|
||||
///
|
||||
@ -14,18 +19,26 @@ use crate::{AbstractTree, Error, Map, Result, Statement, Type, Value};
|
||||
/// 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(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
#[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(source: &str, node: Node, context: &Map) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "block", node)?;
|
||||
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
|
||||
@ -33,12 +46,17 @@ impl AbstractTree for Block {
|
||||
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_node(source, child_node, context)?;
|
||||
let statement = Statement::from_syntax(child_node, source, &block_context)?;
|
||||
|
||||
if statement.is_return() {
|
||||
contains_return = true;
|
||||
}
|
||||
|
||||
statements.push(statement);
|
||||
}
|
||||
@ -46,23 +64,20 @@ impl AbstractTree for Block {
|
||||
|
||||
Ok(Block {
|
||||
is_async,
|
||||
contains_return,
|
||||
statements,
|
||||
})
|
||||
}
|
||||
|
||||
fn check_type(&self, _context: &Map) -> Result<()> {
|
||||
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||
for statement in &self.statements {
|
||||
if let Statement::Return(inner_statement) = statement {
|
||||
return inner_statement.check_type(_context);
|
||||
} else {
|
||||
statement.check_type(_context)?;
|
||||
}
|
||||
statement.validate(_source, _context)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value> {
|
||||
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()));
|
||||
@ -71,17 +86,14 @@ impl AbstractTree for Block {
|
||||
.into_par_iter()
|
||||
.enumerate()
|
||||
.find_map_first(|(index, statement)| {
|
||||
let result = statement.run(source, context);
|
||||
let is_last_statement = index == statements.len() - 1;
|
||||
let is_return_statement = if let Statement::Return(_) = statement {
|
||||
true
|
||||
let result = statement.run(_source, _context);
|
||||
let should_return = if self.contains_return {
|
||||
statement.is_return()
|
||||
} else {
|
||||
false
|
||||
index == statements.len() - 1
|
||||
};
|
||||
|
||||
if is_return_statement || result.is_err() {
|
||||
Some(result)
|
||||
} else if is_last_statement {
|
||||
if should_return {
|
||||
let get_write_lock = final_result.write();
|
||||
|
||||
match get_write_lock {
|
||||
@ -89,41 +101,70 @@ impl AbstractTree for Block {
|
||||
*final_result = result;
|
||||
None
|
||||
}
|
||||
Err(error) => Some(Err(error.into())),
|
||||
Err(_error) => Some(Err(RuntimeError::RwLock(RwLockError))),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(final_result.into_inner()?)
|
||||
.unwrap_or(final_result.into_inner().map_err(|_| RwLockError)?)
|
||||
} else {
|
||||
let mut prev_result = None;
|
||||
|
||||
for statement in &self.statements {
|
||||
if let Statement::Return(inner_statement) = statement {
|
||||
return inner_statement.run(source, context);
|
||||
for (index, statement) in self.statements.iter().enumerate() {
|
||||
if statement.is_return() {
|
||||
return statement.run(_source, _context);
|
||||
}
|
||||
|
||||
prev_result = Some(statement.run(source, context));
|
||||
if index == self.statements.len() - 1 {
|
||||
return statement.run(_source, _context);
|
||||
}
|
||||
}
|
||||
|
||||
prev_result.unwrap_or(Ok(Value::none()))
|
||||
Ok(Value::none())
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type> {
|
||||
if let Some(statement) = self.statements.iter().find(|statement| {
|
||||
if let Statement::Return(_) = statement {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
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);
|
||||
}
|
||||
}) {
|
||||
statement.expected_type(context)
|
||||
} else if let Some(statement) = self.statements.last() {
|
||||
statement.expected_type(context)
|
||||
} else {
|
||||
Ok(Type::None)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -1,167 +0,0 @@
|
||||
use std::{env::args, sync::OnceLock};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{
|
||||
built_in_functions::string_functions, AbstractTree, BuiltInFunction, Function, Identifier,
|
||||
List, Map, Result, Type, TypeDefinition, Value,
|
||||
};
|
||||
|
||||
static ARGS: OnceLock<Value> = OnceLock::new();
|
||||
static FS: OnceLock<Value> = OnceLock::new();
|
||||
static JSON: OnceLock<Value> = OnceLock::new();
|
||||
static RANDOM: OnceLock<Value> = OnceLock::new();
|
||||
static STRING: OnceLock<Value> = OnceLock::new();
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum BuiltInValue {
|
||||
Args,
|
||||
AssertEqual,
|
||||
Fs,
|
||||
Json,
|
||||
Length,
|
||||
Output,
|
||||
Random,
|
||||
String,
|
||||
}
|
||||
|
||||
impl BuiltInValue {
|
||||
fn r#type(&self) -> Type {
|
||||
match self {
|
||||
BuiltInValue::Args => Type::list(Type::String),
|
||||
BuiltInValue::AssertEqual => BuiltInFunction::AssertEqual.r#type(),
|
||||
BuiltInValue::Fs => Type::Map(Vec::new()),
|
||||
BuiltInValue::Json => Type::Map(Vec::new()),
|
||||
BuiltInValue::Length => BuiltInFunction::Length.r#type(),
|
||||
BuiltInValue::Output => BuiltInFunction::Output.r#type(),
|
||||
BuiltInValue::Random => Type::Map(vec![
|
||||
(
|
||||
Identifier::new("boolean".to_string()),
|
||||
TypeDefinition::new(BuiltInFunction::RandomBoolean.r#type()),
|
||||
),
|
||||
(
|
||||
Identifier::new("float".to_string()),
|
||||
TypeDefinition::new(BuiltInFunction::RandomFloat.r#type()),
|
||||
),
|
||||
(
|
||||
Identifier::new("from".to_string()),
|
||||
TypeDefinition::new(BuiltInFunction::RandomFrom.r#type()),
|
||||
),
|
||||
(
|
||||
Identifier::new("integer".to_string()),
|
||||
TypeDefinition::new(BuiltInFunction::RandomInteger.r#type()),
|
||||
),
|
||||
]),
|
||||
BuiltInValue::String => Type::Map(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}),
|
||||
BuiltInValue::AssertEqual => {
|
||||
&Value::Function(Function::BuiltIn(BuiltInFunction::AssertEqual))
|
||||
}
|
||||
BuiltInValue::Fs => FS.get_or_init(|| {
|
||||
let fs_context = Map::new();
|
||||
|
||||
fs_context
|
||||
.set(
|
||||
"read".to_string(),
|
||||
Value::Function(Function::BuiltIn(BuiltInFunction::FsRead)),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Value::Map(fs_context)
|
||||
}),
|
||||
BuiltInValue::Json => JSON.get_or_init(|| {
|
||||
let json_context = Map::new();
|
||||
|
||||
json_context
|
||||
.set(
|
||||
"parse".to_string(),
|
||||
Value::Function(Function::BuiltIn(BuiltInFunction::JsonParse)),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Value::Map(json_context)
|
||||
}),
|
||||
BuiltInValue::Length => &Value::Function(Function::BuiltIn(BuiltInFunction::Length)),
|
||||
BuiltInValue::Output => &Value::Function(Function::BuiltIn(BuiltInFunction::Output)),
|
||||
BuiltInValue::Random => RANDOM.get_or_init(|| {
|
||||
let random_context = Map::new();
|
||||
|
||||
{
|
||||
let mut variables = random_context.variables_mut().unwrap();
|
||||
|
||||
for built_in_function in [
|
||||
BuiltInFunction::RandomBoolean,
|
||||
BuiltInFunction::RandomFloat,
|
||||
BuiltInFunction::RandomFrom,
|
||||
BuiltInFunction::RandomInteger,
|
||||
] {
|
||||
let key = built_in_function.name().to_string();
|
||||
let value = Value::Function(Function::BuiltIn(built_in_function));
|
||||
let r#type = built_in_function.r#type();
|
||||
|
||||
variables.insert(key, (value, r#type));
|
||||
}
|
||||
}
|
||||
|
||||
Value::Map(random_context)
|
||||
}),
|
||||
BuiltInValue::String => STRING.get_or_init(|| {
|
||||
let string_context = Map::new();
|
||||
|
||||
{
|
||||
let mut variables = string_context.variables_mut().unwrap();
|
||||
|
||||
for string_function in string_functions() {
|
||||
let key = string_function.name().to_string();
|
||||
let value = Value::Function(Function::BuiltIn(BuiltInFunction::String(
|
||||
string_function,
|
||||
)));
|
||||
let r#type = string_function.r#type();
|
||||
|
||||
variables.insert(key, (value, r#type));
|
||||
}
|
||||
}
|
||||
|
||||
Value::Map(string_context)
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AbstractTree for BuiltInValue {
|
||||
fn from_syntax_node(_source: &str, node: Node, _context: &Map) -> Result<Self> {
|
||||
let built_in_value = match node.kind() {
|
||||
"args" => BuiltInValue::Args,
|
||||
"assert_equal" => BuiltInValue::AssertEqual,
|
||||
"fs" => BuiltInValue::Fs,
|
||||
"json" => BuiltInValue::Json,
|
||||
"length" => BuiltInValue::Length,
|
||||
"output" => BuiltInValue::Output,
|
||||
"random" => BuiltInValue::Random,
|
||||
"string" => BuiltInValue::String,
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
Ok(built_in_value)
|
||||
}
|
||||
|
||||
fn run(&self, _source: &str, _context: &Map) -> Result<Value> {
|
||||
Ok(self.get().clone())
|
||||
}
|
||||
|
||||
fn expected_type(&self, _context: &Map) -> Result<Type> {
|
||||
Ok(self.r#type())
|
||||
}
|
||||
}
|
76
src/abstract_tree/command.rs
Normal file
76
src/abstract_tree/command.rs
Normal file
@ -0,0 +1,76 @@
|
||||
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!()
|
||||
}
|
||||
}
|
92
src/abstract_tree/enum_defintion.rs
Normal file
92
src/abstract_tree/enum_defintion.rs
Normal file
@ -0,0 +1,92 @@
|
||||
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) = ¤t_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!()
|
||||
}
|
||||
}
|
70
src/abstract_tree/enum_pattern.rs
Normal file
70
src/abstract_tree/enum_pattern.rs
Normal file
@ -0,0 +1,70 @@
|
||||
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!()
|
||||
}
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{
|
||||
value_node::ValueNode, AbstractTree, Error, Identifier, Index, Map, Result, Type, Value, Yield,
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
value_node::ValueNode,
|
||||
AbstractTree, As, Command, Context, Format, FunctionCall, Identifier, Index, Logic, Math,
|
||||
SyntaxNode, Type, Value,
|
||||
};
|
||||
|
||||
use super::{function_call::FunctionCall, logic::Logic, math::Math};
|
||||
|
||||
/// Abstract representation of an expression statement.
|
||||
///
|
||||
/// Unlike statements, which can involve complex logic, an expression is
|
||||
@ -20,12 +20,17 @@ pub enum Expression {
|
||||
Math(Box<Math>),
|
||||
Logic(Box<Logic>),
|
||||
FunctionCall(Box<FunctionCall>),
|
||||
Yield(Box<Yield>),
|
||||
Command(Command),
|
||||
As(Box<As>),
|
||||
}
|
||||
|
||||
impl AbstractTree for Expression {
|
||||
fn from_syntax_node(source: &str, node: Node, context: &Map) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "expression", node)?;
|
||||
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()
|
||||
@ -34,30 +39,24 @@ impl AbstractTree for Expression {
|
||||
};
|
||||
|
||||
let expression = match child.kind() {
|
||||
"value" => Expression::Value(ValueNode::from_syntax_node(source, child, context)?),
|
||||
"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_node(source, child, context)?)
|
||||
Expression::Identifier(Identifier::from_syntax(child, source, _context)?)
|
||||
}
|
||||
"index" => {
|
||||
Expression::Index(Box::new(Index::from_syntax_node(source, child, context)?))
|
||||
}
|
||||
"math" => Expression::Math(Box::new(Math::from_syntax_node(source, child, context)?)),
|
||||
"logic" => {
|
||||
Expression::Logic(Box::new(Logic::from_syntax_node(source, child, context)?))
|
||||
}
|
||||
"function_call" => Expression::FunctionCall(Box::new(FunctionCall::from_syntax_node(
|
||||
source, child, 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,
|
||||
)?)),
|
||||
"yield" => {
|
||||
Expression::Yield(Box::new(Yield::from_syntax_node(source, child, context)?))
|
||||
}
|
||||
"command" => Expression::Command(Command::from_syntax(child, source, _context)?),
|
||||
_ => {
|
||||
return Err(Error::UnexpectedSyntaxNode {
|
||||
expected: "value_node, identifier, index, math, logic, function_call or yield"
|
||||
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||
expected: "value, identifier, index, math, logic, function call, as or command"
|
||||
.to_string(),
|
||||
actual: child.kind().to_string(),
|
||||
location: child.start_position(),
|
||||
relevant_source: source[child.byte_range()].to_string(),
|
||||
position: node.range().into(),
|
||||
})
|
||||
}
|
||||
};
|
||||
@ -65,39 +64,57 @@ impl AbstractTree for Expression {
|
||||
Ok(expression)
|
||||
}
|
||||
|
||||
fn check_type(&self, _context: &Map) -> Result<()> {
|
||||
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||
match self {
|
||||
Expression::Value(value_node) => value_node.check_type(_context),
|
||||
Expression::Identifier(identifier) => identifier.check_type(_context),
|
||||
Expression::Math(math) => math.check_type(_context),
|
||||
Expression::Logic(logic) => logic.check_type(_context),
|
||||
Expression::FunctionCall(function_call) => function_call.check_type(_context),
|
||||
Expression::Index(index) => index.check_type(_context),
|
||||
Expression::Yield(r#yield) => r#yield.check_type(_context),
|
||||
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 run(&self, source: &str, context: &Map) -> Result<Value> {
|
||||
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||
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::Yield(r#yield) => r#yield.run(source, context),
|
||||
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 expected_type(&self, context: &Map) -> Result<Type> {
|
||||
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
|
||||
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::Yield(r#yield) => r#yield.expected_type(context),
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
use rayon::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{AbstractTree, Block, Error, Expression, Identifier, Map, Result, Type, Value};
|
||||
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)]
|
||||
@ -11,70 +14,140 @@ pub struct For {
|
||||
item_id: Identifier,
|
||||
collection: Expression,
|
||||
block: Block,
|
||||
source_position: SourcePosition,
|
||||
|
||||
#[serde(skip)]
|
||||
context: Context,
|
||||
}
|
||||
|
||||
impl AbstractTree for For {
|
||||
fn from_syntax_node(source: &str, node: Node, context: &Map) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "for", node)?;
|
||||
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(Error::UnexpectedSyntaxNode {
|
||||
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||
expected: "for or async for".to_string(),
|
||||
actual: for_node.kind().to_string(),
|
||||
location: for_node.start_position(),
|
||||
relevant_source: source[for_node.byte_range()].to_string(),
|
||||
position: node.range().into(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let identifier_node = node.child(1).unwrap();
|
||||
let identifier = Identifier::from_syntax_node(source, identifier_node, context)?;
|
||||
let identifier = Identifier::from_syntax(identifier_node, source, context)?;
|
||||
|
||||
let expression_node = node.child(3).unwrap();
|
||||
let expression = Expression::from_syntax_node(source, expression_node, context)?;
|
||||
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_node(source, item_node, context)?;
|
||||
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 run(&self, source: &str, context: &Map) -> Result<Value> {
|
||||
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 values = expression_run.as_list()?.items();
|
||||
let key = self.item_id.inner();
|
||||
let key = &self.item_id;
|
||||
|
||||
if self.is_async {
|
||||
values.par_iter().try_for_each(|value| {
|
||||
let iter_context = Map::clone_from(context)?;
|
||||
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)?;
|
||||
}
|
||||
}
|
||||
|
||||
iter_context.set(key.clone(), value.clone(), None)?;
|
||||
return Ok(Value::none());
|
||||
}
|
||||
|
||||
self.block.run(source, &iter_context).map(|_value| ())
|
||||
})?;
|
||||
} else {
|
||||
let loop_context = Map::clone_from(context)?;
|
||||
|
||||
for value in values.iter() {
|
||||
loop_context.set(key.clone(), value.clone(), None)?;
|
||||
|
||||
self.block.run(source, &loop_context)?;
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_type(&self, _context: &Map) -> Result<Type> {
|
||||
Ok(Type::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);
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,41 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{AbstractTree, Error, Expression, FunctionExpression, Map, Result, Type, Value};
|
||||
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 {
|
||||
pub fn new(function_expression: FunctionExpression, arguments: Vec<Expression>) -> Self {
|
||||
/// 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(source: &str, node: Node, context: &Map) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "function_call", node)?;
|
||||
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_node(source, function_node, context)?;
|
||||
let function_expression = FunctionExpression::from_syntax(function_node, source, context)?;
|
||||
|
||||
let mut arguments = Vec::new();
|
||||
|
||||
@ -32,7 +43,7 @@ impl AbstractTree for FunctionCall {
|
||||
let child = node.child(index).unwrap();
|
||||
|
||||
if child.is_named() {
|
||||
let expression = Expression::from_syntax_node(source, child, context)?;
|
||||
let expression = Expression::from_syntax(child, source, context)?;
|
||||
|
||||
arguments.push(expression);
|
||||
}
|
||||
@ -41,84 +52,11 @@ impl AbstractTree for FunctionCall {
|
||||
Ok(FunctionCall {
|
||||
function_expression,
|
||||
arguments,
|
||||
syntax_position: node.range().into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn check_type(&self, context: &Map) -> Result<()> {
|
||||
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(Error::ExpectedFunctionType {
|
||||
actual: function_expression_type,
|
||||
});
|
||||
};
|
||||
|
||||
for (index, expression) in self.arguments.iter().enumerate() {
|
||||
if let Some(r#type) = parameter_types.get(index) {
|
||||
r#type.check(&expression.expected_type(context)?)?;
|
||||
}
|
||||
}
|
||||
|
||||
if self.arguments.len() != parameter_types.len() {
|
||||
return Err(Error::ExpectedFunctionArgumentAmount {
|
||||
source: "TODO".to_string(),
|
||||
expected: parameter_types.len(),
|
||||
actual: self.arguments.len(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value> {
|
||||
let (name, value) = match &self.function_expression {
|
||||
FunctionExpression::Identifier(identifier) => {
|
||||
let key = identifier.inner();
|
||||
let variables = context.variables()?;
|
||||
|
||||
if let Some((value, _)) = variables.get(key) {
|
||||
(Some(key.clone()), value.clone())
|
||||
} else {
|
||||
return Err(Error::FunctionIdentifierNotFound(
|
||||
identifier.inner().clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
FunctionExpression::FunctionCall(function_call) => {
|
||||
(None, function_call.run(source, context)?)
|
||||
}
|
||||
FunctionExpression::Value(value_node) => (None, value_node.run(source, context)?),
|
||||
FunctionExpression::Index(index) => (None, index.run(source, context)?),
|
||||
FunctionExpression::Yield(r#yield) => (None, r#yield.run(source, context)?),
|
||||
};
|
||||
|
||||
let mut arguments = Vec::with_capacity(self.arguments.len());
|
||||
|
||||
for expression in &self.arguments {
|
||||
let value = expression.run(source, context)?;
|
||||
|
||||
arguments.push(value);
|
||||
}
|
||||
|
||||
if let Some(name) = &name {
|
||||
context.set(name.to_string(), value.clone(), None)?;
|
||||
}
|
||||
|
||||
value
|
||||
.as_function()
|
||||
.map_err(|error| {
|
||||
println!("{name:?}");
|
||||
|
||||
error
|
||||
})?
|
||||
.call(name, &arguments, source, context)
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type> {
|
||||
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
|
||||
match &self.function_expression {
|
||||
FunctionExpression::Identifier(identifier) => {
|
||||
let identifier_type = identifier.expected_type(context)?;
|
||||
@ -143,8 +81,122 @@ impl AbstractTree for FunctionCall {
|
||||
Ok(value_type)
|
||||
}
|
||||
}
|
||||
FunctionExpression::Index(index) => index.expected_type(context),
|
||||
FunctionExpression::Yield(r#yield) => r#yield.expected_type(context),
|
||||
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(')');
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{
|
||||
AbstractTree, Error, FunctionCall, Identifier, Index, Map, Result, Type, Value, ValueNode,
|
||||
Yield,
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Context, Format, FunctionCall, Identifier, Index, SyntaxNode, Type, Value,
|
||||
ValueNode,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
@ -12,12 +12,11 @@ pub enum FunctionExpression {
|
||||
FunctionCall(Box<FunctionCall>),
|
||||
Value(ValueNode),
|
||||
Index(Index),
|
||||
Yield(Box<Yield>),
|
||||
}
|
||||
|
||||
impl AbstractTree for FunctionExpression {
|
||||
fn from_syntax_node(source: &str, node: Node, context: &Map) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "function_expression", node)?;
|
||||
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() {
|
||||
@ -27,26 +26,20 @@ impl AbstractTree for FunctionExpression {
|
||||
};
|
||||
|
||||
let function_expression = match child.kind() {
|
||||
"identifier" => FunctionExpression::Identifier(Identifier::from_syntax_node(
|
||||
source, child, context,
|
||||
)?),
|
||||
"identifier" => {
|
||||
FunctionExpression::Identifier(Identifier::from_syntax(child, source, context)?)
|
||||
}
|
||||
|
||||
"function_call" => FunctionExpression::FunctionCall(Box::new(
|
||||
FunctionCall::from_syntax_node(source, child, context)?,
|
||||
FunctionCall::from_syntax(child, source, context)?,
|
||||
)),
|
||||
"value" => {
|
||||
FunctionExpression::Value(ValueNode::from_syntax_node(source, child, context)?)
|
||||
}
|
||||
"index" => FunctionExpression::Index(Index::from_syntax_node(source, child, context)?),
|
||||
"yield" => FunctionExpression::Yield(Box::new(Yield::from_syntax_node(
|
||||
source, child, context,
|
||||
)?)),
|
||||
"value" => FunctionExpression::Value(ValueNode::from_syntax(child, source, context)?),
|
||||
"index" => FunctionExpression::Index(Index::from_syntax(child, source, context)?),
|
||||
_ => {
|
||||
return Err(Error::UnexpectedSyntaxNode {
|
||||
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||
expected: "identifier, function call, value or index".to_string(),
|
||||
actual: child.kind().to_string(),
|
||||
location: child.start_position(),
|
||||
relevant_source: source[child.byte_range()].to_string(),
|
||||
position: node.range().into(),
|
||||
})
|
||||
}
|
||||
};
|
||||
@ -54,23 +47,45 @@ impl AbstractTree for FunctionExpression {
|
||||
Ok(function_expression)
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value> {
|
||||
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),
|
||||
FunctionExpression::Yield(r#yield) => r#yield.run(source, context),
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type> {
|
||||
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),
|
||||
FunctionExpression::Yield(r#yield) => r#yield.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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{
|
||||
AbstractTree, Block, Error, Function, Identifier, Map, Result, Type, TypeDefinition, Value,
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Block, Context, Format, Function, Identifier, SourcePosition, SyntaxNode, Type,
|
||||
TypeSpecification, Value,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
@ -10,17 +13,13 @@ pub struct FunctionNode {
|
||||
parameters: Vec<Identifier>,
|
||||
body: Block,
|
||||
r#type: Type,
|
||||
syntax_position: SourcePosition,
|
||||
|
||||
#[serde(skip)]
|
||||
context: Context,
|
||||
}
|
||||
|
||||
impl FunctionNode {
|
||||
pub fn new(parameters: Vec<Identifier>, body: Block, r#type: Type) -> Self {
|
||||
Self {
|
||||
parameters,
|
||||
body,
|
||||
r#type,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parameters(&self) -> &Vec<Identifier> {
|
||||
&self.parameters
|
||||
}
|
||||
@ -33,6 +32,14 @@ impl FunctionNode {
|
||||
&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 {
|
||||
@ -42,40 +49,11 @@ impl FunctionNode {
|
||||
_ => &Type::None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call(
|
||||
&self,
|
||||
name: Option<String>,
|
||||
arguments: &[Value],
|
||||
source: &str,
|
||||
outer_context: &Map,
|
||||
) -> Result<Value> {
|
||||
let parameter_argument_pairs = self.parameters.iter().zip(arguments.iter());
|
||||
let function_context = Map::clone_from(outer_context)?;
|
||||
|
||||
for (identifier, value) in parameter_argument_pairs {
|
||||
let key = identifier.inner().clone();
|
||||
|
||||
function_context.set(key, value.clone(), None)?;
|
||||
}
|
||||
|
||||
if let Some(name) = name {
|
||||
function_context.set(
|
||||
name,
|
||||
Value::Function(Function::ContextDefined(self.clone())),
|
||||
None,
|
||||
)?;
|
||||
}
|
||||
|
||||
let return_value = self.body.run(source, &function_context)?;
|
||||
|
||||
Ok(return_value)
|
||||
}
|
||||
}
|
||||
|
||||
impl AbstractTree for FunctionNode {
|
||||
fn from_syntax_node(source: &str, node: Node, context: &Map) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "function", node)?;
|
||||
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();
|
||||
@ -85,51 +63,117 @@ impl AbstractTree for FunctionNode {
|
||||
let child = node.child(index).unwrap();
|
||||
|
||||
if child.kind() == "identifier" {
|
||||
let identifier = Identifier::from_syntax_node(source, child, context)?;
|
||||
let identifier = Identifier::from_syntax(child, source, context)?;
|
||||
|
||||
parameters.push(identifier);
|
||||
}
|
||||
|
||||
if child.kind() == "type_definition" {
|
||||
let type_definition = TypeDefinition::from_syntax_node(source, child, context)?;
|
||||
if child.kind() == "type_specification" {
|
||||
let type_specification = TypeSpecification::from_syntax(child, source, context)?;
|
||||
|
||||
parameter_types.push(type_definition.take_inner());
|
||||
parameter_types.push(type_specification.take_inner());
|
||||
}
|
||||
}
|
||||
|
||||
let function_context = Map::clone_from(context)?;
|
||||
|
||||
for (parameter_name, parameter_type) in parameters.iter().zip(parameter_types.iter()) {
|
||||
function_context.set(
|
||||
parameter_name.inner().clone(),
|
||||
Value::none(),
|
||||
Some(parameter_type.clone()),
|
||||
)?;
|
||||
}
|
||||
|
||||
let return_type_node = node.child(child_count - 2).unwrap();
|
||||
let return_type = TypeDefinition::from_syntax_node(source, return_type_node, context)?;
|
||||
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_node(source, body_node, &function_context)?;
|
||||
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::new(parameters, body, r#type))
|
||||
Ok(FunctionNode {
|
||||
parameters,
|
||||
body,
|
||||
r#type,
|
||||
syntax_position,
|
||||
context: function_context,
|
||||
})
|
||||
}
|
||||
|
||||
fn check_type(&self, context: &Map) -> Result<()> {
|
||||
self.return_type()
|
||||
.check(&self.body.expected_type(context)?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&self, _source: &str, _context: &Map) -> Result<Value> {
|
||||
Ok(Value::Function(Function::ContextDefined(self.clone())))
|
||||
}
|
||||
|
||||
fn expected_type(&self, _context: &Map) -> Result<Type> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -1,55 +1,167 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
use std::{
|
||||
fmt::{self, Display, Formatter},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use crate::{AbstractTree, Error, Map, Result, Type, Value};
|
||||
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, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct Identifier(String);
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
||||
pub struct Identifier(Arc<String>);
|
||||
|
||||
impl Identifier {
|
||||
pub fn new(inner: String) -> Self {
|
||||
Identifier(inner)
|
||||
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 take_inner(self) -> String {
|
||||
self.0
|
||||
pub fn from_raw_parts(arc: Arc<String>) -> Self {
|
||||
Identifier(arc)
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> &String {
|
||||
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(source: &str, node: Node, _context: &Map) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "identifier", node)?;
|
||||
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(text.to_string()))
|
||||
Ok(Identifier::new(text))
|
||||
}
|
||||
|
||||
fn run(&self, _source: &str, context: &Map) -> Result<Value> {
|
||||
if let Some((value, _)) = context.variables()?.get(&self.0) {
|
||||
if !value.is_none() {
|
||||
return Ok(value.clone());
|
||||
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(Error::VariableIdentifierNotFound(self.0.clone()))
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type> {
|
||||
if let Some((_value, r#type)) = context.variables()?.get(&self.0) {
|
||||
Ok(r#type.clone())
|
||||
} else {
|
||||
Ok(Type::None)
|
||||
}
|
||||
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)))
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{AbstractTree, Block, Expression, Map, Result, Type, Value};
|
||||
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 {
|
||||
@ -10,15 +12,16 @@ pub struct IfElse {
|
||||
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(source: &str, node: Node, context: &Map) -> Result<Self> {
|
||||
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_node(source, if_expression_node, context)?;
|
||||
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_node(source, if_block_node, context)?;
|
||||
let if_block = Block::from_syntax(if_block_node, source, context)?;
|
||||
|
||||
let child_count = node.child_count();
|
||||
let mut else_if_expressions = Vec::new();
|
||||
@ -30,19 +33,19 @@ impl AbstractTree for IfElse {
|
||||
|
||||
if child.kind() == "else_if" {
|
||||
let expression_node = child.child(1).unwrap();
|
||||
let expression = Expression::from_syntax_node(source, expression_node, context)?;
|
||||
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_node(source, block_node, context)?;
|
||||
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_node(source, else_node, context)?);
|
||||
else_block = Some(Block::from_syntax(else_node, source, context)?);
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,23 +55,71 @@ impl AbstractTree for IfElse {
|
||||
else_if_expressions,
|
||||
else_if_blocks,
|
||||
else_block,
|
||||
source_position: SourcePosition::from(node.range()),
|
||||
})
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value> {
|
||||
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 expressions = &self.else_if_expressions;
|
||||
let else_ifs = self
|
||||
.else_if_expressions
|
||||
.iter()
|
||||
.zip(self.else_if_blocks.iter());
|
||||
|
||||
for (index, expression) in expressions.iter().enumerate() {
|
||||
for (expression, block) in else_ifs {
|
||||
let if_boolean = expression.run(source, context)?.as_boolean()?;
|
||||
|
||||
if if_boolean {
|
||||
let block = self.else_if_blocks.get(index).unwrap();
|
||||
|
||||
return block.run(source, context);
|
||||
}
|
||||
}
|
||||
@ -80,8 +131,30 @@ impl AbstractTree for IfElse {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type> {
|
||||
self.if_block.expected_type(context)
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{AbstractTree, Error, IndexExpression, List, Map, Result, Type, Value};
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Context, Format, Identifier, IndexExpression, SourcePosition, SyntaxNode, Type,
|
||||
Value,
|
||||
};
|
||||
|
||||
/// Abstract representation of an index expression.
|
||||
///
|
||||
@ -10,101 +13,122 @@ use crate::{AbstractTree, Error, IndexExpression, List, Map, Result, Type, Value
|
||||
pub struct Index {
|
||||
pub collection: IndexExpression,
|
||||
pub index: IndexExpression,
|
||||
pub index_end: Option<IndexExpression>,
|
||||
source_position: SourcePosition,
|
||||
}
|
||||
|
||||
impl AbstractTree for Index {
|
||||
fn from_syntax_node(source: &str, node: Node, context: &Map) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "index", node)?;
|
||||
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_node(source, collection_node, context)?;
|
||||
let collection = IndexExpression::from_syntax(collection_node, source, context)?;
|
||||
|
||||
let index_node = node.child(2).unwrap();
|
||||
let index = IndexExpression::from_syntax_node(source, index_node, context)?;
|
||||
|
||||
let index_end_node = node.child(4);
|
||||
let index_end = if let Some(index_end_node) = index_end_node {
|
||||
Some(IndexExpression::from_syntax_node(
|
||||
source,
|
||||
index_end_node,
|
||||
context,
|
||||
)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let index = IndexExpression::from_syntax(index_node, source, context)?;
|
||||
|
||||
Ok(Index {
|
||||
collection,
|
||||
index,
|
||||
index_end,
|
||||
source_position: SourcePosition::from(node.range()),
|
||||
})
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value> {
|
||||
let collection = self.collection.run(source, context)?;
|
||||
|
||||
match collection {
|
||||
Value::List(list) => {
|
||||
let index = self.index.run(source, context)?.as_integer()? as usize;
|
||||
|
||||
let item = if let Some(index_end) = &self.index_end {
|
||||
let index_end = index_end.run(source, context)?.as_integer()? as usize;
|
||||
let sublist = list.items()[index..=index_end].to_vec();
|
||||
|
||||
Value::List(List::with_items(sublist))
|
||||
} else {
|
||||
list.items().get(index).cloned().unwrap_or_default()
|
||||
};
|
||||
|
||||
Ok(item)
|
||||
}
|
||||
Value::Map(map) => {
|
||||
let value = if let IndexExpression::Identifier(identifier) = &self.index {
|
||||
let key = identifier.inner();
|
||||
|
||||
map.variables()?
|
||||
.get(key)
|
||||
.map(|(value, _)| value.clone())
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
let value = self.index.run(source, context)?;
|
||||
let key = value.as_string()?;
|
||||
|
||||
map.variables()?
|
||||
.get(key.as_str())
|
||||
.map(|(value, _)| value.clone())
|
||||
.unwrap_or_default()
|
||||
};
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
Value::String(string) => {
|
||||
let index = self.index.run(source, context)?.as_integer()? as usize;
|
||||
let item = string.read()?.chars().nth(index).unwrap_or_default();
|
||||
|
||||
Ok(Value::string(item.to_string()))
|
||||
}
|
||||
_ => Err(Error::ExpectedCollection { actual: collection }),
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type> {
|
||||
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
|
||||
match self.collection.expected_type(context)? {
|
||||
Type::List(item_type) => Ok(*item_type.clone()),
|
||||
Type::Map(identifier_types) => {
|
||||
if let IndexExpression::Identifier(index_identifier) = &self.index {
|
||||
for (identifier, r#type) in identifier_types {
|
||||
if &identifier == index_identifier {
|
||||
return Ok(r#type.take_inner());
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
Ok(Type::None)
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -1,62 +1,59 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{AbstractTree, Error, Index, IndexExpression, Map, Result, Statement, Type, Value};
|
||||
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,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub enum AssignmentOperator {
|
||||
Equal,
|
||||
PlusEqual,
|
||||
MinusEqual,
|
||||
position: SourcePosition,
|
||||
}
|
||||
|
||||
impl AbstractTree for IndexAssignment {
|
||||
fn from_syntax_node(source: &str, node: Node, context: &Map) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "index_assignment", node)?;
|
||||
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_node(source, index_node, context)?;
|
||||
let index = Index::from_syntax(index_node, source, context)?;
|
||||
|
||||
let operator_node = node.child(1).unwrap().child(0).unwrap();
|
||||
let operator = match operator_node.kind() {
|
||||
"=" => AssignmentOperator::Equal,
|
||||
"+=" => AssignmentOperator::PlusEqual,
|
||||
"-=" => AssignmentOperator::MinusEqual,
|
||||
_ => {
|
||||
return Err(Error::UnexpectedSyntaxNode {
|
||||
expected: "=, += or -=".to_string(),
|
||||
actual: operator_node.kind().to_string(),
|
||||
location: operator_node.start_position(),
|
||||
relevant_source: source[operator_node.byte_range()].to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
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_node(source, statement_node, context)?;
|
||||
let statement = Statement::from_syntax(statement_node, source, context)?;
|
||||
|
||||
Ok(IndexAssignment {
|
||||
index,
|
||||
operator,
|
||||
statement,
|
||||
position: node.range().into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value> {
|
||||
let index_collection = self.index.collection.run(source, context)?;
|
||||
let index_context = index_collection.as_map().unwrap_or(context);
|
||||
let index_key = if let IndexExpression::Identifier(identifier) = &self.index.index {
|
||||
identifier.inner()
|
||||
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 {
|
||||
return Err(Error::VariableIdentifierNotFound(
|
||||
self.index.index.run(source, context)?.to_string(),
|
||||
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),
|
||||
));
|
||||
};
|
||||
|
||||
@ -64,21 +61,17 @@ impl AbstractTree for IndexAssignment {
|
||||
|
||||
let new_value = match self.operator {
|
||||
AssignmentOperator::PlusEqual => {
|
||||
if let Some((mut previous_value, _)) =
|
||||
index_context.variables()?.get(index_key).cloned()
|
||||
{
|
||||
previous_value += value;
|
||||
previous_value
|
||||
if let Some(previous_value) = context.get_value(index_identifier)? {
|
||||
previous_value.add(value, self.position)?
|
||||
} else {
|
||||
Value::none()
|
||||
return Err(RuntimeError::ValidationFailure(
|
||||
ValidationError::VariableIdentifierNotFound(index_identifier.clone()),
|
||||
));
|
||||
}
|
||||
}
|
||||
AssignmentOperator::MinusEqual => {
|
||||
if let Some((mut previous_value, _)) =
|
||||
index_context.variables()?.get(index_key).cloned()
|
||||
{
|
||||
previous_value -= value;
|
||||
previous_value
|
||||
if let Some(previous_value) = context.get_value(index_identifier)? {
|
||||
previous_value.subtract(value, self.position)?
|
||||
} else {
|
||||
Value::none()
|
||||
}
|
||||
@ -86,12 +79,18 @@ impl AbstractTree for IndexAssignment {
|
||||
AssignmentOperator::Equal => value,
|
||||
};
|
||||
|
||||
index_context.set(index_key.clone(), new_value, None)?;
|
||||
context.set_value(index_identifier.clone(), new_value)?;
|
||||
|
||||
Ok(Value::none())
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_type(&self, _context: &Map) -> Result<Type> {
|
||||
Ok(Type::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);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
value_node::ValueNode, AbstractTree, Error, FunctionCall, Identifier, Index, Map, Result, Type,
|
||||
Value,
|
||||
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)]
|
||||
@ -14,8 +15,8 @@ pub enum IndexExpression {
|
||||
}
|
||||
|
||||
impl AbstractTree for IndexExpression {
|
||||
fn from_syntax_node(source: &str, node: tree_sitter::Node, context: &Map) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "index_expression", node)?;
|
||||
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() {
|
||||
@ -25,22 +26,21 @@ impl AbstractTree for IndexExpression {
|
||||
};
|
||||
|
||||
let abstract_node = match child.kind() {
|
||||
"value" => IndexExpression::Value(ValueNode::from_syntax_node(source, child, context)?),
|
||||
"value" => IndexExpression::Value(ValueNode::from_syntax(child, source, context)?),
|
||||
"identifier" => {
|
||||
IndexExpression::Identifier(Identifier::from_syntax_node(source, child, context)?)
|
||||
IndexExpression::Identifier(Identifier::from_syntax(child, source, context)?)
|
||||
}
|
||||
"index" => {
|
||||
IndexExpression::Index(Box::new(Index::from_syntax_node(source, child, context)?))
|
||||
IndexExpression::Index(Box::new(Index::from_syntax(child, source, context)?))
|
||||
}
|
||||
"function_call" => IndexExpression::FunctionCall(Box::new(
|
||||
FunctionCall::from_syntax_node(source, child, context)?,
|
||||
)),
|
||||
"function_call" => IndexExpression::FunctionCall(Box::new(FunctionCall::from_syntax(
|
||||
child, source, context,
|
||||
)?)),
|
||||
_ => {
|
||||
return Err(Error::UnexpectedSyntaxNode {
|
||||
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||
expected: "value, identifier, index or function call".to_string(),
|
||||
actual: child.kind().to_string(),
|
||||
location: child.start_position(),
|
||||
relevant_source: source[child.byte_range()].to_string(),
|
||||
position: node.range().into(),
|
||||
})
|
||||
}
|
||||
};
|
||||
@ -48,16 +48,7 @@ impl AbstractTree for IndexExpression {
|
||||
Ok(abstract_node)
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type> {
|
||||
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),
|
||||
@ -65,4 +56,43 @@ impl AbstractTree for IndexExpression {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{AbstractTree, Error, Expression, Map, Result, Type, Value};
|
||||
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)]
|
||||
@ -12,45 +14,24 @@ pub struct Logic {
|
||||
}
|
||||
|
||||
impl AbstractTree for Logic {
|
||||
fn from_syntax_node(source: &str, node: Node, context: &Map) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "logic", node)?;
|
||||
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().child(0).unwrap(),
|
||||
node.child(2).unwrap(),
|
||||
)
|
||||
(first_node, node.child(1).unwrap(), node.child(2).unwrap())
|
||||
} else {
|
||||
(
|
||||
node.child(1).unwrap(),
|
||||
node.child(2).unwrap().child(0).unwrap(),
|
||||
node.child(2).unwrap(),
|
||||
node.child(3).unwrap(),
|
||||
)
|
||||
}
|
||||
};
|
||||
let left = Expression::from_syntax_node(source, left_node, context)?;
|
||||
let operator = match operator_node.kind() {
|
||||
"==" => LogicOperator::Equal,
|
||||
"!=" => LogicOperator::NotEqual,
|
||||
"&&" => LogicOperator::And,
|
||||
"||" => LogicOperator::Or,
|
||||
">" => LogicOperator::Greater,
|
||||
"<" => LogicOperator::Less,
|
||||
">=" => LogicOperator::GreaterOrEqual,
|
||||
"<=" => LogicOperator::LessOrEqaul,
|
||||
_ => {
|
||||
return Err(Error::UnexpectedSyntaxNode {
|
||||
expected: "==, !=, &&, ||, >, <, >= or <=".to_string(),
|
||||
actual: operator_node.kind().to_string(),
|
||||
location: operator_node.start_position(),
|
||||
relevant_source: source[operator_node.byte_range()].to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
let right = Expression::from_syntax_node(source, right_node, context)?;
|
||||
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,
|
||||
@ -59,9 +40,23 @@ impl AbstractTree for Logic {
|
||||
})
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value> {
|
||||
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()) {
|
||||
@ -82,25 +77,19 @@ impl AbstractTree for Logic {
|
||||
LogicOperator::Greater => left > right,
|
||||
LogicOperator::Less => left < right,
|
||||
LogicOperator::GreaterOrEqual => left >= right,
|
||||
LogicOperator::LessOrEqaul => left <= right,
|
||||
LogicOperator::LessOrEqual => left <= right,
|
||||
};
|
||||
|
||||
Ok(Value::Boolean(result))
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_type(&self, _context: &Map) -> Result<Type> {
|
||||
Ok(Type::Boolean)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub enum LogicOperator {
|
||||
Equal,
|
||||
NotEqual,
|
||||
And,
|
||||
Or,
|
||||
Greater,
|
||||
Less,
|
||||
GreaterOrEqual,
|
||||
LessOrEqaul,
|
||||
}
|
||||
|
93
src/abstract_tree/logic_operator.rs
Normal file
93
src/abstract_tree/logic_operator.rs
Normal file
@ -0,0 +1,93 @@
|
||||
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, "<="),
|
||||
}
|
||||
}
|
||||
}
|
117
src/abstract_tree/map_node.rs
Normal file
117
src/abstract_tree/map_node.rs
Normal file
@ -0,0 +1,117 @@
|
||||
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) = ¤t_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!()
|
||||
}
|
||||
}
|
@ -2,50 +2,51 @@
|
||||
//!
|
||||
//! Note that this module is called "match" but is escaped as "r#match" because
|
||||
//! "match" is a keyword in Rust.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
use tree_sitter::Node as SyntaxNode;
|
||||
|
||||
use crate::{AbstractTree, Error, Expression, Map, Result, Statement, Type, Value};
|
||||
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<(Expression, Statement)>,
|
||||
options: Vec<(MatchPattern, Statement)>,
|
||||
fallback: Option<Box<Statement>>,
|
||||
|
||||
#[serde(skip)]
|
||||
context: Context,
|
||||
}
|
||||
|
||||
impl AbstractTree for Match {
|
||||
fn from_syntax_node(source: &str, node: Node, context: &Map) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "match", node)?;
|
||||
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_node(source, matcher_node, context)?;
|
||||
let matcher = Expression::from_syntax(matcher_node, source, context)?;
|
||||
|
||||
let mut options = Vec::new();
|
||||
let mut previous_expression = None;
|
||||
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() == "*" {
|
||||
next_statement_is_fallback = true;
|
||||
}
|
||||
|
||||
if child.kind() == "expression" {
|
||||
previous_expression = Some(Expression::from_syntax_node(source, child, context)?);
|
||||
if child.kind() == "match_pattern" {
|
||||
previous_pattern = Some(MatchPattern::from_syntax(child, source, context)?);
|
||||
}
|
||||
|
||||
if child.kind() == "statement" {
|
||||
let statement = Statement::from_syntax_node(source, child, context)?;
|
||||
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_expression {
|
||||
} else if let Some(expression) = &previous_pattern {
|
||||
options.push((expression.clone(), statement));
|
||||
}
|
||||
}
|
||||
@ -55,16 +56,61 @@ impl AbstractTree for Match {
|
||||
matcher,
|
||||
options,
|
||||
fallback,
|
||||
context: Context::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value> {
|
||||
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 (expression, statement) in &self.options {
|
||||
let option_value = expression.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 matcher_value == option_value {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -75,10 +121,25 @@ impl AbstractTree for Match {
|
||||
Ok(Value::none())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type> {
|
||||
let (_, first_statement) = self.options.first().unwrap();
|
||||
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(" {");
|
||||
|
||||
first_statement.expected_type(context)
|
||||
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('}');
|
||||
}
|
||||
}
|
||||
|
64
src/abstract_tree/match_pattern.rs
Normal file
64
src/abstract_tree/match_pattern.rs
Normal file
@ -0,0 +1,64 @@
|
||||
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!()
|
||||
}
|
||||
}
|
@ -1,7 +1,10 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{AbstractTree, Error, Expression, Map, Result, Type, Value};
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Context, Expression, Format, MathOperator, SourcePosition, SyntaxNode, Type,
|
||||
Value,
|
||||
};
|
||||
|
||||
/// Abstract representation of a math operation.
|
||||
///
|
||||
@ -12,66 +15,60 @@ pub struct Math {
|
||||
left: Expression,
|
||||
operator: MathOperator,
|
||||
right: Expression,
|
||||
position: SourcePosition,
|
||||
}
|
||||
|
||||
impl AbstractTree for Math {
|
||||
fn from_syntax_node(source: &str, node: Node, context: &Map) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "math", node)?;
|
||||
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_node(source, left_node, context)?;
|
||||
let left = Expression::from_syntax(left_node, source, context)?;
|
||||
|
||||
let operator_node = node.child(1).unwrap().child(0).unwrap();
|
||||
let operator = match operator_node.kind() {
|
||||
"+" => MathOperator::Add,
|
||||
"-" => MathOperator::Subtract,
|
||||
"*" => MathOperator::Multiply,
|
||||
"/" => MathOperator::Divide,
|
||||
"%" => MathOperator::Modulo,
|
||||
_ => {
|
||||
return Err(Error::UnexpectedSyntaxNode {
|
||||
expected: "+, -, *, / or %".to_string(),
|
||||
actual: operator_node.kind().to_string(),
|
||||
location: operator_node.start_position(),
|
||||
relevant_source: source[operator_node.byte_range()].to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
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_node(source, right_node, context)?;
|
||||
let right = Expression::from_syntax(right_node, source, context)?;
|
||||
|
||||
Ok(Math {
|
||||
left,
|
||||
operator,
|
||||
right,
|
||||
position: node.range().into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value> {
|
||||
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 + right,
|
||||
MathOperator::Subtract => left - right,
|
||||
MathOperator::Multiply => left * right,
|
||||
MathOperator::Divide => left / right,
|
||||
MathOperator::Modulo => left % right,
|
||||
}?;
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type> {
|
||||
self.left.expected_type(context)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub enum MathOperator {
|
||||
Add,
|
||||
Subtract,
|
||||
Multiply,
|
||||
Divide,
|
||||
Modulo,
|
||||
}
|
||||
|
69
src/abstract_tree/math_operator.rs
Normal file
69
src/abstract_tree/math_operator.rs
Normal file
@ -0,0 +1,69 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,14 +1,13 @@
|
||||
//! Abstract, executable representations of corresponding items found in Dust
|
||||
//! source code. The types that implement [AbstractTree] are inteded to be
|
||||
//! created by an [Evaluator].
|
||||
//!
|
||||
//! When adding new lanugage features, first extend the grammar to recognize new
|
||||
//! syntax nodes. Then add a new AbstractTree type using the existing types as
|
||||
//! examples.
|
||||
|
||||
//! created by an [Interpreter].
|
||||
pub mod r#as;
|
||||
pub mod assignment;
|
||||
pub mod assignment_operator;
|
||||
pub mod block;
|
||||
pub mod built_in_value;
|
||||
pub mod command;
|
||||
pub mod enum_defintion;
|
||||
pub mod enum_pattern;
|
||||
pub mod expression;
|
||||
pub mod r#for;
|
||||
pub mod function_call;
|
||||
@ -20,39 +19,80 @@ 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 mod r#yield;
|
||||
|
||||
pub use {
|
||||
assignment::*, block::*, built_in_value::*, expression::*, function_call::*,
|
||||
function_expression::*, function_node::*, identifier::*, if_else::*, index::*,
|
||||
index_assignment::IndexAssignment, index_expression::*, logic::*, math::*, r#for::*,
|
||||
r#match::*, r#while::*, r#yield::*, statement::*, type_definition::*, value_node::*,
|
||||
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 tree_sitter::Node;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Error, Map, Result, Value};
|
||||
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(source: &str, node: Node, context: &Map) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "root", node)?;
|
||||
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_node(source, statement_node, context)?;
|
||||
let statement = Statement::from_syntax(statement_node, source, context)?;
|
||||
|
||||
statements.push(statement);
|
||||
}
|
||||
@ -60,41 +100,50 @@ impl AbstractTree for Root {
|
||||
Ok(Root { statements })
|
||||
}
|
||||
|
||||
fn check_type(&self, _context: &Map) -> Result<()> {
|
||||
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||
for statement in &self.statements {
|
||||
if let Statement::Return(inner_statement) = statement {
|
||||
return inner_statement.check_type(_context);
|
||||
} else {
|
||||
statement.check_type(_context)?;
|
||||
}
|
||||
statement.validate(_source, _context)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value> {
|
||||
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
||||
let mut value = Value::none();
|
||||
|
||||
for statement in &self.statements {
|
||||
if let Statement::Return(inner_statement) = statement {
|
||||
return inner_statement.run(source, context);
|
||||
} else {
|
||||
value = statement.run(source, context)?;
|
||||
value = statement.run(source, context)?;
|
||||
|
||||
if statement.is_return() {
|
||||
return Ok(value);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type> {
|
||||
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 {
|
||||
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
|
||||
@ -103,15 +152,27 @@ pub trait AbstractTree: Sized {
|
||||
///
|
||||
/// If necessary, the source code can be accessed directly by getting the
|
||||
/// node's byte range.
|
||||
fn from_syntax_node(source: &str, node: Node, context: &Map) -> Result<Self>;
|
||||
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError>;
|
||||
|
||||
/// Verify the type integrity of the node.
|
||||
fn check_type(&self, _context: &Map) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
/// 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>;
|
||||
|
||||
/// Execute dust code by traversing the tree.
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value>;
|
||||
/// 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>;
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type>;
|
||||
/// 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(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
45
src/abstract_tree/new.rs
Normal file
45
src/abstract_tree/new.rs
Normal file
@ -0,0 +1,45 @@
|
||||
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!()
|
||||
}
|
||||
}
|
@ -1,110 +1,202 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{
|
||||
AbstractTree, Assignment, Block, Error, Expression, For, IfElse, IndexAssignment, Map, Match,
|
||||
Result, Type, Value, While,
|
||||
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 enum Statement {
|
||||
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>),
|
||||
Return(Box<Statement>),
|
||||
For(Box<For>),
|
||||
IndexAssignment(Box<IndexAssignment>),
|
||||
TypeDefinition(TypeDefinition),
|
||||
}
|
||||
|
||||
impl AbstractTree for Statement {
|
||||
fn from_syntax_node(source: &str, node: Node, context: &Map) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "statement", node)?;
|
||||
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(Statement::Assignment(Box::new(
|
||||
Assignment::from_syntax_node(source, child, context)?,
|
||||
"assignment" => Ok(StatementKind::Assignment(Box::new(
|
||||
Assignment::from_syntax(child, source, context)?,
|
||||
))),
|
||||
"expression" => Ok(Statement::Expression(Expression::from_syntax_node(
|
||||
source, child, context,
|
||||
"expression" => Ok(StatementKind::Expression(Expression::from_syntax(
|
||||
child, source, context,
|
||||
)?)),
|
||||
"if_else" => Ok(Statement::IfElse(Box::new(IfElse::from_syntax_node(
|
||||
source, child, context,
|
||||
"if_else" => Ok(StatementKind::IfElse(Box::new(IfElse::from_syntax(
|
||||
child, source, context,
|
||||
)?))),
|
||||
"while" => Ok(Statement::While(Box::new(While::from_syntax_node(
|
||||
source, child, context,
|
||||
"while" => Ok(StatementKind::While(Box::new(While::from_syntax(
|
||||
child, source, context,
|
||||
)?))),
|
||||
"block" => Ok(Statement::Block(Box::new(Block::from_syntax_node(
|
||||
source, child, context,
|
||||
"block" => Ok(StatementKind::Block(Box::new(Block::from_syntax(
|
||||
child, source, context,
|
||||
)?))),
|
||||
"for" => Ok(Statement::For(Box::new(For::from_syntax_node(
|
||||
source, child, context,
|
||||
"for" => Ok(StatementKind::For(Box::new(For::from_syntax(
|
||||
child, source, context,
|
||||
)?))),
|
||||
"index_assignment" => Ok(Statement::IndexAssignment(Box::new(
|
||||
IndexAssignment::from_syntax_node(source, child, context)?,
|
||||
"index_assignment" => Ok(StatementKind::IndexAssignment(Box::new(
|
||||
IndexAssignment::from_syntax(child, source, context)?,
|
||||
))),
|
||||
"match" => Ok(Statement::Match(Match::from_syntax_node(
|
||||
source, child, context,
|
||||
"match" => Ok(StatementKind::Match(Match::from_syntax(
|
||||
child, source, context,
|
||||
)?)),
|
||||
"return" => {
|
||||
let statement_node = child.child(1).unwrap();
|
||||
|
||||
Ok(Statement::Return(Box::new(Statement::from_syntax_node(source, statement_node, context)?)))
|
||||
},
|
||||
_ => Err(Error::UnexpectedSyntaxNode {
|
||||
"type_definition" => Ok(StatementKind::TypeDefinition(TypeDefinition::from_syntax(
|
||||
child, source, context
|
||||
)?)),
|
||||
_ => Err(SyntaxError::UnexpectedSyntaxNode {
|
||||
expected:
|
||||
"assignment, index assignment, expression, block, return, if...else, while, for or match".to_string(),
|
||||
"assignment, index assignment, expression, type_definition, block, return, if...else, while, for or match".to_string(),
|
||||
actual: child.kind().to_string(),
|
||||
location: child.start_position(),
|
||||
relevant_source: source[child.byte_range()].to_string(),
|
||||
position: node.range().into(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_type(&self, _context: &Map) -> Result<()> {
|
||||
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||
match self {
|
||||
Statement::Assignment(assignment) => assignment.check_type(_context),
|
||||
Statement::Expression(expression) => expression.check_type(_context),
|
||||
Statement::IfElse(if_else) => if_else.check_type(_context),
|
||||
Statement::Match(r#match) => r#match.check_type(_context),
|
||||
Statement::While(r#while) => r#while.check_type(_context),
|
||||
Statement::Block(block) => block.check_type(_context),
|
||||
Statement::For(r#for) => r#for.check_type(_context),
|
||||
Statement::IndexAssignment(index_assignment) => index_assignment.check_type(_context),
|
||||
Statement::Return(statement) => statement.check_type(_context),
|
||||
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 run(&self, source: &str, context: &Map) -> Result<Value> {
|
||||
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||
match self {
|
||||
Statement::Assignment(assignment) => assignment.run(source, context),
|
||||
Statement::Expression(expression) => expression.run(source, context),
|
||||
Statement::IfElse(if_else) => if_else.run(source, context),
|
||||
Statement::Match(r#match) => r#match.run(source, context),
|
||||
Statement::While(r#while) => r#while.run(source, context),
|
||||
Statement::Block(block) => block.run(source, context),
|
||||
Statement::For(r#for) => r#for.run(source, context),
|
||||
Statement::IndexAssignment(index_assignment) => index_assignment.run(source, context),
|
||||
Statement::Return(statement) => statement.run(source, context),
|
||||
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 expected_type(&self, context: &Map) -> Result<Type> {
|
||||
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
|
||||
match self {
|
||||
Statement::Assignment(assignment) => assignment.expected_type(context),
|
||||
Statement::Expression(expression) => expression.expected_type(context),
|
||||
Statement::IfElse(if_else) => if_else.expected_type(context),
|
||||
Statement::Match(r#match) => r#match.expected_type(context),
|
||||
Statement::While(r#while) => r#while.expected_type(context),
|
||||
Statement::Block(block) => block.expected_type(context),
|
||||
Statement::For(r#for) => r#for.expected_type(context),
|
||||
Statement::IndexAssignment(index_assignment) => index_assignment.expected_type(context),
|
||||
Statement::Return(statement) => statement.expected_type(context),
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
120
src/abstract_tree/struct_definition.rs
Normal file
120
src/abstract_tree/struct_definition.rs
Normal file
@ -0,0 +1,120 @@
|
||||
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)) = (¤t_identifier, ¤t_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) = ¤t_identifier {
|
||||
let r#type = if let Some(r#type) = ¤t_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!()
|
||||
}
|
||||
}
|
394
src/abstract_tree/type.rs
Normal file
394
src/abstract_tree/type.rs
Normal file
@ -0,0 +1,394 @@
|
||||
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!(),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,329 +1,73 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
use tree_sitter::Node as SyntaxNode;
|
||||
|
||||
use crate::{AbstractTree, Error, Identifier, Map, Result, Value};
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Context, EnumDefinition, Format, Identifier, StructDefinition, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct TypeDefinition {
|
||||
r#type: Type,
|
||||
pub enum TypeDefinition {
|
||||
Enum(EnumDefinition),
|
||||
Struct(StructDefinition),
|
||||
}
|
||||
|
||||
impl TypeDefinition {
|
||||
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
|
||||
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(source: &str, node: Node, context: &Map) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "type_definition", node)?;
|
||||
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node("type_definition", node)?;
|
||||
|
||||
let type_node = node.child(1).unwrap();
|
||||
let r#type = Type::from_syntax_node(source, type_node, context)?;
|
||||
let child = node.child(0).unwrap();
|
||||
|
||||
Ok(TypeDefinition { r#type })
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value> {
|
||||
self.r#type.run(source, context)
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type> {
|
||||
self.r#type.expected_type(context)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TypeDefinition {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "<{}>", self.r#type)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub enum Type {
|
||||
Any,
|
||||
Boolean,
|
||||
Collection,
|
||||
Float,
|
||||
Function {
|
||||
parameter_types: Vec<Type>,
|
||||
return_type: Box<Type>,
|
||||
},
|
||||
Integer,
|
||||
List(Box<Type>),
|
||||
Map(Vec<(Identifier, TypeDefinition)>),
|
||||
None,
|
||||
Number,
|
||||
String,
|
||||
Option(Box<Type>),
|
||||
}
|
||||
|
||||
impl Type {
|
||||
pub fn list(item_type: Type) -> Self {
|
||||
Type::List(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),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn option(optional_type: Type) -> Self {
|
||||
Type::Option(Box::new(optional_type))
|
||||
}
|
||||
|
||||
pub fn check(&self, other: &Type) -> Result<()> {
|
||||
match (self, other) {
|
||||
(Type::Any, _)
|
||||
| (_, Type::Any)
|
||||
| (Type::Boolean, Type::Boolean)
|
||||
| (Type::Collection, Type::Collection)
|
||||
| (Type::Collection, Type::List(_))
|
||||
| (Type::List(_), Type::Collection)
|
||||
| (Type::Collection, Type::Map(_))
|
||||
| (Type::Map(_), Type::Collection)
|
||||
| (Type::Collection, Type::String)
|
||||
| (Type::String, Type::Collection)
|
||||
| (Type::Float, Type::Float)
|
||||
| (Type::Integer, Type::Integer)
|
||||
| (Type::Number, Type::Number)
|
||||
| (Type::Number, Type::Integer)
|
||||
| (Type::Number, Type::Float)
|
||||
| (Type::Integer, Type::Number)
|
||||
| (Type::Float, Type::Number)
|
||||
| (Type::None, Type::None)
|
||||
| (Type::String, Type::String) => Ok(()),
|
||||
(Type::Map(left), Type::Map(right)) => {
|
||||
if left == right {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::TypeCheck {
|
||||
expected: self.clone(),
|
||||
actual: other.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
(Type::Option(left), Type::Option(right)) => {
|
||||
if left == right {
|
||||
Ok(())
|
||||
} else if let Type::Any = left.as_ref() {
|
||||
Ok(())
|
||||
} else if let Type::Any = right.as_ref() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::TypeCheck {
|
||||
expected: self.clone(),
|
||||
actual: other.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
(Type::Option(_), Type::None) | (Type::None, Type::Option(_)) => Ok(()),
|
||||
(Type::List(self_item_type), Type::List(other_item_type)) => {
|
||||
if self_item_type.check(other_item_type).is_err() {
|
||||
Err(Error::TypeCheck {
|
||||
expected: self.clone(),
|
||||
actual: other.clone(),
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
(
|
||||
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.check(other_parameter_type).is_err() {
|
||||
return Err(Error::TypeCheck {
|
||||
expected: self.clone(),
|
||||
actual: other.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if self_return_type.check(other_return_type).is_err() {
|
||||
Err(Error::TypeCheck {
|
||||
expected: self.clone(),
|
||||
actual: other.clone(),
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
_ => Err(Error::TypeCheck {
|
||||
expected: self.clone(),
|
||||
actual: other.clone(),
|
||||
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(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AbstractTree for Type {
|
||||
fn from_syntax_node(_source: &str, node: Node, _context: &Map) -> Result<Self> {
|
||||
Error::expect_syntax_node(_source, "type", node)?;
|
||||
|
||||
let type_node = node.child(0).unwrap();
|
||||
|
||||
let r#type = match type_node.kind() {
|
||||
"[" => {
|
||||
let item_type_node = node.child(1).unwrap();
|
||||
let item_type = Type::from_syntax_node(_source, item_type_node, _context)?;
|
||||
|
||||
Type::List(Box::new(item_type))
|
||||
}
|
||||
"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_node(_source, child, _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_node(_source, final_node, _context)?
|
||||
} else {
|
||||
Type::None
|
||||
};
|
||||
|
||||
Type::Function {
|
||||
parameter_types,
|
||||
return_type: Box::new(return_type),
|
||||
}
|
||||
}
|
||||
"int" => Type::Integer,
|
||||
|
||||
"num" => Type::Number,
|
||||
"none" => Type::None,
|
||||
"str" => Type::String,
|
||||
"{" => {
|
||||
let child_count = node.child_count();
|
||||
let mut identifier_types = Vec::new();
|
||||
let mut identifier = None;
|
||||
|
||||
for index in 1..child_count - 1 {
|
||||
let child = node.child(index).unwrap();
|
||||
|
||||
match child.kind() {
|
||||
"identifier" => {
|
||||
identifier =
|
||||
Some(Identifier::from_syntax_node(_source, child, _context)?);
|
||||
}
|
||||
"type_definition" => {
|
||||
if let Some(identifier) = &identifier {
|
||||
let type_definition =
|
||||
TypeDefinition::from_syntax_node(_source, child, _context)?;
|
||||
|
||||
identifier_types.push((identifier.clone(), type_definition));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Type::Map(identifier_types)
|
||||
}
|
||||
"option" => {
|
||||
let inner_type_node = node.child(2).unwrap();
|
||||
let inner_type = Type::from_syntax_node(_source, inner_type_node, _context)?;
|
||||
|
||||
Type::Option(Box::new(inner_type))
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::UnexpectedSyntaxNode {
|
||||
expected: "any, bool, float, int, num, str, option, (, [ or {".to_string(),
|
||||
actual: type_node.kind().to_string(),
|
||||
location: type_node.start_position(),
|
||||
relevant_source: _source[type_node.byte_range()].to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Ok(r#type)
|
||||
}
|
||||
|
||||
fn run(&self, _source: &str, _context: &Map) -> Result<Value> {
|
||||
Ok(Value::none())
|
||||
}
|
||||
|
||||
fn expected_type(&self, _context: &Map) -> Result<Type> {
|
||||
Ok(Type::None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Type {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||
match self {
|
||||
Type::Any => write!(f, "any"),
|
||||
Type::Boolean => write!(f, "bool"),
|
||||
Type::Collection => write!(f, "collection"),
|
||||
Type::Float => write!(f, "float"),
|
||||
Type::Function {
|
||||
parameter_types,
|
||||
return_type,
|
||||
} => {
|
||||
write!(f, "(")?;
|
||||
TypeDefinition::Enum(enum_definition) => enum_definition.expected_type(_context),
|
||||
TypeDefinition::Struct(struct_definition) => struct_definition.expected_type(_context),
|
||||
}
|
||||
}
|
||||
|
||||
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(item_type) => write!(f, "[{item_type}]"),
|
||||
Type::Map(identifier_types) => {
|
||||
write!(f, "{{")?;
|
||||
|
||||
for (identifier, r#type) in identifier_types {
|
||||
write!(f, "{} {}", identifier.inner(), r#type)?;
|
||||
}
|
||||
|
||||
write!(f, "}}")
|
||||
}
|
||||
Type::Number => write!(f, "num"),
|
||||
Type::None => write!(f, "none"),
|
||||
Type::String => write!(f, "str"),
|
||||
Type::Option(inner_type) => {
|
||||
write!(f, "option({})", inner_type)
|
||||
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!()
|
||||
}
|
||||
}
|
||||
|
56
src/abstract_tree/type_specification.rs
Normal file
56
src/abstract_tree/type_specification.rs
Normal file
@ -0,0 +1,56 @@
|
||||
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('>');
|
||||
}
|
||||
}
|
@ -1,14 +1,15 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::{cmp::Ordering, ops::RangeInclusive};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
use tree_sitter::Node as SyntaxNode;
|
||||
|
||||
use crate::{
|
||||
AbstractTree, BuiltInValue, Error, Expression, Function, Identifier, List, Map, Result,
|
||||
Statement, Type, TypeDefinition, Value,
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Context, Expression, Format, Function, FunctionNode,
|
||||
Identifier, List, Type, Value, TypeDefinition, MapNode,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub enum ValueNode {
|
||||
Boolean(String),
|
||||
Float(String),
|
||||
@ -16,20 +17,32 @@ pub enum ValueNode {
|
||||
Integer(String),
|
||||
String(String),
|
||||
List(Vec<Expression>),
|
||||
Option(Option<Box<Expression>>),
|
||||
Map(BTreeMap<String, (Statement, Option<Type>)>),
|
||||
BuiltInValue(BuiltInValue),
|
||||
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(source: &str, node: Node, context: &Map) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "value", node)?;
|
||||
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" => ValueNode::Function(Function::from_syntax_node(source, child, context)?),
|
||||
"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;
|
||||
@ -43,8 +56,8 @@ impl AbstractTree for ValueNode {
|
||||
let current_node = child.child(index).unwrap();
|
||||
|
||||
if current_node.is_named() {
|
||||
let expression =
|
||||
Expression::from_syntax_node(source, current_node, context)?;
|
||||
let expression = Expression::from_syntax(current_node, source, context)?;
|
||||
|
||||
expressions.push(expression);
|
||||
}
|
||||
}
|
||||
@ -52,69 +65,50 @@ impl AbstractTree for ValueNode {
|
||||
ValueNode::List(expressions)
|
||||
}
|
||||
"map" => {
|
||||
let mut child_nodes = BTreeMap::new();
|
||||
let mut current_key = "".to_string();
|
||||
let mut current_type = None;
|
||||
|
||||
for index in 0..child.child_count() - 1 {
|
||||
let child_syntax_node = child.child(index).unwrap();
|
||||
|
||||
if child_syntax_node.kind() == "identifier" {
|
||||
current_key =
|
||||
Identifier::from_syntax_node(source, child_syntax_node, context)?
|
||||
.take_inner();
|
||||
current_type = None;
|
||||
}
|
||||
|
||||
if child_syntax_node.kind() == "type_definition" {
|
||||
current_type = Some(
|
||||
TypeDefinition::from_syntax_node(source, child_syntax_node, context)?
|
||||
.take_inner(),
|
||||
);
|
||||
}
|
||||
|
||||
if child_syntax_node.kind() == "statement" {
|
||||
let statement =
|
||||
Statement::from_syntax_node(source, child_syntax_node, context)?;
|
||||
|
||||
if let Some(type_definition) = ¤t_type {
|
||||
type_definition.check(&statement.expected_type(context)?)?;
|
||||
}
|
||||
|
||||
child_nodes.insert(current_key.clone(), (statement, current_type.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
ValueNode::Map(child_nodes)
|
||||
ValueNode::Map(MapNode::from_syntax(child, source, context)?)
|
||||
}
|
||||
"option" => {
|
||||
let first_grandchild = child.child(0).unwrap();
|
||||
"range" => {
|
||||
let mut split = source[child.byte_range()].split("..");
|
||||
let start = split.next().unwrap().parse().unwrap();
|
||||
let end = split.next().unwrap().parse().unwrap();
|
||||
|
||||
if first_grandchild.kind() == "none" {
|
||||
ValueNode::Option(None)
|
||||
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 {
|
||||
let expression_node = child.child(2).unwrap();
|
||||
let expression =
|
||||
Expression::from_syntax_node(source, expression_node, context)?;
|
||||
None
|
||||
};
|
||||
|
||||
ValueNode::Option(Some(Box::new(expression)))
|
||||
}
|
||||
ValueNode::Enum { name, variant , expression }
|
||||
}
|
||||
"built_in_value" => {
|
||||
let built_in_value_node = child.child(0).unwrap();
|
||||
"struct_instance" => {
|
||||
let name_node = child.child(0).unwrap();
|
||||
let name = Identifier::from_syntax(name_node, source, context)?;
|
||||
|
||||
ValueNode::BuiltInValue(BuiltInValue::from_syntax_node(
|
||||
source,
|
||||
built_in_value_node,
|
||||
context,
|
||||
)?)
|
||||
let properties_node = child.child(2).unwrap();
|
||||
let properties = MapNode::from_syntax(properties_node, source, context)?;
|
||||
|
||||
ValueNode::Struct
|
||||
{
|
||||
name,
|
||||
properties
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::UnexpectedSyntaxNode {
|
||||
expected: "string, integer, float, boolean, list, map, or option".to_string(),
|
||||
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||
expected:
|
||||
"string, integer, float, boolean, range, list, map, option, function, struct or enum"
|
||||
.to_string(),
|
||||
actual: child.kind().to_string(),
|
||||
location: child.start_position(),
|
||||
relevant_source: source[child.byte_range()].to_string(),
|
||||
position: node.range().into(),
|
||||
})
|
||||
}
|
||||
};
|
||||
@ -122,10 +116,90 @@ impl AbstractTree for ValueNode {
|
||||
Ok(value_node)
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value> {
|
||||
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) => Value::Float(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()),
|
||||
@ -140,84 +214,144 @@ impl AbstractTree for ValueNode {
|
||||
|
||||
Value::List(List::with_items(values))
|
||||
}
|
||||
ValueNode::Option(option) => {
|
||||
let option_value = if let Some(expression) = option {
|
||||
Some(Box::new(expression.run(source, context)?))
|
||||
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 {
|
||||
None
|
||||
return Err(RuntimeError::ValidationFailure(
|
||||
ValidationError::TypeDefinitionNotFound(name.clone())
|
||||
));
|
||||
};
|
||||
|
||||
Value::Option(option_value)
|
||||
Value::Struct(instance)
|
||||
|
||||
}
|
||||
ValueNode::Map(key_statement_pairs) => {
|
||||
let map = Map::new();
|
||||
|
||||
{
|
||||
for (key, (statement, r#type)) in key_statement_pairs {
|
||||
let value = statement.run(source, context)?;
|
||||
|
||||
map.set(key.clone(), value, r#type.clone())?;
|
||||
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::Map(map)
|
||||
}
|
||||
ValueNode::BuiltInValue(built_in_value) => built_in_value.run(source, context)?,
|
||||
Value::Enum(instance)
|
||||
},
|
||||
};
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type> {
|
||||
let r#type = match self {
|
||||
ValueNode::Boolean(_) => Type::Boolean,
|
||||
ValueNode::Float(_) => Type::Float,
|
||||
ValueNode::Function(function) => function.r#type().clone(),
|
||||
ValueNode::Integer(_) => Type::Integer,
|
||||
ValueNode::String(_) => Type::String,
|
||||
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) => {
|
||||
let mut previous_type = None;
|
||||
output.push('[');
|
||||
|
||||
for expression in expressions {
|
||||
let expression_type = expression.expected_type(context)?;
|
||||
|
||||
if let Some(previous) = previous_type {
|
||||
if expression_type != previous {
|
||||
return Ok(Type::List(Box::new(Type::Any)));
|
||||
}
|
||||
}
|
||||
|
||||
previous_type = Some(expression_type);
|
||||
expression.format(output, indent_level);
|
||||
}
|
||||
|
||||
if let Some(previous) = previous_type {
|
||||
Type::List(Box::new(previous))
|
||||
} else {
|
||||
Type::List(Box::new(Type::Any))
|
||||
}
|
||||
output.push(']');
|
||||
}
|
||||
ValueNode::Option(option) => {
|
||||
if let Some(expression) = option {
|
||||
Type::Option(Box::new(expression.expected_type(context)?))
|
||||
} else {
|
||||
Type::None
|
||||
}
|
||||
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::Map(statements) => {
|
||||
let mut identifier_types = Vec::new();
|
||||
|
||||
for (key, (statement, _)) in statements {
|
||||
identifier_types.push((
|
||||
Identifier::new(key.clone()),
|
||||
TypeDefinition::new(statement.expected_type(context)?),
|
||||
));
|
||||
}
|
||||
|
||||
Type::Map(identifier_types)
|
||||
}
|
||||
ValueNode::BuiltInValue(built_in_value) => built_in_value.expected_type(context)?,
|
||||
};
|
||||
|
||||
Ok(r#type)
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{AbstractTree, Block, Error, Expression, Map, Result, Type, Value};
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Block, Context, Expression, Format, SyntaxNode, Type, Value,
|
||||
};
|
||||
|
||||
/// Abstract representation of a while loop.
|
||||
///
|
||||
@ -13,27 +15,50 @@ pub struct While {
|
||||
}
|
||||
|
||||
impl AbstractTree for While {
|
||||
fn from_syntax_node(source: &str, node: Node, context: &Map) -> crate::Result<Self> {
|
||||
Error::expect_syntax_node(source, "while", node)?;
|
||||
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_node(source, expression_node, context)?;
|
||||
let expression = Expression::from_syntax(expression_node, source, context)?;
|
||||
|
||||
let block_node = node.child(2).unwrap();
|
||||
let block = Block::from_syntax_node(source, block_node, context)?;
|
||||
let block = Block::from_syntax(block_node, source, context)?;
|
||||
|
||||
Ok(While { expression, block })
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value> {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type> {
|
||||
self.block.expected_type(context)
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
@ -1,54 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{
|
||||
function_expression::FunctionExpression, AbstractTree, Error, Expression, FunctionCall, Map,
|
||||
Result, Type, Value,
|
||||
};
|
||||
|
||||
/// Abstract representation of a yield expression.
|
||||
///
|
||||
/// Yield is an alternate means of calling and passing values to a function.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct Yield {
|
||||
call: FunctionCall,
|
||||
}
|
||||
|
||||
impl AbstractTree for Yield {
|
||||
fn from_syntax_node(source: &str, node: Node, context: &Map) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "yield", node)?;
|
||||
|
||||
let input_node = node.child(0).unwrap();
|
||||
let input = Expression::from_syntax_node(source, input_node, context)?;
|
||||
|
||||
let function_node = node.child(2).unwrap();
|
||||
let function_expression =
|
||||
FunctionExpression::from_syntax_node(source, function_node, context)?;
|
||||
|
||||
let mut arguments = Vec::new();
|
||||
|
||||
arguments.push(input);
|
||||
|
||||
for index in 3..node.child_count() - 1 {
|
||||
let child = node.child(index).unwrap();
|
||||
|
||||
if child.is_named() {
|
||||
let expression = Expression::from_syntax_node(source, child, context)?;
|
||||
|
||||
arguments.push(expression);
|
||||
}
|
||||
}
|
||||
|
||||
let call = FunctionCall::new(function_expression, arguments);
|
||||
|
||||
Ok(Yield { call })
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value> {
|
||||
self.call.run(source, context)
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type> {
|
||||
self.call.expected_type(context)
|
||||
}
|
||||
}
|
@ -1,227 +0,0 @@
|
||||
use std::{fs::read_to_string, path::PathBuf};
|
||||
|
||||
use dust_lang::{Interpreter, Map, Result, Value};
|
||||
use egui::{Align, Color32, Layout, RichText, ScrollArea};
|
||||
use egui_extras::{Column, TableBuilder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct App {
|
||||
path: String,
|
||||
source: String,
|
||||
context: Map,
|
||||
#[serde(skip)]
|
||||
interpreter: Interpreter,
|
||||
output: Result<Value>,
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new(cc: &eframe::CreationContext<'_>, path: PathBuf) -> Self {
|
||||
fn create_app(path: PathBuf) -> App {
|
||||
let context = Map::new();
|
||||
let mut interpreter = Interpreter::new(context.clone());
|
||||
let read_source = read_to_string(&path);
|
||||
let source = if let Ok(source) = read_source {
|
||||
source
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let output = interpreter.run(&source);
|
||||
|
||||
App {
|
||||
path: path.to_string_lossy().to_string(),
|
||||
source,
|
||||
context,
|
||||
interpreter,
|
||||
output,
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
|
||||
cc.egui_ctx.set_zoom_factor(1.2);
|
||||
|
||||
if path.is_file() {
|
||||
create_app(path)
|
||||
} else {
|
||||
if let Some(storage) = cc.storage {
|
||||
return eframe::get_value(storage, eframe::APP_KEY)
|
||||
.unwrap_or_else(|| create_app(path));
|
||||
} else {
|
||||
create_app(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for App {
|
||||
/// Called by the frame work to save state before shutdown.
|
||||
fn save(&mut self, storage: &mut dyn eframe::Storage) {
|
||||
eframe::set_value(storage, eframe::APP_KEY, self);
|
||||
}
|
||||
|
||||
/// Called each time the UI needs repainting, which may be many times per second.
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||
egui::menu::bar(ui, |ui| {
|
||||
if ui.button("Quit").clicked() {
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
}
|
||||
|
||||
ui.add_space(16.0);
|
||||
|
||||
egui::widgets::global_dark_light_mode_buttons(ui);
|
||||
|
||||
ui.with_layout(Layout::right_to_left(Align::Max), |ui| {
|
||||
egui::warn_if_debug_build(ui);
|
||||
ui.hyperlink_to("source code", "https://git.jeffa.io/jeff/dust");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.columns(2, |columns| {
|
||||
ScrollArea::vertical()
|
||||
.id_source("source")
|
||||
.show(&mut columns[0], |ui| {
|
||||
if let Some(error) = &self.error {
|
||||
ui.label(RichText::new(error).color(Color32::LIGHT_RED));
|
||||
}
|
||||
|
||||
ui.text_edit_singleline(&mut self.path);
|
||||
ui.code_editor(&mut self.source);
|
||||
|
||||
if ui.button("read").clicked() {
|
||||
match read_to_string(&self.path) {
|
||||
Ok(source) => {
|
||||
self.source = source;
|
||||
self.error = None;
|
||||
}
|
||||
Err(error) => self.error = Some(error.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
if ui.button("run").clicked() {
|
||||
self.output = self.interpreter.run(&self.source);
|
||||
}
|
||||
});
|
||||
ScrollArea::vertical()
|
||||
.id_source("output")
|
||||
.show(&mut columns[1], |ui| match &self.output {
|
||||
Ok(value) => display_value(value, ui),
|
||||
Err(error) => {
|
||||
ui.label(RichText::new(error.to_string()).color(Color32::LIGHT_RED));
|
||||
|
||||
display_value(&Value::Map(self.context.clone()), ui);
|
||||
|
||||
match &self.output {
|
||||
Ok(value) => {
|
||||
display_value(value, ui);
|
||||
}
|
||||
Err(error) => {
|
||||
ui.label(error.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn display_value(value: &Value, ui: &mut egui::Ui) {
|
||||
match value {
|
||||
Value::List(list) => {
|
||||
let table = TableBuilder::new(ui)
|
||||
.striped(true)
|
||||
.resizable(true)
|
||||
.column(Column::auto())
|
||||
.column(Column::auto());
|
||||
|
||||
table
|
||||
.header(20.0, |mut header| {
|
||||
header.col(|ui| {
|
||||
ui.strong("index");
|
||||
});
|
||||
})
|
||||
.body(|mut body| {
|
||||
for (index, value) in list.items().iter().enumerate() {
|
||||
body.row(20.0, |mut row| {
|
||||
row.col(|ui| {
|
||||
ui.label(index.to_string());
|
||||
});
|
||||
row.col(|ui| {
|
||||
display_value(value, ui);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
Value::Map(map) => {
|
||||
let table = TableBuilder::new(ui)
|
||||
.striped(true)
|
||||
.resizable(true)
|
||||
.column(Column::auto())
|
||||
.column(Column::auto());
|
||||
|
||||
table
|
||||
.header(20.0, |mut header| {
|
||||
header.col(|ui| {
|
||||
ui.strong("key");
|
||||
});
|
||||
})
|
||||
.body(|mut body| {
|
||||
for (key, (value, _)) in map.variables().unwrap().iter() {
|
||||
body.row(20.0, |mut row| {
|
||||
row.col(|ui| {
|
||||
ui.label(key);
|
||||
});
|
||||
row.col(|ui| {
|
||||
display_value(value, ui);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
Value::Function(function) => {
|
||||
ui.label(function.to_string());
|
||||
}
|
||||
Value::String(string) => {
|
||||
ui.label(RichText::new(string.read().unwrap().as_str()).color(Color32::GREEN));
|
||||
}
|
||||
Value::Float(float) => {
|
||||
ui.label(float.to_string());
|
||||
}
|
||||
Value::Integer(integer) => {
|
||||
ui.label(RichText::new(integer.to_string()).color(Color32::BLUE));
|
||||
}
|
||||
Value::Boolean(boolean) => {
|
||||
ui.label(RichText::new(boolean.to_string()).color(Color32::RED));
|
||||
}
|
||||
Value::Option(option) => match option {
|
||||
Some(value) => {
|
||||
let table = TableBuilder::new(ui)
|
||||
.striped(true)
|
||||
.resizable(true)
|
||||
.column(Column::auto());
|
||||
|
||||
table
|
||||
.header(20.0, |mut header| {
|
||||
header.col(|ui| {
|
||||
ui.strong("some");
|
||||
});
|
||||
})
|
||||
.body(|mut body| {
|
||||
body.row(20.0, |mut row| {
|
||||
row.col(|ui| {
|
||||
display_value(value, ui);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
None => {
|
||||
ui.label("none");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
mod app;
|
||||
|
||||
#[derive(Parser)]
|
||||
struct Args {
|
||||
// Path to the file to read.
|
||||
path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn main() -> eframe::Result<()> {
|
||||
env_logger::init();
|
||||
|
||||
let path = if let Some(path) = Args::parse().path {
|
||||
path
|
||||
} else {
|
||||
PathBuf::new()
|
||||
};
|
||||
let native_options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([400.0, 300.0])
|
||||
.with_min_inner_size([300.0, 220.0]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
eframe::run_native(
|
||||
"Dust GUI",
|
||||
native_options,
|
||||
Box::new(|cc| Box::new(app::App::new(cc, path))),
|
||||
)
|
||||
}
|
59
src/built_in_functions/fs.rs
Normal file
59
src/built_in_functions/fs.rs
Normal file
@ -0,0 +1,59 @@
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
77
src/built_in_functions/json.rs
Normal file
77
src/built_in_functions/json.rs
Normal file
@ -0,0 +1,77 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,34 +1,51 @@
|
||||
mod string;
|
||||
pub mod fs;
|
||||
pub mod json;
|
||||
pub mod str;
|
||||
|
||||
use std::fs::read_to_string;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use rand::{random, thread_rng, Rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Error, Map, Result, Type, Value};
|
||||
use crate::{
|
||||
error::{RuntimeError, ValidationError},
|
||||
Context, EnumInstance, Format, Identifier, Type, Value,
|
||||
};
|
||||
|
||||
pub use string::{string_functions, StringFunction};
|
||||
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,
|
||||
FsRead,
|
||||
JsonParse,
|
||||
Fs(Fs),
|
||||
Json(Json),
|
||||
Length,
|
||||
Output,
|
||||
RandomBoolean,
|
||||
RandomFloat,
|
||||
RandomFrom,
|
||||
RandomInteger,
|
||||
String(StringFunction),
|
||||
String(StrFunction),
|
||||
}
|
||||
|
||||
impl BuiltInFunction {
|
||||
pub fn name(&self) -> &'static str {
|
||||
impl Callable for BuiltInFunction {
|
||||
fn name(&self) -> &'static str {
|
||||
match self {
|
||||
BuiltInFunction::AssertEqual => "assert_equal",
|
||||
BuiltInFunction::FsRead => "read",
|
||||
BuiltInFunction::JsonParse => "parse",
|
||||
BuiltInFunction::Fs(fs_function) => fs_function.name(),
|
||||
BuiltInFunction::Json(json_function) => json_function.name(),
|
||||
BuiltInFunction::Length => "length",
|
||||
BuiltInFunction::Output => "output",
|
||||
BuiltInFunction::RandomBoolean => "boolean",
|
||||
@ -39,11 +56,26 @@ impl BuiltInFunction {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn r#type(&self) -> Type {
|
||||
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::FsRead => Type::function(vec![Type::String], Type::String),
|
||||
BuiltInFunction::JsonParse => Type::function(vec![Type::String], Type::Any),
|
||||
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),
|
||||
@ -54,52 +86,56 @@ impl BuiltInFunction {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call(&self, arguments: &[Value], _source: &str, _outer_context: &Map) -> Result<Value> {
|
||||
fn call(
|
||||
&self,
|
||||
arguments: &[Value],
|
||||
_source: &str,
|
||||
context: &Context,
|
||||
) -> Result<Value, RuntimeError> {
|
||||
match self {
|
||||
BuiltInFunction::AssertEqual => {
|
||||
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let left = arguments.get(0).unwrap();
|
||||
let right = arguments.get(1).unwrap();
|
||||
|
||||
Ok(Value::Boolean(left == right))
|
||||
}
|
||||
BuiltInFunction::FsRead => {
|
||||
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let path = arguments.first().unwrap().as_string()?;
|
||||
let file_content = read_to_string(path.as_str())?;
|
||||
|
||||
Ok(Value::string(file_content))
|
||||
}
|
||||
BuiltInFunction::JsonParse => {
|
||||
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
let value = serde_json::from_str(&string)?;
|
||||
|
||||
Ok(value)
|
||||
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 => {
|
||||
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
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()
|
||||
list.items()?.len()
|
||||
} else if let Ok(map) = value.as_map() {
|
||||
map.variables()?.len()
|
||||
map.inner().len()
|
||||
} else if let Ok(str) = value.as_string() {
|
||||
str.chars().count()
|
||||
} else {
|
||||
return Err(Error::ExpectedCollection {
|
||||
actual: value.clone(),
|
||||
});
|
||||
return Err(RuntimeError::ValidationFailure(
|
||||
ValidationError::ExpectedCollection {
|
||||
actual: value.clone(),
|
||||
},
|
||||
));
|
||||
};
|
||||
|
||||
Ok(Value::Integer(length as i64))
|
||||
}
|
||||
BuiltInFunction::Output => {
|
||||
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let value = arguments.first().unwrap();
|
||||
|
||||
@ -108,22 +144,22 @@ impl BuiltInFunction {
|
||||
Ok(Value::none())
|
||||
}
|
||||
BuiltInFunction::RandomBoolean => {
|
||||
Error::expect_argument_amount(self.name(), 0, arguments.len())?;
|
||||
RuntimeError::expect_argument_amount(self.name(), 0, arguments.len())?;
|
||||
|
||||
Ok(Value::Boolean(random()))
|
||||
}
|
||||
BuiltInFunction::RandomFloat => {
|
||||
Error::expect_argument_amount(self.name(), 0, arguments.len())?;
|
||||
RuntimeError::expect_argument_amount(self.name(), 0, arguments.len())?;
|
||||
|
||||
Ok(Value::Float(random()))
|
||||
}
|
||||
BuiltInFunction::RandomFrom => {
|
||||
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
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();
|
||||
let items = list.items()?;
|
||||
|
||||
if items.len() == 0 {
|
||||
Ok(Value::none())
|
||||
@ -138,13 +174,25 @@ impl BuiltInFunction {
|
||||
}
|
||||
}
|
||||
BuiltInFunction::RandomInteger => {
|
||||
Error::expect_argument_amount(self.name(), 0, arguments.len())?;
|
||||
RuntimeError::expect_argument_amount(self.name(), 0, arguments.len())?;
|
||||
|
||||
Ok(Value::Integer(random()))
|
||||
}
|
||||
BuiltInFunction::String(string_function) => {
|
||||
string_function.call(arguments, _source, _outer_context)
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
591
src/built_in_functions/str.rs
Normal file
591
src/built_in_functions/str.rs
Normal file
@ -0,0 +1,591 @@
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,550 +0,0 @@
|
||||
use enum_iterator::{all, Sequence};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Error, List, Map, Result, Type, Value};
|
||||
|
||||
pub fn string_functions() -> impl Iterator<Item = StringFunction> {
|
||||
all()
|
||||
}
|
||||
|
||||
#[derive(Sequence, Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum StringFunction {
|
||||
AsBytes,
|
||||
EndsWith,
|
||||
Find,
|
||||
Insert,
|
||||
IsAscii,
|
||||
IsEmpty,
|
||||
Lines,
|
||||
Matches,
|
||||
Parse,
|
||||
Pop,
|
||||
Push,
|
||||
Remove,
|
||||
ReplaceRange,
|
||||
Retain,
|
||||
Split,
|
||||
SplitAt,
|
||||
SplitInclusive,
|
||||
SplitN,
|
||||
SplitOnce,
|
||||
SplitTerminator,
|
||||
SplitWhitespace,
|
||||
StartsWith,
|
||||
StripPrefix,
|
||||
ToLowercase,
|
||||
ToUppercase,
|
||||
Trim,
|
||||
TrimEnd,
|
||||
TrimEndMatches,
|
||||
TrimMatches,
|
||||
TrimStart,
|
||||
TrimStartMatches,
|
||||
Truncate,
|
||||
}
|
||||
|
||||
impl StringFunction {
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
StringFunction::AsBytes => "as_bytes",
|
||||
StringFunction::EndsWith => "ends_with",
|
||||
StringFunction::Find => "find",
|
||||
StringFunction::Insert => "insert",
|
||||
StringFunction::IsAscii => "is_ascii",
|
||||
StringFunction::IsEmpty => "is_empty",
|
||||
StringFunction::Lines => "lines",
|
||||
StringFunction::Matches => "matches",
|
||||
StringFunction::Parse => "parse",
|
||||
StringFunction::Pop => "pop",
|
||||
StringFunction::Push => "push",
|
||||
StringFunction::Remove => "remove",
|
||||
StringFunction::ReplaceRange => "replace_range",
|
||||
StringFunction::Retain => "retain",
|
||||
StringFunction::Split => "split",
|
||||
StringFunction::SplitAt => "split_at",
|
||||
StringFunction::SplitInclusive => "split_inclusive",
|
||||
StringFunction::SplitN => "split_n",
|
||||
StringFunction::SplitOnce => "split_once",
|
||||
StringFunction::SplitTerminator => "split_terminator",
|
||||
StringFunction::SplitWhitespace => "split_whitespace",
|
||||
StringFunction::StartsWith => "starts_with",
|
||||
StringFunction::StripPrefix => "strip_prefix",
|
||||
StringFunction::ToLowercase => "to_lowercase",
|
||||
StringFunction::ToUppercase => "to_uppercase",
|
||||
StringFunction::Trim => "trim",
|
||||
StringFunction::TrimEnd => "trim_end",
|
||||
StringFunction::TrimEndMatches => "trim_end_matches",
|
||||
StringFunction::TrimMatches => "trim_matches",
|
||||
StringFunction::TrimStart => "trim_start",
|
||||
StringFunction::TrimStartMatches => "trim_start_matches",
|
||||
StringFunction::Truncate => "truncate",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn r#type(&self) -> Type {
|
||||
match self {
|
||||
StringFunction::AsBytes => {
|
||||
Type::function(vec![Type::String], Type::list(Type::Integer))
|
||||
}
|
||||
StringFunction::EndsWith => {
|
||||
Type::function(vec![Type::String, Type::String], Type::Boolean)
|
||||
}
|
||||
StringFunction::Find => Type::function(
|
||||
vec![Type::String, Type::String],
|
||||
Type::option(Type::Integer),
|
||||
),
|
||||
StringFunction::Insert => {
|
||||
Type::function(vec![Type::String, Type::Integer, Type::String], Type::None)
|
||||
}
|
||||
StringFunction::IsAscii => Type::function(vec![Type::String], Type::Boolean),
|
||||
StringFunction::IsEmpty => Type::function(vec![Type::String], Type::Boolean),
|
||||
StringFunction::Lines => Type::function(vec![Type::String], Type::list(Type::String)),
|
||||
StringFunction::Matches => {
|
||||
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
|
||||
}
|
||||
StringFunction::Parse => Type::function(vec![Type::String], Type::Any),
|
||||
StringFunction::Pop => Type::function(vec![], Type::option(Type::String)),
|
||||
StringFunction::Push => Type::function(vec![Type::String], Type::None),
|
||||
StringFunction::Remove => Type::function(
|
||||
vec![Type::String, Type::Integer],
|
||||
Type::option(Type::String),
|
||||
),
|
||||
StringFunction::ReplaceRange => Type::function(
|
||||
vec![Type::String, Type::list(Type::Integer), Type::String],
|
||||
Type::None,
|
||||
),
|
||||
StringFunction::Retain => Type::function(
|
||||
vec![
|
||||
Type::String,
|
||||
Type::function(vec![Type::String], Type::Boolean),
|
||||
],
|
||||
Type::None,
|
||||
),
|
||||
StringFunction::Split => {
|
||||
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
|
||||
}
|
||||
StringFunction::SplitAt => {
|
||||
Type::function(vec![Type::String, Type::Integer], Type::list(Type::String))
|
||||
}
|
||||
StringFunction::SplitInclusive => {
|
||||
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
|
||||
}
|
||||
StringFunction::SplitN => Type::function(
|
||||
vec![Type::String, Type::Integer, Type::String],
|
||||
Type::list(Type::String),
|
||||
),
|
||||
StringFunction::SplitOnce => {
|
||||
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
|
||||
}
|
||||
StringFunction::SplitTerminator => {
|
||||
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
|
||||
}
|
||||
StringFunction::SplitWhitespace => {
|
||||
Type::function(vec![Type::String], Type::list(Type::String))
|
||||
}
|
||||
StringFunction::StartsWith => {
|
||||
Type::function(vec![Type::String, Type::String], Type::Boolean)
|
||||
}
|
||||
StringFunction::StripPrefix => {
|
||||
Type::function(vec![Type::String, Type::String], Type::option(Type::String))
|
||||
}
|
||||
StringFunction::ToLowercase => Type::function(vec![Type::String], Type::String),
|
||||
StringFunction::ToUppercase => Type::function(vec![Type::String], Type::String),
|
||||
StringFunction::Truncate => {
|
||||
Type::function(vec![Type::String, Type::Integer], Type::None)
|
||||
}
|
||||
StringFunction::Trim => Type::function(vec![Type::String], Type::String),
|
||||
StringFunction::TrimEnd => Type::function(vec![Type::String], Type::String),
|
||||
StringFunction::TrimEndMatches => {
|
||||
Type::function(vec![Type::String, Type::String], Type::String)
|
||||
}
|
||||
StringFunction::TrimMatches => {
|
||||
Type::function(vec![Type::String, Type::String], Type::String)
|
||||
}
|
||||
StringFunction::TrimStart => Type::function(vec![Type::String], Type::String),
|
||||
StringFunction::TrimStartMatches => {
|
||||
Type::function(vec![Type::String, Type::String], Type::String)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call(&self, arguments: &[Value], _source: &str, _outer_context: &Map) -> Result<Value> {
|
||||
let value = match self {
|
||||
StringFunction::AsBytes => {
|
||||
Error::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))
|
||||
}
|
||||
StringFunction::EndsWith => {
|
||||
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.get(0).unwrap().as_string()?;
|
||||
let pattern_string = arguments.get(1).unwrap().as_string()?;
|
||||
let pattern = pattern_string.as_str();
|
||||
|
||||
Value::Boolean(string.ends_with(pattern))
|
||||
}
|
||||
StringFunction::Find => {
|
||||
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.get(0).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| Box::new(Value::Integer(index as i64)));
|
||||
|
||||
Value::Option(find)
|
||||
}
|
||||
StringFunction::IsAscii => {
|
||||
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
|
||||
Value::Boolean(string.is_ascii())
|
||||
}
|
||||
StringFunction::IsEmpty => {
|
||||
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
|
||||
Value::Boolean(string.is_empty())
|
||||
}
|
||||
StringFunction::Insert => {
|
||||
Error::expect_argument_amount(self.name(), 3, arguments.len())?;
|
||||
|
||||
let mut string = arguments.get(0).unwrap().as_string()?.to_string();
|
||||
let index = arguments.get(1).unwrap().as_integer()? as usize;
|
||||
let insertion = arguments.get(2).unwrap().as_string()?;
|
||||
|
||||
string.insert_str(index, &insertion);
|
||||
|
||||
Value::none()
|
||||
}
|
||||
StringFunction::Lines => {
|
||||
Error::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))
|
||||
}
|
||||
StringFunction::Matches => {
|
||||
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.get(0).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))
|
||||
}
|
||||
StringFunction::Parse => {
|
||||
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
|
||||
if let Ok(integer) = string.parse::<i64>() {
|
||||
Value::option(Some(Value::Integer(integer)))
|
||||
} else if let Ok(float) = string.parse::<f64>() {
|
||||
Value::option(Some(Value::Float(float)))
|
||||
} else {
|
||||
Value::none()
|
||||
}
|
||||
}
|
||||
StringFunction::Pop => {
|
||||
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let mut string = arguments.first().unwrap().as_string_mut()?;
|
||||
let popped = string.pop().map(|char| Value::string(char.to_string()));
|
||||
|
||||
Value::option(popped)
|
||||
}
|
||||
StringFunction::Push => {
|
||||
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let mut string = arguments.get(0).unwrap().as_string_mut()?;
|
||||
let addition = arguments.get(1).unwrap().as_string()?;
|
||||
|
||||
string.push_str(&addition);
|
||||
|
||||
Value::none()
|
||||
}
|
||||
StringFunction::Remove => {
|
||||
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let mut string = arguments.get(0).unwrap().as_string_mut()?;
|
||||
let index = arguments.get(1).unwrap().as_integer()? as usize;
|
||||
let mut chars = string.chars().collect::<Vec<char>>();
|
||||
|
||||
if index <= chars.len() - 1 {
|
||||
let removed = chars.remove(index);
|
||||
let new_string = chars
|
||||
.iter()
|
||||
.map(|char| char.to_string())
|
||||
.collect::<String>();
|
||||
|
||||
*string = new_string;
|
||||
|
||||
Value::some(Value::string(removed))
|
||||
} else {
|
||||
Value::none()
|
||||
}
|
||||
}
|
||||
StringFunction::ReplaceRange => {
|
||||
Error::expect_argument_amount(self.name(), 3, arguments.len())?;
|
||||
|
||||
let mut string = arguments.get(0).unwrap().as_string_mut()?;
|
||||
let range = arguments.get(1).unwrap().as_list()?.items();
|
||||
let start = range.get(0).unwrap_or_default().as_integer()? as usize;
|
||||
let end = range.get(1).unwrap_or_default().as_integer()? as usize;
|
||||
let pattern = arguments.get(2).unwrap().as_string()?;
|
||||
|
||||
string.replace_range(start..end, &pattern);
|
||||
|
||||
Value::none()
|
||||
}
|
||||
StringFunction::Retain => {
|
||||
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let mut string = arguments.get(0).unwrap().as_string_mut()?;
|
||||
let predicate = arguments.get(1).unwrap().as_function()?;
|
||||
|
||||
string.retain(|char| {
|
||||
predicate
|
||||
.call(None, &[Value::string(char)], _source, _outer_context)
|
||||
.unwrap()
|
||||
.as_boolean()
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
Value::none()
|
||||
}
|
||||
StringFunction::Split => {
|
||||
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.get(0).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))
|
||||
}
|
||||
StringFunction::SplitAt => {
|
||||
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.get(0).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()),
|
||||
]))
|
||||
}
|
||||
StringFunction::SplitInclusive => {
|
||||
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.get(0).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))
|
||||
}
|
||||
StringFunction::SplitN => {
|
||||
Error::expect_argument_amount(self.name(), 3, arguments.len())?;
|
||||
|
||||
let string = arguments.get(0).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))
|
||||
}
|
||||
StringFunction::SplitOnce => {
|
||||
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.get(0).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()),
|
||||
]))
|
||||
});
|
||||
|
||||
Value::option(sections)
|
||||
}
|
||||
StringFunction::SplitTerminator => {
|
||||
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.get(0).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))
|
||||
}
|
||||
StringFunction::SplitWhitespace => {
|
||||
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let string = arguments.get(0).unwrap().as_string()?;
|
||||
let sections = string
|
||||
.split_whitespace()
|
||||
.map(|section| Value::string(section.to_string()))
|
||||
.collect();
|
||||
|
||||
Value::List(List::with_items(sections))
|
||||
}
|
||||
StringFunction::StartsWith => {
|
||||
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.get(0).unwrap().as_string()?;
|
||||
let pattern_string = arguments.get(1).unwrap().as_string()?;
|
||||
let pattern = pattern_string.as_str();
|
||||
|
||||
Value::Boolean(string.starts_with(pattern))
|
||||
}
|
||||
StringFunction::StripPrefix => {
|
||||
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.get(0).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()));
|
||||
|
||||
Value::option(stripped)
|
||||
}
|
||||
StringFunction::ToLowercase => {
|
||||
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let string = arguments.get(0).unwrap().as_string()?;
|
||||
let lowercase = string.to_lowercase();
|
||||
|
||||
Value::string(lowercase)
|
||||
}
|
||||
StringFunction::ToUppercase => {
|
||||
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let string = arguments.get(0).unwrap().as_string()?;
|
||||
let uppercase = string.to_uppercase();
|
||||
|
||||
Value::string(uppercase)
|
||||
}
|
||||
StringFunction::Trim => {
|
||||
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let trimmed = arguments.first().unwrap().as_string()?.trim().to_string();
|
||||
|
||||
Value::string(trimmed)
|
||||
}
|
||||
StringFunction::TrimEnd => {
|
||||
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let trimmed = arguments
|
||||
.first()
|
||||
.unwrap()
|
||||
.as_string()?
|
||||
.trim_end()
|
||||
.to_string();
|
||||
|
||||
Value::string(trimmed)
|
||||
}
|
||||
StringFunction::TrimEndMatches => {
|
||||
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.get(0).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)
|
||||
}
|
||||
StringFunction::TrimMatches => {
|
||||
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.get(0).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)
|
||||
}
|
||||
StringFunction::TrimStart => {
|
||||
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let trimmed = arguments
|
||||
.first()
|
||||
.unwrap()
|
||||
.as_string()?
|
||||
.trim_start()
|
||||
.to_string();
|
||||
|
||||
Value::string(trimmed)
|
||||
}
|
||||
StringFunction::TrimStartMatches => {
|
||||
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.get(0).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)
|
||||
}
|
||||
StringFunction::Truncate => {
|
||||
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let mut string = arguments.get(0).unwrap().as_string_mut()?;
|
||||
let new_length = arguments.get(1).unwrap().as_integer()? as usize;
|
||||
|
||||
*string = string
|
||||
.chars()
|
||||
.take(new_length)
|
||||
.map(|char| char.to_string())
|
||||
.collect();
|
||||
|
||||
Value::none()
|
||||
}
|
||||
};
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
}
|
51
src/built_in_identifiers.rs
Normal file
51
src/built_in_identifiers.rs
Normal file
@ -0,0 +1,51 @@
|
||||
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())))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
56
src/built_in_type_definitions.rs
Normal file
56
src/built_in_type_definitions.rs
Normal file
@ -0,0 +1,56 @@
|
||||
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)
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
29
src/built_in_types.rs
Normal file
29
src/built_in_types.rs
Normal file
@ -0,0 +1,29 @@
|
||||
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))
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
180
src/built_in_values.rs
Normal file
180
src/built_in_values.rs
Normal file
@ -0,0 +1,180 @@
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
395
src/context/mod.rs
Normal file
395
src/context/mod.rs
Normal file
@ -0,0 +1,395 @@
|
||||
//! 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);
|
||||
}
|
||||
}
|
74
src/context/usage_counter.rs
Normal file
74
src/context/usage_counter.rs
Normal file
@ -0,0 +1,74 @@
|
||||
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,
|
||||
}
|
8
src/context/value_data.rs
Normal file
8
src/context/value_data.rs
Normal file
@ -0,0 +1,8 @@
|
||||
use crate::{Type, TypeDefinition, Value};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub enum ValueData {
|
||||
Value(Value),
|
||||
TypeHint(Type),
|
||||
TypeDefinition(TypeDefinition),
|
||||
}
|
444
src/error.rs
444
src/error.rs
@ -1,444 +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.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::{LanguageError, Node, Point};
|
||||
|
||||
use crate::{value::Value, Type};
|
||||
|
||||
use std::{
|
||||
fmt::{self, Formatter},
|
||||
io,
|
||||
num::ParseFloatError,
|
||||
string::FromUtf8Error,
|
||||
sync::PoisonError,
|
||||
time,
|
||||
};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Error {
|
||||
WithContext {
|
||||
error: Box<Error>,
|
||||
#[serde(skip)]
|
||||
location: Point,
|
||||
source: String,
|
||||
},
|
||||
|
||||
UnexpectedSyntaxNode {
|
||||
expected: String,
|
||||
actual: String,
|
||||
#[serde(skip)]
|
||||
location: Point,
|
||||
relevant_source: String,
|
||||
},
|
||||
|
||||
TypeCheck {
|
||||
expected: Type,
|
||||
actual: Type,
|
||||
},
|
||||
|
||||
/// 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.
|
||||
ExpectedBuiltInFunctionArgumentAmount {
|
||||
function_name: String,
|
||||
expected: usize,
|
||||
actual: usize,
|
||||
},
|
||||
|
||||
/// A function was called with the wrong amount of arguments.
|
||||
ExpectedFunctionArgumentAmount {
|
||||
source: String,
|
||||
expected: usize,
|
||||
actual: usize,
|
||||
},
|
||||
|
||||
/// A function was called with the wrong amount of arguments.
|
||||
ExpectedFunctionArgumentMinimum {
|
||||
source: String,
|
||||
minumum_expected: usize,
|
||||
actual: usize,
|
||||
},
|
||||
|
||||
ExpectedFunctionType {
|
||||
actual: 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,
|
||||
},
|
||||
|
||||
ExpectedNone {
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
ExpectedMap {
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
ExpectedTable {
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
ExpectedFunction {
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
ExpectedOption {
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
/// A string, list, map or table value was expected.
|
||||
ExpectedCollection {
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
/// A `VariableIdentifier` operation did not find its value in the context.
|
||||
VariableIdentifierNotFound(String),
|
||||
|
||||
/// A `FunctionIdentifier` operation did not find its value in the context.
|
||||
FunctionIdentifierNotFound(String),
|
||||
|
||||
/// The function failed due to an external error.
|
||||
External(String),
|
||||
|
||||
/// A custom error explained by its message.
|
||||
CustomMessage(String),
|
||||
|
||||
/// Invalid user input.
|
||||
Syntax {
|
||||
source: String,
|
||||
#[serde(skip)]
|
||||
location: Point,
|
||||
},
|
||||
|
||||
SerdeJson(String),
|
||||
|
||||
ParserCancelled,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn at_node(self, node: Node, source: &str) -> Self {
|
||||
Error::WithContext {
|
||||
error: Box::new(self),
|
||||
location: node.start_position(),
|
||||
source: source[node.byte_range()].to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_syntax_node(source: &str, expected: &str, actual: Node) -> Result<()> {
|
||||
if expected == actual.kind() {
|
||||
Ok(())
|
||||
} else if actual.is_error() {
|
||||
Err(Error::Syntax {
|
||||
source: source[actual.byte_range()].to_string(),
|
||||
location: actual.start_position(),
|
||||
})
|
||||
} else {
|
||||
Err(Error::UnexpectedSyntaxNode {
|
||||
expected: expected.to_string(),
|
||||
actual: actual.kind().to_string(),
|
||||
location: actual.start_position(),
|
||||
relevant_source: source[actual.byte_range()].to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_argument_amount(
|
||||
function_name: &str,
|
||||
expected: usize,
|
||||
actual: usize,
|
||||
) -> Result<()> {
|
||||
if expected == actual {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::ExpectedBuiltInFunctionArgumentAmount {
|
||||
function_name: function_name.to_string(),
|
||||
expected,
|
||||
actual,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_type_check_error(&self, other: &Error) -> bool {
|
||||
match self {
|
||||
Error::WithContext { error, .. } => error.as_ref() == other,
|
||||
_ => self == other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LanguageError> for Error {
|
||||
fn from(value: LanguageError) -> Self {
|
||||
Error::External(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<PoisonError<T>> for Error {
|
||||
fn from(value: PoisonError<T>) -> Self {
|
||||
Error::External(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FromUtf8Error> for Error {
|
||||
fn from(value: FromUtf8Error) -> Self {
|
||||
Error::External(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseFloatError> for Error {
|
||||
fn from(value: ParseFloatError) -> Self {
|
||||
Error::External(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<csv::Error> for Error {
|
||||
fn from(value: csv::Error) -> Self {
|
||||
Error::External(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(value: std::io::Error) -> Self {
|
||||
Error::External(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<reqwest::Error> for Error {
|
||||
fn from(value: reqwest::Error) -> Self {
|
||||
Error::External(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for Error {
|
||||
fn from(value: serde_json::Error) -> Self {
|
||||
Error::SerdeJson(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<time::SystemTimeError> for Error {
|
||||
fn from(value: time::SystemTimeError) -> Self {
|
||||
Error::External(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<toml::de::Error> for Error {
|
||||
fn from(value: toml::de::Error) -> Self {
|
||||
Error::External(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl fmt::Debug for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{self}")
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut 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
|
||||
),
|
||||
ExpectedBuiltInFunctionArgumentAmount {
|
||||
function_name: tool_name,
|
||||
expected,
|
||||
actual,
|
||||
} => write!(
|
||||
f,
|
||||
"{tool_name} expected {expected} arguments, but got {actual}.",
|
||||
),
|
||||
ExpectedFunctionArgumentAmount {
|
||||
source,
|
||||
expected,
|
||||
actual,
|
||||
} => write!(
|
||||
f,
|
||||
"{source} expected {expected} arguments, but got {actual}.",
|
||||
),
|
||||
ExpectedFunctionArgumentMinimum {
|
||||
source,
|
||||
minumum_expected,
|
||||
actual,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"{source} expected at least {minumum_expected} arguments, but got {actual}."
|
||||
)
|
||||
}
|
||||
ExpectedString { actual } => {
|
||||
write!(f, "Expected a string but got {actual}.")
|
||||
}
|
||||
ExpectedInteger { actual } => write!(f, "Expected an integer, but got {actual}."),
|
||||
ExpectedFloat { actual } => write!(f, "Expected a float, but got {actual}."),
|
||||
ExpectedNumber { actual } => {
|
||||
write!(f, "Expected a float or integer but got {actual}.",)
|
||||
}
|
||||
ExpectedNumberOrString { actual } => {
|
||||
write!(f, "Expected a number or string, but got {actual}.")
|
||||
}
|
||||
ExpectedBoolean { actual } => {
|
||||
write!(f, "Expected a boolean, but got {actual}.")
|
||||
}
|
||||
ExpectedList { actual } => write!(f, "Expected a list, but got {actual}."),
|
||||
ExpectedMinLengthList {
|
||||
minimum_len,
|
||||
actual_len,
|
||||
} => write!(
|
||||
f,
|
||||
"Expected a list of at least {minimum_len} values, but got one with {actual_len}.",
|
||||
),
|
||||
ExpectedFixedLenList {
|
||||
expected_len,
|
||||
actual,
|
||||
} => write!(
|
||||
f,
|
||||
"Expected a list of len {}, but got {:?}.",
|
||||
expected_len, actual
|
||||
),
|
||||
ExpectedNone { actual } => write!(f, "Expected an empty value, but got {actual}."),
|
||||
ExpectedMap { actual } => write!(f, "Expected a map, but got {actual}."),
|
||||
ExpectedTable { actual } => write!(f, "Expected a table, but got {actual}."),
|
||||
ExpectedFunction { actual } => {
|
||||
write!(f, "Expected function, but got {actual}.")
|
||||
}
|
||||
ExpectedOption { actual } => write!(f, "Expected option, but got {actual}."),
|
||||
ExpectedCollection { actual } => {
|
||||
write!(
|
||||
f,
|
||||
"Expected a string, list, map or table, but got {actual}.",
|
||||
)
|
||||
}
|
||||
VariableIdentifierNotFound(key) => write!(
|
||||
f,
|
||||
"Variable identifier is not bound to anything by context: {key}.",
|
||||
),
|
||||
FunctionIdentifierNotFound(key) => write!(
|
||||
f,
|
||||
"Function identifier is not bound to anything by context: {key}."
|
||||
),
|
||||
UnexpectedSyntaxNode {
|
||||
expected,
|
||||
actual,
|
||||
location,
|
||||
relevant_source,
|
||||
} => {
|
||||
let location = get_position(location);
|
||||
|
||||
write!(
|
||||
f,
|
||||
"Expected {expected}, but got {actual} at {location}. Code: {relevant_source} ",
|
||||
)
|
||||
}
|
||||
WrongColumnAmount { expected, actual } => write!(
|
||||
f,
|
||||
"Wrong column amount. Expected {expected} but got {actual}."
|
||||
),
|
||||
External(message) => write!(f, "External error: {message}"),
|
||||
CustomMessage(message) => write!(f, "{message}"),
|
||||
Syntax { source, location } => {
|
||||
let location = get_position(location);
|
||||
|
||||
write!(f, "Syntax error at {location}: {source}")
|
||||
}
|
||||
TypeCheck { expected, actual } => write!(
|
||||
f,
|
||||
"Type check error. Expected type {expected} but got type {actual}."
|
||||
),
|
||||
WithContext {
|
||||
error,
|
||||
location,
|
||||
source,
|
||||
} => {
|
||||
let location = get_position(location);
|
||||
|
||||
write!(f, "{error} Occured at {location}: \"{source}\"")
|
||||
}
|
||||
SerdeJson(message) => write!(f, "JSON processing error: {message}"),
|
||||
ParserCancelled => write!(
|
||||
f,
|
||||
"Parsing was cancelled either manually or because it took too long."
|
||||
),
|
||||
ExpectedFunctionType { actual } => write!(f, "Expected a function but got {actual}."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_position(position: &Point) -> String {
|
||||
format!("column {}, row {}", position.row + 1, position.column)
|
||||
}
|
110
src/error/mod.rs
Normal file
110
src/error/mod.rs
Normal file
@ -0,0 +1,110 @@
|
||||
//! 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."),
|
||||
}
|
||||
}
|
||||
}
|
193
src/error/runtime_error.rs
Normal file
193
src/error/runtime_error.rs
Normal file
@ -0,0 +1,193 @@
|
||||
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:?}")
|
||||
}
|
||||
}
|
30
src/error/rw_lock_error.rs
Normal file
30
src/error/rw_lock_error.rs
Normal file
@ -0,0 +1,30 @@
|
||||
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
|
||||
}
|
||||
}
|
126
src/error/syntax_error.rs
Normal file
126
src/error/syntax_error.rs
Normal file
@ -0,0 +1,126 @@
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
274
src/error/validation_error.rs
Normal file
274
src/error/validation_error.rs
Normal file
@ -0,0 +1,274 @@
|
||||
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:?}")
|
||||
}
|
||||
}
|
219
src/interpret.rs
219
src/interpret.rs
@ -1,46 +1,56 @@
|
||||
//! The top level of Dust's API with functions to interpret Dust code.
|
||||
//! Tools to interpret dust source code.
|
||||
//!
|
||||
//! You can use this library externally by calling either of the "eval"
|
||||
//! functions or by constructing your own Evaluator.
|
||||
use tree_sitter::{Node, Parser, Tree as TSTree, TreeCursor};
|
||||
//! 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, Error, Map, Result, Root, Value};
|
||||
use crate::{language, AbstractTree, Context, ContextMode, Error, Format, Root, Value};
|
||||
|
||||
/// Interpret the given source code.
|
||||
/// Interpret the given source code. Returns the value of last statement or the
|
||||
/// first error encountered.
|
||||
///
|
||||
/// Returns a vector of results from evaluating the source code. Each comment
|
||||
/// and statemtent will have its own result.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use dust_lang::*;
|
||||
/// assert_eq!(interpret("1 + 2 + 3"), Ok(Value::Integer(6)));
|
||||
/// ```
|
||||
pub fn interpret(source: &str) -> Result<Value> {
|
||||
interpret_with_context(source, Map::new())
|
||||
/// 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.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use dust_lang::*;
|
||||
/// let context = Map::new();
|
||||
///
|
||||
/// context.set("one".into(), 1.into(), None);
|
||||
/// context.set("two".into(), 2.into(), None);
|
||||
/// context.set("three".into(), 3.into(), None);
|
||||
///
|
||||
/// let dust_code = "four = 4 one + two + three + four";
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// interpret_with_context(dust_code, context),
|
||||
/// Ok(Value::Integer(10))
|
||||
/// );
|
||||
/// ```
|
||||
pub fn interpret_with_context(source: &str, context: Map) -> Result<Value> {
|
||||
/// 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)?;
|
||||
|
||||
@ -48,91 +58,100 @@ pub fn interpret_with_context(source: &str, context: Map) -> Result<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: Map,
|
||||
syntax_tree: Option<TSTree>,
|
||||
abstract_tree: Option<Root>,
|
||||
context: Context,
|
||||
}
|
||||
|
||||
impl Interpreter {
|
||||
pub fn new(context: Map) -> Self {
|
||||
/// 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.");
|
||||
|
||||
Interpreter {
|
||||
parser,
|
||||
context,
|
||||
syntax_tree: None,
|
||||
abstract_tree: None,
|
||||
}
|
||||
parser.set_logger(Some(Box::new(|_log_type, message| {
|
||||
log::trace!("{}", message)
|
||||
})));
|
||||
|
||||
Interpreter { parser, context }
|
||||
}
|
||||
|
||||
pub fn parse(&mut self, source: &str) -> Result<()> {
|
||||
fn check_for_error(source: &str, node: Node, cursor: &mut TreeCursor) -> Result<()> {
|
||||
if node.is_error() {
|
||||
Err(Error::Syntax {
|
||||
source: source[node.byte_range()].to_string(),
|
||||
location: node.start_position(),
|
||||
})
|
||||
} else {
|
||||
for child in node.children(&mut cursor.clone()) {
|
||||
check_for_error(source, child, cursor)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let syntax_tree = self.parser.parse(source, None);
|
||||
|
||||
if let Some(tree) = &syntax_tree {
|
||||
let root = tree.root_node();
|
||||
let mut cursor = root.walk();
|
||||
|
||||
check_for_error(source, root, &mut cursor)?;
|
||||
}
|
||||
|
||||
self.syntax_tree = syntax_tree;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run(&mut self, source: &str) -> Result<Value> {
|
||||
self.parse(source)?;
|
||||
|
||||
self.abstract_tree = if let Some(syntax_tree) = &self.syntax_tree {
|
||||
Some(Root::from_syntax_node(
|
||||
source,
|
||||
syntax_tree.root_node(),
|
||||
&self.context,
|
||||
)?)
|
||||
} else {
|
||||
return Err(Error::ParserCancelled);
|
||||
};
|
||||
|
||||
if let Some(abstract_tree) = &self.abstract_tree {
|
||||
abstract_tree.check_type(&self.context)?;
|
||||
abstract_tree.run(source, &self.context)
|
||||
} else {
|
||||
Ok(Value::none())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn syntax_tree(&self) -> Result<String> {
|
||||
if let Some(syntax_tree) = &self.syntax_tree {
|
||||
Ok(syntax_tree.root_node().to_sexp())
|
||||
/// 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(Map::new())
|
||||
Interpreter::new(Context::default())
|
||||
}
|
||||
}
|
||||
|
43
src/lib.rs
43
src/lib.rs
@ -1,21 +1,26 @@
|
||||
//! The Dust library is used to implement the Dust language, `src/main.rs` implements the command
|
||||
//! line binary.
|
||||
//! The Dust library is used to parse, format and run dust source code.
|
||||
//!
|
||||
//! 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.
|
||||
//! 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.
|
||||
pub use crate::{
|
||||
abstract_tree::*,
|
||||
built_in_functions::BuiltInFunction,
|
||||
error::*,
|
||||
interpret::*,
|
||||
value::{function::Function, list::List, map::Map, Value},
|
||||
abstract_tree::*, built_in_functions::BuiltInFunction, context::*, error::Error, interpret::*,
|
||||
value::*,
|
||||
};
|
||||
|
||||
mod abstract_tree;
|
||||
pub use tree_sitter::Node as SyntaxNode;
|
||||
|
||||
pub mod abstract_tree;
|
||||
pub mod built_in_functions;
|
||||
mod error;
|
||||
mod interpret;
|
||||
mod value;
|
||||
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;
|
||||
|
||||
use tree_sitter::Language;
|
||||
|
||||
@ -30,18 +35,6 @@ pub fn language() -> Language {
|
||||
unsafe { tree_sitter_dust() }
|
||||
}
|
||||
|
||||
/// The content of the [`node-types.json`][] file for this grammar.
|
||||
///
|
||||
/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types
|
||||
pub const NODE_TYPES: &str = include_str!("../tree-sitter-dust/src/node-types.json");
|
||||
|
||||
// Uncomment these to include any queries that this grammar contains
|
||||
|
||||
// pub const HIGHLIGHTS_QUERY: &'static str = include_str!("../../queries/highlights.scm");
|
||||
// pub const INJECTIONS_QUERY: &'static str = include_str!("../../queries/injections.scm");
|
||||
// pub const LOCALS_QUERY: &'static str = include_str!("../../queries/locals.scm");
|
||||
// pub const TAGS_QUERY: &'static str = include_str!("../../queries/tags.scm");
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
|
468
src/main.rs
468
src/main.rs
@ -1,18 +1,20 @@
|
||||
//! Command line interface for the dust 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 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,
|
||||
};
|
||||
use tree_sitter::Parser as TSParser;
|
||||
|
||||
use std::{borrow::Cow, fs::read_to_string};
|
||||
use std::{borrow::Cow, fs::read_to_string, io::Write, path::PathBuf, process::Command};
|
||||
|
||||
use dust_lang::{language, Interpreter, Map, Value};
|
||||
use dust_lang::{
|
||||
built_in_values::all_built_in_values, Context, ContextMode, Error, Interpreter, Value,
|
||||
ValueData,
|
||||
};
|
||||
|
||||
/// Command-line arguments to be parsed.
|
||||
#[derive(Parser, Debug)]
|
||||
@ -22,62 +24,73 @@ struct Args {
|
||||
#[arg(short, long)]
|
||||
command: Option<String>,
|
||||
|
||||
/// Data to assign to the "input" variable.
|
||||
#[arg(short, long)]
|
||||
input: Option<String>,
|
||||
|
||||
/// A path to file whose contents will be assigned to the "input" variable.
|
||||
#[arg(short = 'p', long)]
|
||||
input_path: Option<String>,
|
||||
|
||||
/// Show the syntax tree.
|
||||
#[arg(short = 't', long = "tree")]
|
||||
show_syntax_tree: bool,
|
||||
/// 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() {
|
||||
return run_cli_shell();
|
||||
let run_shell_result = run_shell(context);
|
||||
|
||||
match run_shell_result {
|
||||
Ok(_) => {}
|
||||
Err(error) => eprintln!("{error}"),
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let source = if let Some(path) = &args.path {
|
||||
read_to_string(path).unwrap()
|
||||
} else if let Some(command) = &args.command {
|
||||
command.clone()
|
||||
} else if let Some(command) = args.command {
|
||||
command
|
||||
} else {
|
||||
"".to_string()
|
||||
String::with_capacity(0)
|
||||
};
|
||||
|
||||
let context = Map::new();
|
||||
|
||||
if let Some(input) = args.input {
|
||||
context
|
||||
.set("input".to_string(), Value::string(input), None)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
if let Some(path) = args.input_path {
|
||||
let file_contents = read_to_string(path).unwrap();
|
||||
|
||||
context
|
||||
.set("input".to_string(), Value::string(file_contents), None)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let mut parser = TSParser::new();
|
||||
parser.set_language(language()).unwrap();
|
||||
|
||||
let mut interpreter = Interpreter::new(context);
|
||||
|
||||
if args.show_syntax_tree {
|
||||
interpreter.parse(&source).unwrap();
|
||||
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!("{}", interpreter.syntax_tree().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);
|
||||
@ -88,122 +101,307 @@ fn main() {
|
||||
println!("{value}")
|
||||
}
|
||||
}
|
||||
Err(error) => eprintln!("{error}"),
|
||||
Err(error) => eprintln!("{}", error.create_report(&source)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Helper, Completer, Validator)]
|
||||
struct DustReadline {
|
||||
#[rustyline(Completer)]
|
||||
completer: FilenameCompleter,
|
||||
// struct DustHighlighter {
|
||||
// context: Context,
|
||||
// }
|
||||
|
||||
tool_hints: Vec<ToolHint>,
|
||||
// impl DustHighlighter {
|
||||
// fn new(context: Context) -> Self {
|
||||
// Self { context }
|
||||
// }
|
||||
// }
|
||||
|
||||
#[rustyline(Hinter)]
|
||||
_hinter: HistoryHinter,
|
||||
// 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,
|
||||
}
|
||||
|
||||
impl DustReadline {
|
||||
impl StarshipPrompt {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
completer: FilenameCompleter::new(),
|
||||
_hinter: HistoryHinter {},
|
||||
tool_hints: Vec::new(),
|
||||
left: String::new(),
|
||||
right: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ToolHint {
|
||||
display: String,
|
||||
complete_to: usize,
|
||||
}
|
||||
|
||||
impl Hint for ToolHint {
|
||||
fn display(&self) -> &str {
|
||||
&self.display
|
||||
}
|
||||
|
||||
fn completion(&self) -> Option<&str> {
|
||||
if self.complete_to > 0 {
|
||||
Some(&self.display[..self.complete_to])
|
||||
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()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
">".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;
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHint {
|
||||
fn suffix(&self, strip_chars: usize) -> ToolHint {
|
||||
ToolHint {
|
||||
display: self.display[strip_chars..].to_string(),
|
||||
complete_to: self.complete_to.saturating_sub(strip_chars),
|
||||
}
|
||||
impl 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 Hinter for DustReadline {
|
||||
type Hint = ToolHint;
|
||||
pub struct DustCompleter {
|
||||
context: Context,
|
||||
}
|
||||
|
||||
fn hint(&self, line: &str, pos: usize, _ctx: &Context<'_>) -> Option<Self::Hint> {
|
||||
if line.is_empty() || pos < line.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.tool_hints.iter().find_map(|tool_hint| {
|
||||
if tool_hint.display.starts_with(line) {
|
||||
Some(tool_hint.suffix(pos))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
impl DustCompleter {
|
||||
fn new(context: Context) -> Self {
|
||||
DustCompleter { context }
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
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
|
||||
};
|
||||
|
||||
Cow::Owned(highlighted)
|
||||
}
|
||||
}
|
||||
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"
|
||||
};
|
||||
|
||||
fn run_cli_shell() {
|
||||
let context = Map::new();
|
||||
let mut interpreter = Interpreter::new(context);
|
||||
let mut rl: Editor<DustReadline, DefaultHistory> = Editor::new().unwrap();
|
||||
let mut input = String::new();
|
||||
|
||||
rl.set_helper(Some(DustReadline::new()));
|
||||
|
||||
if rl.load_history("target/history.txt").is_err() {
|
||||
println!("No previous history.");
|
||||
}
|
||||
|
||||
loop {
|
||||
let readline = rl.readline("* ");
|
||||
match readline {
|
||||
Ok(line) => {
|
||||
let line = line.as_str();
|
||||
|
||||
input.push('\n');
|
||||
input.push_str(line);
|
||||
rl.add_history_entry(line).unwrap();
|
||||
|
||||
let eval_result = interpreter.run(&input);
|
||||
|
||||
match eval_result {
|
||||
Ok(value) => println!("{value}"),
|
||||
Err(error) => {
|
||||
input = input.trim_end_matches(line).to_string();
|
||||
eprintln!("{error}")
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(ReadlineError::Interrupted) => break,
|
||||
Err(ReadlineError::Eof) => break,
|
||||
Err(error) => eprintln!("{error}"),
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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,
|
||||
]),
|
||||
);
|
||||
|
||||
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());
|
||||
|
||||
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();
|
||||
|
||||
prompt.reload();
|
||||
|
||||
loop {
|
||||
let sig = line_editor.read_line(&prompt);
|
||||
|
||||
match sig {
|
||||
Ok(Signal::Success(buffer)) => {
|
||||
if buffer.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let run_result = interpreter.run(&buffer);
|
||||
|
||||
match run_result {
|
||||
Ok(value) => {
|
||||
if !value.is_none() {
|
||||
println!("{value}")
|
||||
}
|
||||
}
|
||||
Err(error) => println!("{error}"),
|
||||
}
|
||||
|
||||
prompt.reload();
|
||||
}
|
||||
Ok(Signal::CtrlD) | Ok(Signal::CtrlC) => {
|
||||
println!("\nLeaving the Dust shell.");
|
||||
break;
|
||||
}
|
||||
x => {
|
||||
println!("Unknown event: {:?}", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rl.save_history("target/history.txt").unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
40
src/value/enum_instance.rs
Normal file
40
src/value/enum_instance.rs
Normal file
@ -0,0 +1,40 @@
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,45 +1,18 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{AbstractTree, BuiltInFunction, FunctionNode, Map, Result, Type, Value};
|
||||
use crate::{
|
||||
built_in_functions::Callable, BuiltInFunction, Format, FunctionNode, Identifier, Type,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum Function {
|
||||
BuiltIn(BuiltInFunction),
|
||||
ContextDefined(FunctionNode),
|
||||
}
|
||||
|
||||
impl Display for Function {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Function::BuiltIn(built_in_function) => write!(f, "{}", built_in_function.r#type()),
|
||||
Function::ContextDefined(context_defined_function) => {
|
||||
write!(f, "{}", context_defined_function.r#type())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Function {
|
||||
pub fn call(
|
||||
&self,
|
||||
name: Option<String>,
|
||||
arguments: &[Value],
|
||||
source: &str,
|
||||
outer_context: &Map,
|
||||
) -> Result<Value> {
|
||||
match self {
|
||||
Function::BuiltIn(built_in_function) => {
|
||||
built_in_function.call(arguments, source, outer_context)
|
||||
}
|
||||
Function::ContextDefined(context_defined_function) => {
|
||||
context_defined_function.call(name, arguments, source, outer_context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn r#type(&self) -> Type {
|
||||
match self {
|
||||
Function::BuiltIn(built_in_function) => built_in_function.r#type(),
|
||||
@ -48,30 +21,30 @@ impl Function {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AbstractTree for Function {
|
||||
fn from_syntax_node(_source: &str, _node: Node, _context: &Map) -> Result<Self> {
|
||||
let inner_function = FunctionNode::from_syntax_node(_source, _node, _context)?;
|
||||
|
||||
Ok(Function::ContextDefined(inner_function))
|
||||
}
|
||||
|
||||
fn check_type(&self, _context: &Map) -> Result<()> {
|
||||
match self {
|
||||
Function::BuiltIn(_) => Ok(()),
|
||||
Function::ContextDefined(defined_function) => defined_function.check_type(_context),
|
||||
}
|
||||
}
|
||||
|
||||
fn run(&self, _source: &str, _context: &Map) -> Result<Value> {
|
||||
Ok(Value::Function(self.clone()))
|
||||
}
|
||||
|
||||
fn expected_type(&self, _context: &Map) -> Result<Type> {
|
||||
match self {
|
||||
Function::BuiltIn(built_in) => Ok(built_in.r#type()),
|
||||
Function::ContextDefined(context_defined) => Ok(context_defined.r#type().clone()),
|
||||
pub fn parameters(&self) -> Option<&Vec<Identifier>> {
|
||||
if let Function::ContextDefined(function) = self {
|
||||
Some(function.parameters())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,13 @@ use std::{
|
||||
sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard},
|
||||
};
|
||||
|
||||
use crate::Value;
|
||||
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>>>);
|
||||
@ -28,12 +34,51 @@ impl List {
|
||||
List(Arc::new(RwLock::new(items)))
|
||||
}
|
||||
|
||||
pub fn items(&self) -> RwLockReadGuard<'_, Vec<Value>> {
|
||||
self.0.read().unwrap()
|
||||
pub fn items(&self) -> Result<RwLockReadGuard<Vec<Value>>, RwLockError> {
|
||||
Ok(self.0.read()?)
|
||||
}
|
||||
|
||||
pub fn items_mut(&self) -> RwLockWriteGuard<'_, Vec<Value>> {
|
||||
self.0.write().unwrap()
|
||||
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()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,42 +86,34 @@ impl Eq for List {}
|
||||
|
||||
impl PartialEq for List {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
let left = self.0.read().unwrap().clone().into_iter();
|
||||
let right = other.0.read().unwrap().clone().into_iter();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
left.eq(right)
|
||||
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 {
|
||||
let left = self.0.read().unwrap().clone().into_iter();
|
||||
let right = other.0.read().unwrap().clone().into_iter();
|
||||
|
||||
left.cmp(right)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for List {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for List {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let items = self.items();
|
||||
|
||||
write!(f, "[")?;
|
||||
|
||||
for (index, value) in items.iter().enumerate() {
|
||||
write!(f, "{value}")?;
|
||||
|
||||
if index != items.len() - 1 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
if let (Ok(left), Ok(right)) = (self.items(), other.items()) {
|
||||
left.cmp(&right)
|
||||
} else {
|
||||
Ordering::Equal
|
||||
}
|
||||
|
||||
write!(f, "]")
|
||||
}
|
||||
}
|
||||
|
196
src/value/map.rs
196
src/value/map.rs
@ -1,82 +1,77 @@
|
||||
use serde::{
|
||||
de::{MapAccess, Visitor},
|
||||
ser::SerializeMap,
|
||||
Deserialize, Serialize,
|
||||
use serde::{Deserialize, Serialize};
|
||||
use stanza::{
|
||||
renderer::{console::Console, Renderer},
|
||||
style::{HAlign, Styles},
|
||||
table::{Row, Table},
|
||||
};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::BTreeMap,
|
||||
fmt::{self, Display, Formatter},
|
||||
marker::PhantomData,
|
||||
sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard},
|
||||
};
|
||||
|
||||
use crate::{value::Value, Result, Type};
|
||||
use crate::{Identifier, Value};
|
||||
|
||||
/// A collection dust variables comprised of key-value pairs.
|
||||
///
|
||||
/// The inner value is a BTreeMap in order to allow VariableMap instances to be sorted and compared
|
||||
/// to one another.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct Map {
|
||||
variables: Arc<RwLock<BTreeMap<String, (Value, Type)>>>,
|
||||
inner: BTreeMap<Identifier, Value>,
|
||||
}
|
||||
|
||||
impl Map {
|
||||
/// Creates a new instace.
|
||||
pub fn new() -> Self {
|
||||
Map {
|
||||
variables: Arc::new(RwLock::new(BTreeMap::new())),
|
||||
inner: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clone_from(other: &Self) -> Result<Self> {
|
||||
let mut new_map = BTreeMap::new();
|
||||
pub fn with_values(variables: BTreeMap<Identifier, Value>) -> Self {
|
||||
Map { inner: variables }
|
||||
}
|
||||
|
||||
for (key, (value, r#type)) in other.variables()?.iter() {
|
||||
new_map.insert(key.clone(), (value.clone(), r#type.clone()));
|
||||
pub fn inner(&self) -> &BTreeMap<Identifier, Value> {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &Identifier) -> Option<&Value> {
|
||||
self.inner.get(key)
|
||||
}
|
||||
|
||||
pub fn set(&mut self, key: Identifier, value: Value) {
|
||||
self.inner.insert(key, value);
|
||||
}
|
||||
|
||||
pub fn as_text_table(&self) -> Table {
|
||||
let mut table = Table::with_styles(Styles::default().with(HAlign::Centred));
|
||||
|
||||
for (key, value) in &self.inner {
|
||||
if let Value::Map(map) = value {
|
||||
table.push_row(Row::new(
|
||||
Styles::default(),
|
||||
vec![
|
||||
key.into(),
|
||||
map.as_text_table().into(),
|
||||
"".to_string().into(),
|
||||
],
|
||||
));
|
||||
} else if let Value::List(list) = value {
|
||||
table.push_row(Row::new(
|
||||
Styles::default(),
|
||||
vec![key.into(), list.as_text_table().into()],
|
||||
));
|
||||
} else {
|
||||
table.push_row([key.to_string(), value.to_string()]);
|
||||
};
|
||||
}
|
||||
|
||||
Ok(Map {
|
||||
variables: Arc::new(RwLock::new(new_map)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn variables(&self) -> Result<RwLockReadGuard<BTreeMap<String, (Value, Type)>>> {
|
||||
Ok(self.variables.read()?)
|
||||
}
|
||||
|
||||
pub fn variables_mut(&self) -> Result<RwLockWriteGuard<BTreeMap<String, (Value, Type)>>> {
|
||||
Ok(self.variables.write()?)
|
||||
}
|
||||
|
||||
pub fn set(
|
||||
&self,
|
||||
key: String,
|
||||
value: Value,
|
||||
r#type: Option<Type>,
|
||||
) -> Result<Option<(Value, Type)>> {
|
||||
let value_type = r#type.unwrap_or(value.r#type());
|
||||
let previous = self
|
||||
.variables
|
||||
.write()?
|
||||
.insert(key, (value, value_type.clone()));
|
||||
|
||||
Ok(previous)
|
||||
}
|
||||
|
||||
pub fn unset_all(&self) -> Result<()> {
|
||||
for (_key, (value, r#_type)) in self.variables.write()?.iter_mut() {
|
||||
*value = Value::none();
|
||||
if table.is_empty() {
|
||||
table.push_row(vec!["", "empty map", ""])
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn clear(&self) -> Result<()> {
|
||||
self.variables.write()?.clear();
|
||||
|
||||
Ok(())
|
||||
table
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,101 +81,10 @@ impl Default for Map {
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Map {}
|
||||
|
||||
impl PartialEq for Map {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
let left = self.variables.read().unwrap().clone().into_iter();
|
||||
let right = other.variables.read().unwrap().clone().into_iter();
|
||||
|
||||
left.eq(right)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Map {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
let left = self.variables.read().unwrap().clone().into_iter();
|
||||
let right = other.variables.read().unwrap().clone().into_iter();
|
||||
|
||||
left.cmp(right)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Map {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Map {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "{{")?;
|
||||
let renderer = Console::default();
|
||||
|
||||
let variables = self.variables.read().unwrap().clone().into_iter();
|
||||
|
||||
for (key, (value, _)) in variables {
|
||||
writeln!(f, " {key} = {value}")?;
|
||||
}
|
||||
write!(f, "}}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Map {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let variables = self.variables.read().unwrap();
|
||||
let mut map = serializer.serialize_map(Some(variables.len()))?;
|
||||
|
||||
for (key, (value, _type)) in variables.iter() {
|
||||
map.serialize_entry(key, value)?;
|
||||
}
|
||||
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
struct MapVisitor {
|
||||
marker: PhantomData<fn() -> Map>,
|
||||
}
|
||||
|
||||
impl MapVisitor {
|
||||
fn new() -> Self {
|
||||
MapVisitor {
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Visitor<'de> for MapVisitor {
|
||||
type Value = Map;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
|
||||
formatter.write_str("Any valid whale data.")
|
||||
}
|
||||
|
||||
fn visit_map<M>(self, mut access: M) -> std::result::Result<Map, M::Error>
|
||||
where
|
||||
M: MapAccess<'de>,
|
||||
{
|
||||
let map = Map::new();
|
||||
|
||||
{
|
||||
while let Some((key, value)) = access.next_entry::<String, Value>()? {
|
||||
map.set(key, value, None).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Map {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(MapVisitor::new())
|
||||
f.write_str(&renderer.render(&self.as_text_table()))
|
||||
}
|
||||
}
|
||||
|
562
src/value/mod.rs
562
src/value/mod.rs
@ -1,27 +1,35 @@
|
||||
//! Types that represent runtime values.
|
||||
use crate::{
|
||||
error::{Error, Result},
|
||||
Function, Identifier, List, Map, Type, TypeDefinition,
|
||||
built_in_values::BuiltInValue,
|
||||
error::{rw_lock_error::RwLockError, RuntimeError, ValidationError},
|
||||
Identifier, SourcePosition, Type,
|
||||
};
|
||||
|
||||
use serde::{
|
||||
de::{MapAccess, SeqAccess, Visitor},
|
||||
ser::SerializeTuple,
|
||||
ser::{SerializeMap, SerializeTuple},
|
||||
Deserialize, Serialize, Serializer,
|
||||
};
|
||||
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::BTreeMap,
|
||||
convert::TryFrom,
|
||||
fmt::{self, Display, Formatter},
|
||||
marker::PhantomData,
|
||||
ops::{Add, AddAssign, Div, Mul, Rem, Sub, SubAssign},
|
||||
sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard},
|
||||
ops::RangeInclusive,
|
||||
};
|
||||
|
||||
pub use self::{
|
||||
enum_instance::EnumInstance, function::Function, list::List, map::Map,
|
||||
struct_instance::StructInstance,
|
||||
};
|
||||
|
||||
pub mod enum_instance;
|
||||
pub mod function;
|
||||
pub mod list;
|
||||
pub mod map;
|
||||
pub mod struct_instance;
|
||||
|
||||
/// Dust value representation.
|
||||
///
|
||||
@ -30,89 +38,87 @@ pub mod map;
|
||||
/// value that can be treated as any other.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Value {
|
||||
Boolean(bool),
|
||||
Enum(EnumInstance),
|
||||
Float(f64),
|
||||
Function(Function),
|
||||
Integer(i64),
|
||||
List(List),
|
||||
Map(Map),
|
||||
Function(Function),
|
||||
String(Arc<RwLock<String>>),
|
||||
Float(f64),
|
||||
Integer(i64),
|
||||
Boolean(bool),
|
||||
Option(Option<Box<Value>>),
|
||||
}
|
||||
|
||||
impl Default for Value {
|
||||
fn default() -> Self {
|
||||
Value::none()
|
||||
}
|
||||
Range(RangeInclusive<i64>),
|
||||
String(String),
|
||||
Struct(StructInstance),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn string<T: Into<String>>(string: T) -> Self {
|
||||
Value::String(Arc::new(RwLock::new(string.into())))
|
||||
pub fn none() -> Self {
|
||||
BuiltInValue::None.get().clone()
|
||||
}
|
||||
|
||||
pub fn r#type(&self) -> Type {
|
||||
pub fn some(value: Value) -> Value {
|
||||
Value::Enum(EnumInstance::new(
|
||||
Identifier::new("Option"),
|
||||
Identifier::new("Some"),
|
||||
Some(value),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn string<T: Into<String>>(string: T) -> Self {
|
||||
Value::String(string.into())
|
||||
}
|
||||
|
||||
pub fn range(start: i64, end: i64) -> Self {
|
||||
Value::Range(start..=end)
|
||||
}
|
||||
|
||||
pub fn r#type(&self) -> Result<Type, RwLockError> {
|
||||
let r#type = match self {
|
||||
Value::List(list) => {
|
||||
let mut previous_type = None;
|
||||
let mut item_types = Vec::new();
|
||||
|
||||
for value in list.items().iter() {
|
||||
let value_type = value.r#type();
|
||||
for value in list.items()?.iter() {
|
||||
let r#type = value.r#type()?;
|
||||
|
||||
if let Some(previous) = &previous_type {
|
||||
if &value_type != previous {
|
||||
return Type::List(Box::new(Type::Any));
|
||||
}
|
||||
}
|
||||
|
||||
previous_type = Some(value_type);
|
||||
item_types.push(r#type);
|
||||
}
|
||||
|
||||
if let Some(previous) = previous_type {
|
||||
Type::List(Box::new(previous))
|
||||
} else {
|
||||
Type::List(Box::new(Type::Any))
|
||||
}
|
||||
Type::ListExact(item_types)
|
||||
}
|
||||
Value::Map(map) => {
|
||||
let mut identifier_types = Vec::new();
|
||||
if map.inner().is_empty() {
|
||||
Type::Map(None)
|
||||
} else {
|
||||
let mut type_map = BTreeMap::new();
|
||||
|
||||
for (key, (value, _)) in map.variables().unwrap().iter() {
|
||||
identifier_types.push((
|
||||
Identifier::new(key.clone()),
|
||||
TypeDefinition::new(value.r#type()),
|
||||
));
|
||||
for (identifier, value) in map.inner() {
|
||||
type_map.insert(identifier.clone(), value.r#type()?);
|
||||
}
|
||||
|
||||
Type::Map(Some(type_map))
|
||||
}
|
||||
|
||||
Type::Map(identifier_types)
|
||||
}
|
||||
Value::Function(function) => function.r#type().clone(),
|
||||
Value::String(_) => Type::String,
|
||||
Value::Float(_) => Type::Float,
|
||||
Value::Integer(_) => Type::Integer,
|
||||
Value::Boolean(_) => Type::Boolean,
|
||||
Value::Option(option) => {
|
||||
if let Some(value) = option {
|
||||
Type::Option(Box::new(value.r#type()))
|
||||
Value::Range(_) => todo!(),
|
||||
Value::Struct(_) => todo!(),
|
||||
Value::Enum(enum_instance) => {
|
||||
let arguments = if let Some(value) = enum_instance.value() {
|
||||
vec![value.r#type()?]
|
||||
} else {
|
||||
Type::None
|
||||
Vec::with_capacity(0)
|
||||
};
|
||||
|
||||
Type::Custom {
|
||||
name: enum_instance.name().clone(),
|
||||
arguments,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
r#type
|
||||
}
|
||||
|
||||
pub fn none() -> Self {
|
||||
Value::Option(None)
|
||||
}
|
||||
|
||||
pub fn some(value: Value) -> Self {
|
||||
Value::Option(Some(Box::new(value)))
|
||||
}
|
||||
|
||||
pub fn option(option: Option<Value>) -> Self {
|
||||
Value::Option(option.map(|value| Box::new(value)))
|
||||
Ok(r#type)
|
||||
}
|
||||
|
||||
pub fn is_string(&self) -> bool {
|
||||
@ -139,109 +145,105 @@ impl Value {
|
||||
matches!(self, Value::List(_))
|
||||
}
|
||||
|
||||
pub fn is_option(&self) -> bool {
|
||||
matches!(self, Value::Option(_))
|
||||
}
|
||||
|
||||
pub fn is_none(&self) -> bool {
|
||||
matches!(self, Value::Option(None))
|
||||
}
|
||||
|
||||
pub fn is_map(&self) -> bool {
|
||||
matches!(self, Value::Map(_))
|
||||
}
|
||||
|
||||
pub fn is_function(&self) -> bool {
|
||||
matches!(self, Value::Map(_))
|
||||
matches!(self, Value::Function(_))
|
||||
}
|
||||
|
||||
/// Borrows the value stored in `self` as `&str`, or returns `Err` if `self` is not a `Value::String`.
|
||||
pub fn as_string(&self) -> Result<RwLockReadGuard<String>> {
|
||||
pub fn is_none(&self) -> bool {
|
||||
self == &Value::none()
|
||||
}
|
||||
|
||||
/// Borrows the value stored in `self` as `&String`, or returns `Err` if
|
||||
/// `self` is not a `Value::String`.
|
||||
pub fn as_string(&self) -> Result<&String, ValidationError> {
|
||||
match self {
|
||||
Value::String(string) => Ok(string.read()?),
|
||||
value => Err(Error::ExpectedString {
|
||||
Value::String(string) => Ok(string),
|
||||
value => Err(ValidationError::ExpectedString {
|
||||
actual: value.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Borrows the value stored in `self` as `String`, or returns `Err` if `self` is not a `Value::String`.
|
||||
pub fn as_string_mut(&self) -> Result<RwLockWriteGuard<String>> {
|
||||
match self {
|
||||
Value::String(string) => Ok(string.write()?),
|
||||
value => Err(Error::ExpectedString {
|
||||
actual: value.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Copies the value stored in `self` as `i64`, or returns `Err` if `self` is not a `Value::Int`.
|
||||
pub fn as_integer(&self) -> Result<i64> {
|
||||
/// Copies the value stored in `self` as `i64`, or returns `Err` if `self`
|
||||
/// is not a `Value::Int`
|
||||
pub fn as_integer(&self) -> Result<i64, ValidationError> {
|
||||
match self {
|
||||
Value::Integer(i) => Ok(*i),
|
||||
value => Err(Error::ExpectedInteger {
|
||||
value => Err(ValidationError::ExpectedInteger {
|
||||
actual: value.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Copies the value stored in `self` as `f64`, or returns `Err` if `self` is not a `Primitive::Float`.
|
||||
pub fn as_float(&self) -> Result<f64> {
|
||||
/// Copies the value stored in `self` as `f64`, or returns `Err` if `self`
|
||||
/// is not a `Primitive::Float`.
|
||||
pub fn as_float(&self) -> Result<f64, ValidationError> {
|
||||
match self {
|
||||
Value::Float(f) => Ok(*f),
|
||||
value => Err(Error::ExpectedFloat {
|
||||
value => Err(ValidationError::ExpectedFloat {
|
||||
actual: value.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Copies the value stored in `self` as `f64`, or returns `Err` if `self` is not a `Primitive::Float` or `Value::Int`.
|
||||
/// Note that this method silently converts `i64` to `f64`, if `self` is a `Value::Int`.
|
||||
pub fn as_number(&self) -> Result<f64> {
|
||||
/// Copies the value stored in `self` as `f64`, or returns `Err` if `self`
|
||||
/// is not a `Primitive::Float` or `Value::Int`.
|
||||
///
|
||||
/// Note that this method silently converts `i64` to `f64`, if `self` is
|
||||
/// a `Value::Int`.
|
||||
pub fn as_number(&self) -> Result<f64, ValidationError> {
|
||||
match self {
|
||||
Value::Float(f) => Ok(*f),
|
||||
Value::Integer(i) => Ok(*i as f64),
|
||||
value => Err(Error::ExpectedNumber {
|
||||
value => Err(ValidationError::ExpectedNumber {
|
||||
actual: value.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Copies the value stored in `self` as `bool`, or returns `Err` if `self` is not a `Primitive::Boolean`.
|
||||
pub fn as_boolean(&self) -> Result<bool> {
|
||||
/// Copies the value stored in `self` as `bool`, or returns `Err` if `self`
|
||||
/// is not a `Primitive::Boolean`.
|
||||
pub fn as_boolean(&self) -> Result<bool, ValidationError> {
|
||||
match self {
|
||||
Value::Boolean(boolean) => Ok(*boolean),
|
||||
value => Err(Error::ExpectedBoolean {
|
||||
value => Err(ValidationError::ExpectedBoolean {
|
||||
actual: value.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Borrows the value stored in `self` as `Vec<Value>`, or returns `Err` if `self` is not a `Value::List`.
|
||||
pub fn as_list(&self) -> Result<&List> {
|
||||
/// Borrows the value stored in `self` as `Vec<Value>`, or returns `Err` if
|
||||
/// `self` is not a `Value::List`.
|
||||
pub fn as_list(&self) -> Result<&List, ValidationError> {
|
||||
match self {
|
||||
Value::List(list) => Ok(list),
|
||||
value => Err(Error::ExpectedList {
|
||||
value => Err(ValidationError::ExpectedList {
|
||||
actual: value.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes ownership of the value stored in `self` as `Vec<Value>`, or returns `Err` if `self` is not a `Value::List`.
|
||||
pub fn into_inner_list(self) -> Result<List> {
|
||||
/// Takes ownership of the value stored in `self` as `Vec<Value>`, or
|
||||
/// returns `Err` if `self` is not a `Value::List`.
|
||||
pub fn into_inner_list(self) -> Result<List, ValidationError> {
|
||||
match self {
|
||||
Value::List(list) => Ok(list),
|
||||
value => Err(Error::ExpectedList {
|
||||
value => Err(ValidationError::ExpectedList {
|
||||
actual: value.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Borrows the value stored in `self` as `Vec<Value>`, or returns `Err` if `self` is not a `Value::Map`.
|
||||
pub fn as_map(&self) -> Result<&Map> {
|
||||
/// Borrows the value stored in `self` as `Vec<Value>`, or returns `Err` if
|
||||
/// `self` is not a `Value::Map`.
|
||||
pub fn as_map(&self) -> Result<&Map, ValidationError> {
|
||||
match self {
|
||||
Value::Map(map) => Ok(map),
|
||||
value => Err(Error::ExpectedMap {
|
||||
value => Err(ValidationError::ExpectedMap {
|
||||
actual: value.clone(),
|
||||
}),
|
||||
}
|
||||
@ -249,183 +251,104 @@ impl Value {
|
||||
|
||||
/// Borrows the value stored in `self` as `Function`, or returns `Err` if
|
||||
/// `self` is not a `Value::Function`.
|
||||
pub fn as_function(&self) -> Result<&Function> {
|
||||
pub fn as_function(&self) -> Result<&Function, ValidationError> {
|
||||
match self {
|
||||
Value::Function(function) => Ok(function),
|
||||
value => Err(Error::ExpectedFunction {
|
||||
value => Err(ValidationError::ExpectedFunction {
|
||||
actual: value.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Option`, or returns `Err` if `self` is not a `Value::Option`.
|
||||
pub fn as_option(&self) -> Result<&Option<Box<Value>>> {
|
||||
match self {
|
||||
Value::Option(option) => Ok(option),
|
||||
value => Err(Error::ExpectedOption {
|
||||
actual: value.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `()`, or returns `Err` if `self` is not a `Value::none()`.
|
||||
pub fn as_none(&self) -> Result<()> {
|
||||
match self {
|
||||
Value::Option(option) => {
|
||||
if option.is_none() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::ExpectedNone {
|
||||
actual: self.clone(),
|
||||
})
|
||||
}
|
||||
/// Return the sum of `self` and `other`.
|
||||
pub fn add(self, other: Self, position: SourcePosition) -> Result<Value, ValidationError> {
|
||||
match (self, other) {
|
||||
(Value::Float(left), Value::Float(right)) => Ok(Value::Float(left + right)),
|
||||
(Value::Float(left), Value::Integer(right)) => Ok(Value::Float(left + right as f64)),
|
||||
(Value::Integer(left), Value::Float(right)) => Ok(Value::Float((left as f64) + right)),
|
||||
(Value::Integer(left), Value::Integer(right)) => {
|
||||
Ok(Value::Integer(left.saturating_add(right)))
|
||||
}
|
||||
value => Err(Error::ExpectedNone {
|
||||
actual: value.clone(),
|
||||
(Value::List(list), value) | (value, Value::List(list)) => {
|
||||
list.items_mut()?.push(value);
|
||||
|
||||
Ok(Value::List(list))
|
||||
}
|
||||
(Value::String(left), Value::String(right)) => Ok(Value::String(left + &right)),
|
||||
(left, right) => Err(ValidationError::CannotAdd {
|
||||
left,
|
||||
right,
|
||||
position,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the difference of `self` and `other`.
|
||||
pub fn subtract(self, other: Self, position: SourcePosition) -> Result<Value, ValidationError> {
|
||||
match (self, other) {
|
||||
(Value::Float(left), Value::Float(right)) => Ok(Value::Float(left - right)),
|
||||
(Value::Float(left), Value::Integer(right)) => Ok(Value::Float(left - right as f64)),
|
||||
(Value::Integer(left), Value::Float(right)) => Ok(Value::Float(left as f64 - right)),
|
||||
(Value::Integer(left), Value::Integer(right)) => {
|
||||
Ok(Value::Integer(left.saturating_sub(right)))
|
||||
}
|
||||
(left, right) => Err(ValidationError::CannotSubtract {
|
||||
left,
|
||||
right,
|
||||
position,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the product of `self` and `other`.
|
||||
pub fn multiply(self, other: Self, position: SourcePosition) -> Result<Value, ValidationError> {
|
||||
match (self, other) {
|
||||
(Value::Float(left), Value::Float(right)) => Ok(Value::Float(left * right)),
|
||||
(Value::Float(left), Value::Integer(right)) => Ok(Value::Float(left * right as f64)),
|
||||
(Value::Integer(left), Value::Float(right)) => Ok(Value::Float(left as f64 * right)),
|
||||
(Value::Integer(left), Value::Integer(right)) => Ok(Value::Integer(left * right)),
|
||||
(left, right) => Err(ValidationError::CannotMultiply {
|
||||
left,
|
||||
right,
|
||||
position,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the quotient of `self` and `other`.
|
||||
pub fn divide(self, other: Self, position: SourcePosition) -> Result<Value, ValidationError> {
|
||||
match (self, other) {
|
||||
(Value::Float(left), Value::Float(right)) => Ok(Value::Float(left / right)),
|
||||
(Value::Float(left), Value::Integer(right)) => Ok(Value::Float(left / right as f64)),
|
||||
(Value::Integer(left), Value::Float(right)) => Ok(Value::Float(left as f64 / right)),
|
||||
(Value::Integer(left), Value::Integer(right)) => Ok(Value::Integer(left / right)),
|
||||
(left, right) => Err(ValidationError::CannotDivide {
|
||||
left,
|
||||
right,
|
||||
position,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the remainder after diving `self` and `other`.
|
||||
pub fn modulo(self, other: Self, position: SourcePosition) -> Result<Value, ValidationError> {
|
||||
match (self, other) {
|
||||
(Value::Float(left), Value::Float(right)) => Ok(Value::Float(left % right)),
|
||||
(Value::Float(left), Value::Integer(right)) => Ok(Value::Float(left % right as f64)),
|
||||
(Value::Integer(left), Value::Float(right)) => Ok(Value::Float(left as f64 % right)),
|
||||
(Value::Integer(left), Value::Integer(right)) => Ok(Value::Integer(left % right)),
|
||||
(left, right) => Err(ValidationError::CannotDivide {
|
||||
left,
|
||||
right,
|
||||
position,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for &Value {
|
||||
impl Default for Value {
|
||||
fn default() -> Self {
|
||||
&Value::Option(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Value {
|
||||
type Output = Result<Value>;
|
||||
|
||||
fn add(self, other: Self) -> Self::Output {
|
||||
if let (Ok(left), Ok(right)) = (self.as_integer(), other.as_integer()) {
|
||||
return Ok(Value::Integer(left + right));
|
||||
}
|
||||
|
||||
if let (Ok(left), Ok(right)) = (self.as_number(), other.as_number()) {
|
||||
return Ok(Value::Float(left + right));
|
||||
}
|
||||
|
||||
if let (Ok(left), Ok(right)) = (self.as_string(), other.as_string()) {
|
||||
return Ok(Value::string(left.to_string() + right.as_str()));
|
||||
}
|
||||
|
||||
if self.is_string() || other.is_string() {
|
||||
return Ok(Value::string(self.to_string() + &other.to_string()));
|
||||
}
|
||||
|
||||
let non_number_or_string = if !self.is_number() == !self.is_string() {
|
||||
self
|
||||
} else {
|
||||
other
|
||||
};
|
||||
|
||||
Err(Error::ExpectedNumberOrString {
|
||||
actual: non_number_or_string,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Value {
|
||||
type Output = Result<Self>;
|
||||
|
||||
fn sub(self, other: Self) -> Self::Output {
|
||||
if let (Ok(left), Ok(right)) = (self.as_integer(), other.as_integer()) {
|
||||
return Ok(Value::Integer(left - right));
|
||||
}
|
||||
|
||||
if let (Ok(left), Ok(right)) = (self.as_number(), other.as_number()) {
|
||||
return Ok(Value::Float(left - right));
|
||||
}
|
||||
|
||||
let non_number = if !self.is_number() { self } else { other };
|
||||
|
||||
Err(Error::ExpectedNumber { actual: non_number })
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul for Value {
|
||||
type Output = Result<Self>;
|
||||
|
||||
fn mul(self, other: Self) -> Self::Output {
|
||||
if let (Ok(left), Ok(right)) = (self.as_integer(), other.as_integer()) {
|
||||
Ok(Value::Integer(left.saturating_mul(right)))
|
||||
} else if let (Ok(left), Ok(right)) = (self.as_number(), other.as_number()) {
|
||||
Ok(Value::Float(left * right))
|
||||
} else {
|
||||
let non_number = if !self.is_number() { self } else { other };
|
||||
|
||||
Err(Error::ExpectedNumber { actual: non_number })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Div for Value {
|
||||
type Output = Result<Self>;
|
||||
|
||||
fn div(self, other: Self) -> Self::Output {
|
||||
if let (Ok(left), Ok(right)) = (self.as_number(), other.as_number()) {
|
||||
let divided = left / right;
|
||||
let is_even = divided % 2.0 == 0.0;
|
||||
|
||||
if self.is_integer() && other.is_integer() && is_even {
|
||||
Ok(Value::Integer(divided as i64))
|
||||
} else {
|
||||
Ok(Value::Float(divided))
|
||||
}
|
||||
} else {
|
||||
let non_number = if !self.is_number() { self } else { other };
|
||||
|
||||
Err(Error::ExpectedNumber { actual: non_number })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Rem for Value {
|
||||
type Output = Result<Self>;
|
||||
|
||||
fn rem(self, other: Self) -> Self::Output {
|
||||
let left = self.as_integer()?;
|
||||
let right = other.as_integer()?;
|
||||
let result = left % right;
|
||||
|
||||
Ok(Value::Integer(result))
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Value {
|
||||
fn add_assign(&mut self, other: Self) {
|
||||
match (self, other) {
|
||||
(Value::Integer(left), Value::Integer(right)) => *left += right,
|
||||
(Value::Float(left), Value::Float(right)) => *left += right,
|
||||
(Value::Float(left), Value::Integer(right)) => *left += right as f64,
|
||||
(Value::String(left), Value::String(right)) => {
|
||||
*left.write().unwrap() += right.read().unwrap().as_str()
|
||||
}
|
||||
(Value::List(list), value) => list.items_mut().push(value),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign for Value {
|
||||
fn sub_assign(&mut self, other: Self) {
|
||||
match (self, other) {
|
||||
(Value::Integer(left), Value::Integer(right)) => *left -= right,
|
||||
(Value::Float(left), Value::Float(right)) => *left -= right,
|
||||
(Value::Float(left), Value::Integer(right)) => *left -= right as f64,
|
||||
(Value::List(list), value) => {
|
||||
let index_to_remove = list
|
||||
.items()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(i, list_value)| if list_value == &value { Some(i) } else { None });
|
||||
|
||||
if let Some(index) = index_to_remove {
|
||||
list.items_mut().remove(index);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Value::none()
|
||||
}
|
||||
}
|
||||
|
||||
@ -437,13 +360,13 @@ impl PartialEq for Value {
|
||||
(Value::Integer(left), Value::Integer(right)) => left == right,
|
||||
(Value::Float(left), Value::Float(right)) => left == right,
|
||||
(Value::Boolean(left), Value::Boolean(right)) => left == right,
|
||||
(Value::String(left), Value::String(right)) => {
|
||||
left.read().unwrap().as_str() == right.read().unwrap().as_str()
|
||||
}
|
||||
(Value::String(left), Value::String(right)) => left == right,
|
||||
(Value::List(left), Value::List(right)) => left == right,
|
||||
(Value::Map(left), Value::Map(right)) => left == right,
|
||||
(Value::Function(left), Value::Function(right)) => left == right,
|
||||
(Value::Option(left), Value::Option(right)) => left == right,
|
||||
(Value::Range(left), Value::Range(right)) => left == right,
|
||||
(Value::Struct(left), Value::Struct(right)) => left == right,
|
||||
(Value::Enum(left), Value::Enum(right)) => left == right,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -458,11 +381,7 @@ impl PartialOrd for Value {
|
||||
impl Ord for Value {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
match (self, other) {
|
||||
(Value::String(left), Value::String(right)) => left
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.cmp(right.read().unwrap().as_str()),
|
||||
(Value::String(left), Value::String(right)) => left.cmp(right),
|
||||
(Value::String(_), _) => Ordering::Greater,
|
||||
(Value::Float(left), Value::Float(right)) => left.total_cmp(right),
|
||||
(Value::Integer(left), Value::Integer(right)) => left.cmp(right),
|
||||
@ -484,8 +403,17 @@ impl Ord for Value {
|
||||
(Value::Map(_), _) => Ordering::Greater,
|
||||
(Value::Function(left), Value::Function(right)) => left.cmp(right),
|
||||
(Value::Function(_), _) => Ordering::Greater,
|
||||
(Value::Option(left), Value::Option(right)) => left.cmp(right),
|
||||
(Value::Option(_), _) => Ordering::Less,
|
||||
(Value::Struct(left), Value::Struct(right)) => left.cmp(right),
|
||||
(Value::Struct(_), _) => Ordering::Greater,
|
||||
(Value::Enum(left), Value::Enum(right)) => left.cmp(right),
|
||||
(Value::Enum(_), _) => Ordering::Greater,
|
||||
(Value::Range(left), Value::Range(right)) => {
|
||||
let left_len = left.end() - left.start();
|
||||
let right_len = right.end() - right.start();
|
||||
|
||||
left_len.cmp(&right_len)
|
||||
}
|
||||
(Value::Range(_), _) => Ordering::Less,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -496,12 +424,17 @@ impl Serialize for Value {
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
Value::String(inner) => serializer.serialize_str(inner.read().unwrap().as_str()),
|
||||
Value::String(inner) => serializer.serialize_str(inner),
|
||||
Value::Float(inner) => serializer.serialize_f64(*inner),
|
||||
Value::Integer(inner) => serializer.serialize_i64(*inner),
|
||||
Value::Boolean(inner) => serializer.serialize_bool(*inner),
|
||||
Value::List(inner) => {
|
||||
let items = inner.items();
|
||||
let items = if let Ok(items) = inner.items() {
|
||||
items
|
||||
} else {
|
||||
return Err(serde::ser::Error::custom("failed to obtain a read lock"));
|
||||
};
|
||||
|
||||
let mut list = serializer.serialize_tuple(items.len())?;
|
||||
|
||||
for value in items.iter() {
|
||||
@ -510,9 +443,20 @@ impl Serialize for Value {
|
||||
|
||||
list.end()
|
||||
}
|
||||
Value::Option(inner) => inner.serialize(serializer),
|
||||
Value::Map(inner) => inner.serialize(serializer),
|
||||
Value::Map(map) => {
|
||||
let entries = map.inner();
|
||||
let mut map = serializer.serialize_map(Some(entries.len()))?;
|
||||
|
||||
for (key, value) in entries.iter() {
|
||||
map.serialize_entry(key, value)?;
|
||||
}
|
||||
|
||||
map.end()
|
||||
}
|
||||
Value::Function(inner) => inner.serialize(serializer),
|
||||
Value::Struct(inner) => inner.serialize(serializer),
|
||||
Value::Range(range) => range.serialize(serializer),
|
||||
Value::Enum(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -520,20 +464,16 @@ impl Serialize for Value {
|
||||
impl Display for Value {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Value::String(string) => write!(f, "{}", string.read().unwrap()),
|
||||
Value::String(string) => write!(f, "{string}"),
|
||||
Value::Float(float) => write!(f, "{float}"),
|
||||
Value::Integer(int) => write!(f, "{int}"),
|
||||
Value::Boolean(boolean) => write!(f, "{boolean}"),
|
||||
Value::Option(option) => {
|
||||
if let Some(value) = option {
|
||||
write!(f, "some({})", value)
|
||||
} else {
|
||||
write!(f, "none")
|
||||
}
|
||||
}
|
||||
Value::List(list) => write!(f, "{list}"),
|
||||
Value::Map(map) => write!(f, "{map}"),
|
||||
Value::Function(function) => write!(f, "{function}"),
|
||||
Value::Struct(structure) => write!(f, "{structure}"),
|
||||
Value::Range(range) => write!(f, "{}..{}", range.start(), range.end()),
|
||||
Value::Enum(enum_instance) => write!(f, "{enum_instance}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -574,7 +514,7 @@ impl From<Vec<Value>> for Value {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Value> for Result<Value> {
|
||||
impl From<Value> for Result<Value, RuntimeError> {
|
||||
fn from(value: Value) -> Self {
|
||||
Ok(value)
|
||||
}
|
||||
@ -587,49 +527,57 @@ impl From<()> for Value {
|
||||
}
|
||||
|
||||
impl TryFrom<Value> for String {
|
||||
type Error = Error;
|
||||
type Error = RuntimeError;
|
||||
|
||||
fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
|
||||
if let Value::String(rw_lock) = value {
|
||||
Ok(rw_lock.read()?.to_string())
|
||||
if let Value::String(string) = value {
|
||||
Ok(string)
|
||||
} else {
|
||||
Err(Error::ExpectedString { actual: value })
|
||||
Err(RuntimeError::ValidationFailure(
|
||||
ValidationError::ExpectedString { actual: value },
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Value> for f64 {
|
||||
type Error = Error;
|
||||
type Error = RuntimeError;
|
||||
|
||||
fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
|
||||
if let Value::Float(value) = value {
|
||||
Ok(value)
|
||||
} else {
|
||||
Err(Error::ExpectedFloat { actual: value })
|
||||
Err(RuntimeError::ValidationFailure(
|
||||
ValidationError::ExpectedFloat { actual: value },
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Value> for i64 {
|
||||
type Error = Error;
|
||||
type Error = RuntimeError;
|
||||
|
||||
fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
|
||||
if let Value::Integer(value) = value {
|
||||
Ok(value)
|
||||
} else {
|
||||
Err(Error::ExpectedInteger { actual: value })
|
||||
Err(RuntimeError::ValidationFailure(
|
||||
ValidationError::ExpectedInteger { actual: value },
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Value> for bool {
|
||||
type Error = Error;
|
||||
type Error = RuntimeError;
|
||||
|
||||
fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
|
||||
if let Value::Boolean(value) = value {
|
||||
Ok(value)
|
||||
} else {
|
||||
Err(Error::ExpectedBoolean { actual: value })
|
||||
Err(RuntimeError::ValidationFailure(
|
||||
ValidationError::ExpectedBoolean { actual: value },
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -812,9 +760,7 @@ impl<'de> Visitor<'de> for ValueVisitor {
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
Ok(Value::Option(Some(Box::new(Value::deserialize(
|
||||
deserializer,
|
||||
)?))))
|
||||
Ok(Value::Enum(EnumInstance::deserialize(deserializer)?))
|
||||
}
|
||||
|
||||
fn visit_unit<E>(self) -> std::result::Result<Self::Value, E>
|
||||
@ -852,10 +798,12 @@ impl<'de> Visitor<'de> for ValueVisitor {
|
||||
where
|
||||
M: MapAccess<'de>,
|
||||
{
|
||||
let map = Map::new();
|
||||
let mut map = Map::new();
|
||||
|
||||
while let Some((key, value)) = access.next_entry::<String, Value>()? {
|
||||
map.set(key, value, None).unwrap();
|
||||
let identifier = Identifier::new(&key);
|
||||
|
||||
map.set(identifier, value);
|
||||
}
|
||||
|
||||
Ok(Value::Map(map))
|
||||
|
45
src/value/struct_instance.rs
Normal file
45
src/value/struct_instance.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use serde::{ser::SerializeMap, Serialize, Serializer};
|
||||
|
||||
use crate::{Identifier, Map};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct StructInstance {
|
||||
name: Identifier,
|
||||
map: Map,
|
||||
}
|
||||
|
||||
impl StructInstance {
|
||||
pub fn new(name: Identifier, map: Map) -> Self {
|
||||
StructInstance { name, map }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for StructInstance {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
writeln!(f, "{{")?;
|
||||
|
||||
for (key, value) in self.map.inner() {
|
||||
writeln!(f, " {key} <{}> = {value}", value.r#type().unwrap())?;
|
||||
}
|
||||
|
||||
write!(f, "}}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for StructInstance {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let map = self.map.inner();
|
||||
let mut serde_map = serializer.serialize_map(Some(map.len()))?;
|
||||
|
||||
for (key, value) in map.iter() {
|
||||
serde_map.serialize_entry(key, value)?;
|
||||
}
|
||||
|
||||
serde_map.end()
|
||||
}
|
||||
}
|
53
tests/as.rs
Normal file
53
tests/as.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use dust_lang::{
|
||||
error::{RuntimeError, ValidationError},
|
||||
*,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn string_as_string_list() {
|
||||
assert_eq!(
|
||||
interpret("'foobar' as [str]"),
|
||||
Ok(Value::List(List::with_items(vec![
|
||||
Value::String("f".to_string()),
|
||||
Value::String("o".to_string()),
|
||||
Value::String("o".to_string()),
|
||||
Value::String("b".to_string()),
|
||||
Value::String("a".to_string()),
|
||||
Value::String("r".to_string()),
|
||||
])))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_as_list_error() {
|
||||
assert_eq!(
|
||||
interpret("'foobar' as [float]"),
|
||||
Err(Error::Validation(ValidationError::ConversionImpossible {
|
||||
initial_type: Type::String,
|
||||
target_type: Type::ListOf(Box::new(Type::Float))
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
const JSON: &str = "{ \"x\": 1 }";
|
||||
|
||||
#[test]
|
||||
fn conversion_runtime_error() {
|
||||
let json_value = interpret(&format!("json:parse('{JSON}')")).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
interpret(&format!("json:parse('{JSON}') as [map]")),
|
||||
Err(Error::Runtime(RuntimeError::ConversionImpossible {
|
||||
from: json_value.r#type().unwrap(),
|
||||
to: Type::ListOf(Box::new(Type::Map(None))),
|
||||
position: SourcePosition {
|
||||
start_byte: 0,
|
||||
end_byte: 33,
|
||||
start_row: 1,
|
||||
start_column: 0,
|
||||
end_row: 1,
|
||||
end_column: 33,
|
||||
}
|
||||
}))
|
||||
)
|
||||
}
|
57
tests/assignment.rs
Normal file
57
tests/assignment.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use dust_lang::{error::ValidationError, *};
|
||||
|
||||
#[test]
|
||||
fn simple_assignment() {
|
||||
let test = interpret("x = 1 x");
|
||||
|
||||
assert_eq!(Ok(Value::Integer(1)), test);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_assignment_with_type() {
|
||||
let test = interpret("x <int> = 1 x");
|
||||
|
||||
assert_eq!(Ok(Value::Integer(1)), test);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_add_assign() {
|
||||
let test = interpret(
|
||||
"
|
||||
x <[int]> = []
|
||||
x += 1
|
||||
x
|
||||
",
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Ok(Value::List(List::with_items(vec![Value::Integer(1)]))),
|
||||
test
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_add_wrong_type() {
|
||||
let result = interpret(
|
||||
"
|
||||
x <[str]> = []
|
||||
x += 1
|
||||
",
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Err(Error::Validation(ValidationError::TypeCheck {
|
||||
expected: Type::String,
|
||||
actual: Type::Integer,
|
||||
position: SourcePosition {
|
||||
start_byte: 40,
|
||||
end_byte: 46,
|
||||
start_row: 3,
|
||||
start_column: 12,
|
||||
end_row: 3,
|
||||
end_column: 18
|
||||
}
|
||||
})),
|
||||
result
|
||||
);
|
||||
}
|
32
tests/block.rs
Normal file
32
tests/block.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use dust_lang::*;
|
||||
|
||||
#[test]
|
||||
fn simple() {
|
||||
assert_eq!(interpret("{ 1 }"), Ok(Value::Integer(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested() {
|
||||
assert_eq!(interpret("{ 1 { 1 + 1 } }"), Ok(Value::Integer(2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_return() {
|
||||
assert_eq!(interpret("{ return 1; 1 + 1; }"), Ok(Value::Integer(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn async_with_return() {
|
||||
assert_eq!(
|
||||
interpret(
|
||||
"
|
||||
async {
|
||||
return 1
|
||||
1 + 1
|
||||
3
|
||||
}
|
||||
"
|
||||
),
|
||||
Ok(Value::Integer(1))
|
||||
);
|
||||
}
|
49
tests/built_in_string_functions.rs
Normal file
49
tests/built_in_string_functions.rs
Normal file
@ -0,0 +1,49 @@
|
||||
use dust_lang::{interpret, List, Value};
|
||||
|
||||
#[test]
|
||||
fn as_bytes() {
|
||||
let result = interpret("str:as_bytes('123')");
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Ok(Value::List(List::with_items(vec![
|
||||
Value::Integer(49),
|
||||
Value::Integer(50),
|
||||
Value::Integer(51),
|
||||
])))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ends_with() {
|
||||
let result = interpret("str:ends_with('abc', 'c')");
|
||||
|
||||
assert_eq!(result, Ok(Value::Boolean(true)));
|
||||
|
||||
let result = interpret("str:ends_with('abc', 'b')");
|
||||
|
||||
assert_eq!(result, Ok(Value::Boolean(false)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find() {
|
||||
let result = interpret("str:find('abc', 'a')");
|
||||
|
||||
assert_eq!(result, Ok(Value::some(Value::Integer(0))));
|
||||
|
||||
let result = interpret("str:find('foobar', 'b')");
|
||||
|
||||
assert_eq!(result, Ok(Value::some(Value::Integer(3))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert() {
|
||||
assert_eq!(
|
||||
interpret("str:insert('ac', 1, 'b')"),
|
||||
Ok(Value::String("abc".to_string()))
|
||||
);
|
||||
assert_eq!(
|
||||
interpret("str:insert('foo', 3, 'bar')"),
|
||||
Ok(Value::String("foobar".to_string()))
|
||||
);
|
||||
}
|
71
tests/built_in_type_definitions.rs
Normal file
71
tests/built_in_type_definitions.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use dust_lang::*;
|
||||
|
||||
#[test]
|
||||
fn override_built_ins() {
|
||||
assert_eq!(
|
||||
interpret(
|
||||
"
|
||||
enum Option {
|
||||
Some<str>
|
||||
None
|
||||
}
|
||||
|
||||
my_option <Option> = Option::Some('foo')
|
||||
my_option
|
||||
"
|
||||
),
|
||||
Ok(Value::Enum(EnumInstance::new(
|
||||
Identifier::new("Option"),
|
||||
Identifier::new("Some"),
|
||||
Some(Value::String("foo".to_string())),
|
||||
)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn option() {
|
||||
assert_eq!(
|
||||
interpret("Option::None"),
|
||||
Ok(Value::Enum(EnumInstance::new(
|
||||
Identifier::new("Option"),
|
||||
Identifier::new("None"),
|
||||
Some(Value::none()),
|
||||
)))
|
||||
);
|
||||
assert_eq!(
|
||||
interpret(
|
||||
"
|
||||
Option::Some(1)
|
||||
"
|
||||
),
|
||||
Ok(Value::Enum(EnumInstance::new(
|
||||
Identifier::new("Option"),
|
||||
Identifier::new("Some"),
|
||||
Some(Value::Integer(1)),
|
||||
)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result() {
|
||||
assert_eq!(
|
||||
interpret("Result::Ok(1)"),
|
||||
Ok(Value::Enum(EnumInstance::new(
|
||||
Identifier::new("Result"),
|
||||
Identifier::new("Ok"),
|
||||
Some(Value::Integer(1)),
|
||||
)))
|
||||
);
|
||||
assert_eq!(
|
||||
interpret(
|
||||
"
|
||||
Result::Error('uh-oh!')
|
||||
"
|
||||
),
|
||||
Ok(Value::Enum(EnumInstance::new(
|
||||
Identifier::new("Result"),
|
||||
Identifier::new("Error"),
|
||||
Some(Value::String("uh-oh!".to_string())),
|
||||
)))
|
||||
);
|
||||
}
|
31
tests/built_in_values.rs
Normal file
31
tests/built_in_values.rs
Normal file
@ -0,0 +1,31 @@
|
||||
use dust_lang::{error::RuntimeError, *};
|
||||
|
||||
#[test]
|
||||
fn args() {
|
||||
assert!(interpret("args").is_ok_and(|value| value.is_list()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_equal() {
|
||||
assert_eq!(
|
||||
interpret("assert_equal"),
|
||||
Ok(Value::Function(Function::BuiltIn(
|
||||
BuiltInFunction::AssertEqual
|
||||
)))
|
||||
);
|
||||
assert_eq!(
|
||||
interpret("assert_equal(false, false)"),
|
||||
Ok(Value::Enum(EnumInstance::new(
|
||||
Identifier::new("Result"),
|
||||
Identifier::new("Ok"),
|
||||
Some(Value::none()),
|
||||
)))
|
||||
);
|
||||
assert_eq!(
|
||||
interpret("assert_equal(true, false)"),
|
||||
Err(Error::Runtime(RuntimeError::AssertEqualFailed {
|
||||
left: true.into(),
|
||||
right: false.into()
|
||||
}))
|
||||
);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user