Compare commits

...

84 Commits
as ... main

Author SHA1 Message Date
25e3941315 Clean up examples 2024-02-19 21:19:27 -05:00
900de8ca4b Edit README; Improve bench script; Optimize 2024-02-19 20:44:26 -05:00
c4b51a1ef9 Change README outline; Fix bench script 2024-02-19 19:23:20 -05:00
939b7464c6 Use GitHub theme for example pics 2024-02-19 18:15:03 -05:00
64fb20c45e Use GitHub theme for example pic 2024-02-19 18:05:55 -05:00
e3b55092b3 Increment cargo version 2024-02-19 17:59:16 -05:00
bd4983b821 Write README; Use GitHub theme for example pics 2024-02-19 17:57:25 -05:00
a1500bf262 Write README 2024-02-19 17:22:14 -05:00
1585145ff4 Write docs; Update logging and error messages 2024-02-19 17:04:13 -05:00
fb3cd6e6da Add image to README 2024-02-19 15:35:27 -05:00
69347ad435 Update grammar and highlight queries 2024-02-19 15:26:49 -05:00
ca72fe04f1 Start new example; Start new syntax features 2024-02-19 15:04:33 -05:00
eaf26fec5e Begin reqriting README 2024-02-19 11:24:54 -05:00
0eac67eb3a Pass enum tests 2024-02-19 11:13:04 -05:00
37fd722fa6 Fix garbage collection bug 2024-02-18 16:43:47 -05:00
255843cb3b Fix type checking bugs 2024-02-18 15:52:47 -05:00
927a2cfbf9 Fix tests 2024-02-18 15:44:57 -05:00
88d05f0dc9 Clean up 2024-02-18 15:19:30 -05:00
0805b96809 Add type argument syntax 2024-02-18 15:07:53 -05:00
a5f3127bcf Fix command tests and parsing 2024-02-18 11:38:35 -05:00
01bdaa308d Fix test 2024-02-18 10:59:49 -05:00
dbf9ab0d00 Fix function test 2024-02-18 10:55:54 -05:00
2bbbfa34a4 Roll back changes to match syntax; Fix match tests 2024-02-18 10:53:34 -05:00
979335f497 Modify struct, enum and match syntax 2024-02-18 10:34:59 -05:00
ef12d97895 Fix enum tests 2024-02-18 10:19:38 -05:00
14eedc6a2a Pass enum match test 2024-02-18 08:27:59 -05:00
5559860699 Fix doc tests 2024-02-18 07:14:32 -05:00
3a63d4973d Implement specific map types 2024-02-18 06:48:42 -05:00
52027db6c3 Pass index tests; Begin implementing specific maps 2024-02-18 06:28:31 -05:00
4afc8face8 Partially fix indexes break/return statements 2024-02-18 05:37:15 -05:00
5450f00174 Fix validation bug 2024-02-18 04:57:05 -05:00
a52eadc5ad Pass function tests; Fix recursion 2024-02-18 04:48:45 -05:00
d4a5424ad5 Improve logging 2024-02-18 04:18:19 -05:00
f835f2817f Pass if_else test 2024-02-18 03:49:38 -05:00
86ce1dc3af Pass value tests 2024-02-18 01:50:15 -05:00
ca04103372 Clean up 2024-02-18 00:40:48 -05:00
dab3d2de8e Add test; Make garbage collection work 2024-02-18 00:32:03 -05:00
6c699ec900 Improve context API 2024-02-17 23:43:00 -05:00
4f5ad1e4aa Implement automatic value dropping 2024-02-17 22:02:15 -05:00
d05b5a8628 Run cargo fix 2024-02-16 20:30:58 -05:00
a46d5bb4ea Add fancy validation errors 2024-02-16 20:18:07 -05:00
1094a5662c Simplify errors and make them fancier 2024-02-16 19:57:24 -05:00
fd33f330f7 Clean up errors; Add more pretty errors 2024-02-16 18:54:00 -05:00
bda217135e Simplify errors; Make another pretty error type 2024-02-16 17:56:36 -05:00
ee4f37080e Write function for lyneate integration 2024-02-16 17:28:57 -05:00
7003c37aac Remove redundant check for syntax errors 2024-02-16 17:11:28 -05:00
a53f83f03a Begin making pretty errors with lyneate 2024-02-16 16:49:01 -05:00
4b0910a545 Implement new math interface for Value 2024-02-16 15:37:07 -05:00
c82f631524 Begin new math implementation for Value; Clean up 2024-02-16 15:07:24 -05:00
d27c98e393 Add method to inherit all context data from another 2024-02-16 13:40:55 -05:00
97640c1b9b Fix test 2024-02-16 13:31:35 -05:00
d2e0de0483 Fix function contexts and recursion 2024-02-16 13:23:58 -05:00
7eecb7b070 Fix type checking with None type 2024-02-16 11:36:25 -05:00
1819c7e646 Fix type lookup for built-in values 2024-02-16 11:32:02 -05:00
ee692b360e Implement return for root 2024-02-16 11:23:07 -05:00
8c4b2c9eef Implement block returns 2024-02-16 11:21:36 -05:00
122d81f252 Clean up docs 2024-02-16 11:04:43 -05:00
d8705c5d50 Fix docs 2024-02-16 11:00:27 -05:00
c466096c8d Fix doc tests; Add from impls for Identifier 2024-02-16 10:58:37 -05:00
9f2b0461df Add statment_kind syntax node 2024-02-16 10:55:15 -05:00
1ce2178af5 Move return syntax node to option for statements 2024-02-16 10:38:51 -05:00
172a6fa860 Remove return statement; Add StatementInner 2024-02-16 10:36:16 -05:00
51869f04b6 Add test for root node 2024-02-16 10:23:33 -05:00
50a7a7aca1 Add built-in identifiers 2024-02-15 17:04:34 -05:00
edded5043d Fix infintite loop 2024-02-15 16:30:47 -05:00
5e105177cf Simplify TypeDefinition type 2024-02-15 16:06:47 -05:00
ec074177d5 Clean up new API 2024-02-15 16:02:27 -05:00
c2ba519240 Overhaul built-ins and identifiers 2024-02-15 15:20:29 -05:00
91e94a5adc Clean up 2024-02-15 10:37:10 -05:00
e7f5d66297 Implement custom and built-in types 2024-02-15 10:33:25 -05:00
e1c3e8bc0d Fix test 2024-02-15 07:12:10 -05:00
a6e52e4ee6 Implement matching for enums 2024-02-15 07:04:38 -05:00
85cb641af8 Write docs 2024-02-15 04:18:30 -05:00
933ab3900b Write docs 2024-02-15 04:16:34 -05:00
d9f065fbb6 Write docs 2024-02-15 03:57:13 -05:00
540f59e6d8 Modify enum variant syntax 2024-02-15 02:22:04 -05:00
b8f2fe7eb4 Implement Result type 2024-02-15 02:08:42 -05:00
ed1f139595 Remove option value type and built-in value syntax 2024-02-15 02:02:48 -05:00
4c68bc0260 Add built-in option definition 2024-02-15 01:51:05 -05:00
d3601be44c Fix test 2024-02-15 00:58:14 -05:00
fc3dfc0e03 Implement structs; Modify tests 2024-02-15 00:53:43 -05:00
97319d28b2 Implement custom types; Add test 2024-02-14 22:46:40 -05:00
89a4c09307 Implement basic enum instantiation 2024-02-14 22:38:45 -05:00
b8c54ea8bd Begin implementing enums 2024-02-14 20:53:42 -05:00
115 changed files with 35849 additions and 35373 deletions

30
Cargo.lock generated
View File

@ -263,6 +263,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "colored"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
dependencies = [
"lazy_static",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.4" version = "0.9.4"
@ -359,10 +369,11 @@ dependencies = [
[[package]] [[package]]
name = "dust-lang" name = "dust-lang"
version = "0.4.1" version = "0.4.2"
dependencies = [ dependencies = [
"cc", "cc",
"clap", "clap",
"colored",
"crossterm", "crossterm",
"csv", "csv",
"enum-iterator", "enum-iterator",
@ -371,6 +382,7 @@ dependencies = [
"humantime", "humantime",
"libc", "libc",
"log", "log",
"lyneate",
"nu-ansi-term", "nu-ansi-term",
"rand", "rand",
"rayon", "rayon",
@ -830,6 +842,16 @@ version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "lyneate"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db93f0347ea71252796f5c6a61f2b75ed8434635ab2ad84238837a89125fa5e3"
dependencies = [
"colored",
"widestring",
]
[[package]] [[package]]
name = "malloc_buf" name = "malloc_buf"
version = "0.0.6" version = "0.0.6"
@ -1833,6 +1855,12 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "widestring"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8"
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"

View File

@ -1,7 +1,7 @@
[package] [package]
name = "dust-lang" name = "dust-lang"
description = "General purpose programming language" description = "General purpose programming language"
version = "0.4.1" version = "0.4.2"
repository = "https://git.jeffa.io/jeff/dust.git" repository = "https://git.jeffa.io/jeff/dust.git"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
@ -36,6 +36,8 @@ crossterm = "0.27.0"
nu-ansi-term = "0.49.0" nu-ansi-term = "0.49.0"
humantime = "2.1.0" humantime = "2.1.0"
stanza = "0.5.1" stanza = "0.5.1"
colored = "2.1.0"
lyneate = "0.2.1"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
env_logger = "0.10" env_logger = "0.10"

224
README.md
View File

@ -1,188 +1,100 @@
# Dust # Dust
!!! Dust is an experimental project under active development. !!! High-level programming language with effortless concurrency, automatic memory management, type safety and strict error handling.
Dust is a general purpose programming language that emphasises concurrency and correctness. ![Dust version of an example from The Rust Programming Language.](https://git.jeffa.io/jeff/dust/raw/branch/main/docs/assets/example_0.png)
A basic dust program:
```dust
output("Hello world!")
```
Dust can do two (or more) things at the same time with effortless concurrency:
```dust
async {
output('will this one finish first?')
output('or will this one?')
}
```
You can use Dust to run complex operations simply and safely. You can even invoke other programs, run them at the same time, capture their output, and pipe them together.
```dust
# Run each statment in this block in its own thread.
async {
# Invoke another program and capture its output.
ip_info = ^ip address;
# Pipe the output to another program.
^ls -1 --all --long docs/ | ^rg .md | ^echo;
# This block is not async and the statements will be run in order.
{
file = fs:read_file('Cargo.toml')
# This loop will run each iteration in its own thread. If one of them
# reaches a "break" statement, they will all stop.
async for line in str:lines(file) {
if str:contains(line, 'author') {
output(line)
break
}
}
}
}
```
Dust is an interpreted, strictly typed language with first class functions, embracing 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 aims to be panic-free. That means that the interpreter will only fail to run a program due to an intended error, such as a type error or syntax error. If your program passes the these checks, it will run correctly.
<!--toc:start--> <!--toc:start-->
- [Dust](#dust) - [Dust](#dust)
- [Features](#features) - [Features](#features)
- [Usage](#usage) - [Easy to Read and Write](#easy-to-read-and-write)
- [Dust Language](#dust-language) - [Effortless Concurrency](#effortless-concurrency)
- [Installation](#installation) - [Helpful Errors](#helpful-errors)
- [Benchmarks](#benchmarks) - [Static analysis](#static-analysis)
- [Implementation](#implementation) - [Debugging](#debugging)
- [Acknowledgements](#acknowledgements) - [Automatic Memory Management](#automatic-memory-management)
- [Error Handling](#error-handling)
- [Installation and Usage](#installation-and-usage)
<!--toc:end--> <!--toc:end-->
## Features ## Features
- Simplicity: Dust is designed to be easy to learn. ### Easy to Read and Write
- 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.
## Installation Dust has simple, easy-to-learn syntax.
### Cargo ```js
output('Hello world!')
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.
### Build From Source
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. If you get errors about a linking with C, read them carefully to determine which prerequisites are needed.
On Fedora, you can install these prerequisites with:
```sh
sudo dnf group install -y 'C Development Tools and Libraries' && sudo dnf install -y cmake
``` ```
## Usage ### Effortless Concurrency
After installation, the command line interpreter can be given source code to run or it can launch the command-line shell. As with intepreters like `sh` and `bash`, you can use the `-c` flag to pass source code directly. Write multi-threaded code as easily as you would write code for a single thread.
```sh ```js
dust -c "output('Hello world!')" async {
# Output: Hello world! output('Will this one print first?')
output('Or will this one?')
output('Who knows! Each "output" will run in its own thread!')
}
``` ```
Or just provide a path to the source file. ### Helpful Errors
```sh Dust shows you exactly where your code went wrong and suggests changes.
dust examples/hello_world.ds
![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)
}
``` ```
Run `dust --help` to see the available commands and options. ## Installation and Usage
```txt 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.
General purpose programming language
Usage: dust [OPTIONS] [PATH] To install from the git repository:
Arguments: ```fish
[PATH] Location of the file to run git clone https://git.jeffa.io/jeff/dust
cd dust
Options: cargo run --release
-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
``` ```
## Dust Language To install with cargo:
See the [Language Reference](/docs/language.md) for more information. ```fish
cargo install dust-lang
dust
```
## Benchmarks ## Benchmarks
Dust is at an early development stage and these tests are overly simple. Better benchmarks are needed to get a realistic idea of how Dust performs real work. For now, these tests are just for fun. ## Development Status
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.
For the first test, a file with four entries was used. Currently, Dust is being prepared for version 1.0. Until then, there may be breaking changes to the language and CLI.
| 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.
## 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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
docs/assets/type_error.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

26
examples/guessing_game.ds Normal file
View 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
}
}
}

View File

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

View File

@ -11,7 +11,7 @@ hyperfine \
--shell none \ --shell none \
--parameter-list data_path examples/assets/seaCreatures.json \ --parameter-list data_path examples/assets/seaCreatures.json \
--warmup 3 \ --warmup 3 \
"dust -c 'length(json:parse(input))' -p {data_path}" \ "target/release/dust -c 'length(json:parse(fs:read_file(\"{data_path}\")))'" \
"jq 'length' {data_path}" \ "jq 'length' {data_path}" \
"node --eval \"require('node:fs').readFile('{data_path}', (err, data)=>{console.log(JSON.parse(data).length)})\"" \ "node --eval \"require('node:fs').readFile('{data_path}', (err, data)=>{console.log(JSON.parse(data).length)})\"" \
"nu -c 'open {data_path} | length'" "nu -c 'open {data_path} | length'"
@ -20,7 +20,7 @@ hyperfine \
--shell none \ --shell none \
--parameter-list data_path examples/assets/jq_data.json \ --parameter-list data_path examples/assets/jq_data.json \
--warmup 3 \ --warmup 3 \
"dust -c 'length(json:parse(input))' -p {data_path}" \ "target/release/dust -c 'length(json:parse(fs:read_file(\"{data_path}\")))'" \
"jq 'length' {data_path}" \ "jq 'length' {data_path}" \
"node --eval \"require('node:fs').readFile('{data_path}', (err, data)=>{console.log(JSON.parse(data).length)})\"" \ "node --eval \"require('node:fs').readFile('{data_path}', (err, data)=>{console.log(JSON.parse(data).length)})\"" \
"nu -c 'open {data_path} | length'" "nu -c 'open {data_path} | length'"
@ -29,7 +29,7 @@ hyperfine \
--shell none \ --shell none \
--parameter-list data_path dielectron.json \ --parameter-list data_path dielectron.json \
--warmup 3 \ --warmup 3 \
"dust -c 'length(json:parse(input))' -p {data_path}" \ "target/release/dust -c 'length(json:parse(fs:read_file(\"{data_path}\")))'" \
"jq 'length' {data_path}" \ "jq 'length' {data_path}" \
"node --eval \"require('node:fs').readFile('{data_path}', (err, data)=>{console.log(JSON.parse(data).length)})\"" \ "node --eval \"require('node:fs').readFile('{data_path}', (err, data)=>{console.log(JSON.parse(data).length)})\"" \
"nu -c 'open {data_path} | length'" "nu -c 'open {data_path} | length'"

View File

@ -15,7 +15,7 @@ pub struct As {
impl AbstractTree for As { impl AbstractTree for As {
fn from_syntax(node: Node, source: &str, context: &Context) -> Result<Self, SyntaxError> { fn from_syntax(node: Node, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "as", node)?; SyntaxError::expect_syntax_node("as", node)?;
let expression_node = node.child(0).unwrap(); let expression_node = node.child(0).unwrap();
let expression = Expression::from_syntax(expression_node, source, context)?; let expression = Expression::from_syntax(expression_node, source, context)?;
@ -37,9 +37,15 @@ impl AbstractTree for As {
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> { fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
let initial_type = self.expression.expected_type(context)?; let initial_type = self.expression.expected_type(context)?;
if let Type::List(item_type) = &self.r#type { if self.r#type.accepts(&initial_type) {
return Ok(());
}
if let Type::ListOf(item_type) = &self.r#type {
match &initial_type { match &initial_type {
Type::List(expected_item_type) => { Type::ListOf(expected_item_type) => {
println!("{item_type} {expected_item_type}");
if !item_type.accepts(&expected_item_type) { if !item_type.accepts(&expected_item_type) {
return Err(ValidationError::TypeCheck { return Err(ValidationError::TypeCheck {
expected: self.r#type.clone(), expected: self.r#type.clone(),
@ -77,7 +83,9 @@ impl AbstractTree for As {
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> { fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let value = self.expression.run(source, context)?; let value = self.expression.run(source, context)?;
let converted_value = if let Type::List(_) = self.r#type { let converted_value = if self.r#type.accepts(&value.r#type()?) {
return Ok(value);
} else if let Type::ListOf(_) = self.r#type {
match value { match value {
Value::List(list) => Value::List(list), Value::List(list) => Value::List(list),
Value::String(string) => { Value::String(string) => {
@ -90,11 +98,24 @@ impl AbstractTree for As {
} }
_ => { _ => {
return Err(RuntimeError::ConversionImpossible { return Err(RuntimeError::ConversionImpossible {
value, from: value.r#type()?,
target_type: self.r#type.clone(), 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 { } else {
todo!() todo!()
}; };

View File

@ -3,8 +3,8 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
context::Context, context::Context,
error::{RuntimeError, SyntaxError, ValidationError}, error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, AssignmentOperator, Format, Identifier, SourcePosition, Statement, SyntaxNode, AbstractTree, AssignmentOperator, Format, Function, Identifier, SourcePosition, Statement,
Type, TypeSpecification, Value, SyntaxNode, Type, TypeSpecification, Value,
}; };
/// Variable assignment, including add-assign and subtract-assign operations. /// Variable assignment, including add-assign and subtract-assign operations.
@ -14,7 +14,6 @@ pub struct Assignment {
type_specification: Option<TypeSpecification>, type_specification: Option<TypeSpecification>,
operator: AssignmentOperator, operator: AssignmentOperator,
statement: Statement, statement: Statement,
syntax_position: SourcePosition, syntax_position: SourcePosition,
} }
@ -24,7 +23,7 @@ impl AbstractTree for Assignment {
source: &str, source: &str,
context: &Context, context: &Context,
) -> Result<Self, SyntaxError> { ) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "assignment", syntax_node)?; SyntaxError::expect_syntax_node("assignment", syntax_node)?;
let child_count = syntax_node.child_count(); let child_count = syntax_node.child_count();
@ -55,14 +54,15 @@ impl AbstractTree for Assignment {
fn validate(&self, source: &str, context: &Context) -> Result<(), ValidationError> { fn validate(&self, source: &str, context: &Context) -> Result<(), ValidationError> {
if let AssignmentOperator::Equal = self.operator { if let AssignmentOperator::Equal = self.operator {
let key = self.identifier.inner().clone();
let r#type = if let Some(definition) = &self.type_specification { let r#type = if let Some(definition) = &self.type_specification {
definition.inner().clone() definition.inner().clone()
} else { } else {
self.statement.expected_type(context)? self.statement.expected_type(context)?
}; };
context.set_type(key, r#type)?; log::info!("Setting type: {} <{}>", self.identifier, r#type);
context.set_type(self.identifier.clone(), r#type)?;
} }
if let Some(type_specification) = &self.type_specification { if let Some(type_specification) = &self.type_specification {
@ -80,7 +80,7 @@ impl AbstractTree for Assignment {
} }
} }
AssignmentOperator::PlusEqual => { AssignmentOperator::PlusEqual => {
if let Type::List(expected) = type_specification.inner() { if let Type::ListOf(expected) = type_specification.inner() {
let actual = self.identifier.expected_type(context)?; let actual = self.identifier.expected_type(context)?;
if !expected.accepts(&actual) { if !expected.accepts(&actual) {
@ -109,7 +109,7 @@ impl AbstractTree for Assignment {
match self.operator { match self.operator {
AssignmentOperator::Equal => {} AssignmentOperator::Equal => {}
AssignmentOperator::PlusEqual => { AssignmentOperator::PlusEqual => {
if let Type::List(expected) = self.identifier.expected_type(context)? { if let Type::ListOf(expected) = self.identifier.expected_type(context)? {
let actual = self.statement.expected_type(context)?; let actual = self.statement.expected_type(context)?;
if !expected.accepts(&actual) { if !expected.accepts(&actual) {
@ -131,30 +131,35 @@ impl AbstractTree for Assignment {
} }
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> { fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let key = self.identifier.inner(); let right = self.statement.run(source, context)?;
let value = self.statement.run(source, context)?;
let new_value = match self.operator { let new_value = match self.operator {
AssignmentOperator::PlusEqual => { AssignmentOperator::PlusEqual => {
if let Some(mut previous_value) = context.get_value(key)? { let left = self.identifier.run(source, context)?;
previous_value += value;
previous_value left.add(right, self.syntax_position)?
} else {
return Err(RuntimeError::VariableIdentifierNotFound(key.clone()));
}
} }
AssignmentOperator::MinusEqual => { AssignmentOperator::MinusEqual => {
if let Some(mut previous_value) = context.get_value(key)? { if let Some(left) = context.get_value(&self.identifier)? {
previous_value -= value; left.subtract(right, self.syntax_position)?
previous_value
} else { } else {
return Err(RuntimeError::VariableIdentifierNotFound(key.clone())); return Err(RuntimeError::ValidationFailure(
ValidationError::VariableIdentifierNotFound(self.identifier.clone()),
));
} }
} }
AssignmentOperator::Equal => value, AssignmentOperator::Equal => right,
}; };
context.set_value(key.clone(), new_value)?; 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()) Ok(Value::none())
} }

View File

@ -16,10 +16,10 @@ pub enum AssignmentOperator {
impl AbstractTree for AssignmentOperator { impl AbstractTree for AssignmentOperator {
fn from_syntax( fn from_syntax(
node: SyntaxNode, node: SyntaxNode,
source: &str, _source: &str,
_context: &Context, _context: &Context,
) -> Result<Self, SyntaxError> { ) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "assignment_operator", node)?; SyntaxError::expect_syntax_node("assignment_operator", node)?;
let operator_node = node.child(0).unwrap(); let operator_node = node.child(0).unwrap();
let operator = match operator_node.kind() { let operator = match operator_node.kind() {
@ -30,8 +30,7 @@ impl AbstractTree for AssignmentOperator {
return Err(SyntaxError::UnexpectedSyntaxNode { return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "=, += or -=".to_string(), expected: "=, += or -=".to_string(),
actual: operator_node.kind().to_string(), actual: operator_node.kind().to_string(),
location: operator_node.start_position(), position: node.range().into(),
relevant_source: source[operator_node.byte_range()].to_string(),
}) })
} }
}; };

View File

@ -22,15 +22,23 @@ use crate::{
#[derive(Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] #[derive(Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Block { pub struct Block {
is_async: bool, is_async: bool,
contains_return: bool,
statements: Vec<Statement>, statements: Vec<Statement>,
} }
impl Block {
pub fn contains_return(&self) -> bool {
self.contains_return
}
}
impl AbstractTree for Block { impl AbstractTree for Block {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> { fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "block", node)?; SyntaxError::expect_syntax_node("block", node)?;
let first_child = node.child(0).unwrap(); let first_child = node.child(0).unwrap();
let is_async = first_child.kind() == "async"; let is_async = first_child.kind() == "async";
let mut contains_return = false;
let statement_count = if is_async { let statement_count = if is_async {
node.child_count() - 3 node.child_count() - 3
@ -46,12 +54,17 @@ impl AbstractTree for Block {
if child_node.kind() == "statement" { if child_node.kind() == "statement" {
let statement = Statement::from_syntax(child_node, source, &block_context)?; let statement = Statement::from_syntax(child_node, source, &block_context)?;
if statement.is_return() {
contains_return = true;
}
statements.push(statement); statements.push(statement);
} }
} }
Ok(Block { Ok(Block {
is_async, is_async,
contains_return,
statements, statements,
}) })
} }
@ -74,16 +87,13 @@ impl AbstractTree for Block {
.enumerate() .enumerate()
.find_map_first(|(index, statement)| { .find_map_first(|(index, statement)| {
let result = statement.run(_source, _context); let result = statement.run(_source, _context);
let is_last_statement = index == statements.len() - 1; let should_return = if self.contains_return {
let is_return_statement = if let Statement::Return(_) = statement { statement.is_return()
true
} else { } else {
false index == statements.len() - 1
}; };
if is_return_statement || result.is_err() { if should_return {
Some(result)
} else if is_last_statement {
let get_write_lock = final_result.write(); let get_write_lock = final_result.write();
match get_write_lock { match get_write_lock {
@ -99,35 +109,33 @@ impl AbstractTree for Block {
}) })
.unwrap_or(final_result.into_inner().map_err(|_| RwLockError)?) .unwrap_or(final_result.into_inner().map_err(|_| RwLockError)?)
} else { } else {
let mut prev_result = None; for (index, statement) in self.statements.iter().enumerate() {
if statement.is_return() {
for statement in &self.statements { return statement.run(_source, _context);
if let Statement::Return(inner_statement) = statement {
return inner_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: &Context) -> Result<Type, ValidationError> { fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
if let Some(statement) = self.statements.iter().find(|statement| { for (index, statement) in self.statements.iter().enumerate() {
if let Statement::Return(_) = statement { if statement.is_return() {
true return statement.expected_type(_context);
} else {
false
} }
}) {
statement.expected_type(_context) if index == self.statements.len() - 1 {
} else if let Some(statement) = self.statements.last() { return statement.expected_type(_context);
statement.expected_type(_context) }
} else { }
Ok(Type::None) Ok(Type::None)
} }
}
} }
impl Format for Block { impl Format for Block {

View File

@ -1,229 +0,0 @@
use std::{collections::BTreeMap, 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},
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, BuiltInFunction, Context, Format, Function, List, Map, Structure, SyntaxNode,
Type, 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();
/// Returns the entire built-in value API.
pub fn 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,
/// 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::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::Output => "output",
BuiltInValue::Random => "random",
BuiltInValue::Str => "string",
}
}
/// Returns the value's type.
///
/// This is checked with a unit test to ensure it matches the value.
pub fn r#type(&self) -> Type {
match self {
BuiltInValue::Args => Type::list(Type::String),
BuiltInValue::AssertEqual => BuiltInFunction::AssertEqual.r#type(),
BuiltInValue::Fs => Type::Map(None),
BuiltInValue::Json => Type::Map(Some(
Structure::from_map(self.get().as_map().unwrap()).unwrap(),
)),
BuiltInValue::Length => BuiltInFunction::Length.r#type(),
BuiltInValue::Output => BuiltInFunction::Output.r#type(),
BuiltInValue::Random => Type::Map(None),
BuiltInValue::Str => Type::Map(None),
}
}
/// 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))
}),
BuiltInValue::AssertEqual => {
&Value::Function(Function::BuiltIn(BuiltInFunction::AssertEqual))
}
BuiltInValue::Fs => FS.get_or_init(|| {
let mut fs_context = BTreeMap::new();
for fs_function in fs_functions() {
let key = fs_function.name().to_string();
let value =
Value::Function(Function::BuiltIn(BuiltInFunction::Fs(fs_function)));
fs_context.insert(key, value);
}
Value::Map(Map::with_values(fs_context))
}),
BuiltInValue::Json => JSON.get_or_init(|| {
let mut json_context = BTreeMap::new();
for json_function in json_functions() {
let key = json_function.name().to_string();
let value =
Value::Function(Function::BuiltIn(BuiltInFunction::Json(json_function)));
json_context.insert(key, value);
}
Value::Map(Map::with_values(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 mut random_context = BTreeMap::new();
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));
random_context.insert(key, value);
}
Value::Map(Map::with_values(random_context))
}),
BuiltInValue::Str => STRING.get_or_init(|| {
let mut string_context = BTreeMap::new();
for string_function in string_functions() {
let key = string_function.name().to_string();
let value = Value::Function(Function::BuiltIn(BuiltInFunction::String(
string_function,
)));
string_context.insert(key, value);
}
Value::Map(Map::with_values(string_context))
}),
}
}
}
impl AbstractTree for BuiltInValue {
fn from_syntax(
node: SyntaxNode,
_source: &str,
_context: &Context,
) -> Result<Self, SyntaxError> {
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,
"str" => BuiltInValue::Str,
_ => todo!(),
};
Ok(built_in_value)
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(self.r#type())
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
Ok(self.get().clone())
}
}
impl Format for BuiltInValue {
fn format(&self, output: &mut String, _indent_level: u8) {
output.push_str(&self.get().to_string());
}
}
#[cfg(test)]
mod tests {
use crate::built_in_values;
#[test]
fn check_built_in_types() {
for built_in_value in built_in_values() {
let expected = built_in_value.r#type();
let actual = built_in_value.get().r#type();
assert_eq!(expected, actual);
}
}
}

View File

@ -1,4 +1,4 @@
use std::process; use std::process::{self, Stdio};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode; use tree_sitter::Node as SyntaxNode;
@ -21,7 +21,7 @@ impl AbstractTree for Command {
source: &str, source: &str,
_context: &Context, _context: &Context,
) -> Result<Self, SyntaxError> { ) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "command", node)?; SyntaxError::expect_syntax_node("command", node)?;
let command_text_node = node.child(1).unwrap(); let command_text_node = node.child(1).unwrap();
let command_text = source[command_text_node.byte_range()].to_string(); let command_text = source[command_text_node.byte_range()].to_string();
@ -53,18 +53,19 @@ impl AbstractTree for Command {
} }
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> { fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
todo!() Ok(())
} }
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> { fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
let output = process::Command::new(&self.command_text) let output = process::Command::new(&self.command_text)
.args(&self.command_arguments) .args(&self.command_arguments)
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()? .spawn()?
.wait_with_output()? .wait_with_output()?
.stdout; .stdout;
let string = String::from_utf8(output)?;
Ok(Value::String(string)) Ok(Value::String(String::from_utf8(output)?))
} }
} }

View 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) = &current_identifier {
variants.push((identifier.clone(), types));
}
current_identifier = Some(Identifier::from_syntax(child, source, context)?);
types = Vec::new();
}
if child.kind() == "type" {
let r#type = Type::from_syntax(child, source, context)?;
types.push(r#type);
}
}
Ok(EnumDefinition {
identifier,
variants,
})
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::None)
}
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
context.set_definition(self.identifier.clone(), TypeDefinition::Enum(self.clone()))?;
self.identifier.validate(_source, context)?;
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
Ok(Value::none())
}
}
impl Format for EnumDefinition {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -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!()
}
}

View File

@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
error::{RuntimeError, SyntaxError, ValidationError}, error::{RuntimeError, SyntaxError, ValidationError},
value_node::ValueNode, value_node::ValueNode,
AbstractTree, As, Command, Context, Format, FunctionCall, Identifier, Index, Logic, Math, New, AbstractTree, As, Command, Context, Format, FunctionCall, Identifier, Index, Logic, Math,
SyntaxNode, Type, Value, SyntaxNode, Type, Value,
}; };
@ -20,7 +20,6 @@ pub enum Expression {
Math(Box<Math>), Math(Box<Math>),
Logic(Box<Logic>), Logic(Box<Logic>),
FunctionCall(Box<FunctionCall>), FunctionCall(Box<FunctionCall>),
New(New),
Command(Command), Command(Command),
As(Box<As>), As(Box<As>),
} }
@ -31,7 +30,7 @@ impl AbstractTree for Expression {
source: &str, source: &str,
_context: &Context, _context: &Context,
) -> Result<Self, SyntaxError> { ) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "expression", node)?; SyntaxError::expect_syntax_node("expression", node)?;
let child = if node.child(0).unwrap().is_named() { let child = if node.child(0).unwrap().is_named() {
node.child(0).unwrap() node.child(0).unwrap()
@ -51,16 +50,13 @@ impl AbstractTree for Expression {
"function_call" => Expression::FunctionCall(Box::new(FunctionCall::from_syntax( "function_call" => Expression::FunctionCall(Box::new(FunctionCall::from_syntax(
child, source, _context, child, source, _context,
)?)), )?)),
"new" => Expression::New(New::from_syntax(child, source, _context)?),
"command" => Expression::Command(Command::from_syntax(child, source, _context)?), "command" => Expression::Command(Command::from_syntax(child, source, _context)?),
_ => { _ => {
return Err(SyntaxError::UnexpectedSyntaxNode { return Err(SyntaxError::UnexpectedSyntaxNode {
expected: expected: "value, identifier, index, math, logic, function call, as or command"
"value, identifier, index, math, logic, function call, new, as or command"
.to_string(), .to_string(),
actual: child.kind().to_string(), actual: child.kind().to_string(),
location: child.start_position(), position: node.range().into(),
relevant_source: source[child.byte_range()].to_string(),
}) })
} }
}; };
@ -76,7 +72,6 @@ impl AbstractTree for Expression {
Expression::Logic(logic) => logic.expected_type(_context), Expression::Logic(logic) => logic.expected_type(_context),
Expression::FunctionCall(function_call) => function_call.expected_type(_context), Expression::FunctionCall(function_call) => function_call.expected_type(_context),
Expression::Index(index) => index.expected_type(_context), Expression::Index(index) => index.expected_type(_context),
Expression::New(new) => new.expected_type(_context),
Expression::Command(command) => command.expected_type(_context), Expression::Command(command) => command.expected_type(_context),
Expression::As(r#as) => r#as.expected_type(_context), Expression::As(r#as) => r#as.expected_type(_context),
} }
@ -90,7 +85,6 @@ impl AbstractTree for Expression {
Expression::Logic(logic) => logic.validate(_source, _context), Expression::Logic(logic) => logic.validate(_source, _context),
Expression::FunctionCall(function_call) => function_call.validate(_source, _context), Expression::FunctionCall(function_call) => function_call.validate(_source, _context),
Expression::Index(index) => index.validate(_source, _context), Expression::Index(index) => index.validate(_source, _context),
Expression::New(new) => new.validate(_source, _context),
Expression::Command(command) => command.validate(_source, _context), Expression::Command(command) => command.validate(_source, _context),
Expression::As(r#as) => r#as.validate(_source, _context), Expression::As(r#as) => r#as.validate(_source, _context),
} }
@ -104,7 +98,6 @@ impl AbstractTree for Expression {
Expression::Logic(logic) => logic.run(_source, _context), Expression::Logic(logic) => logic.run(_source, _context),
Expression::FunctionCall(function_call) => function_call.run(_source, _context), Expression::FunctionCall(function_call) => function_call.run(_source, _context),
Expression::Index(index) => index.run(_source, _context), Expression::Index(index) => index.run(_source, _context),
Expression::New(new) => new.run(_source, _context),
Expression::Command(command) => command.run(_source, _context), Expression::Command(command) => command.run(_source, _context),
Expression::As(r#as) => r#as.run(_source, _context), Expression::As(r#as) => r#as.run(_source, _context),
} }
@ -120,7 +113,6 @@ impl Format for Expression {
Expression::Logic(logic) => logic.format(_output, _indent_level), Expression::Logic(logic) => logic.format(_output, _indent_level),
Expression::FunctionCall(function_call) => function_call.format(_output, _indent_level), Expression::FunctionCall(function_call) => function_call.format(_output, _indent_level),
Expression::Index(index) => index.format(_output, _indent_level), Expression::Index(index) => index.format(_output, _indent_level),
Expression::New(new) => new.format(_output, _indent_level),
Expression::Command(command) => command.format(_output, _indent_level), Expression::Command(command) => command.format(_output, _indent_level),
Expression::As(r#as) => r#as.format(_output, _indent_level), Expression::As(r#as) => r#as.format(_output, _indent_level),
} }

View File

@ -22,7 +22,7 @@ pub struct For {
impl AbstractTree for For { impl AbstractTree for For {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> { fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "for", node)?; SyntaxError::expect_syntax_node("for", node)?;
let for_node = node.child(0).unwrap(); let for_node = node.child(0).unwrap();
let is_async = match for_node.kind() { let is_async = match for_node.kind() {
@ -32,8 +32,7 @@ impl AbstractTree for For {
return Err(SyntaxError::UnexpectedSyntaxNode { return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "for or async for".to_string(), expected: "for or async for".to_string(),
actual: for_node.kind().to_string(), actual: for_node.kind().to_string(),
location: for_node.start_position(), position: node.range().into(),
relevant_source: source[for_node.byte_range()].to_string(),
}) })
} }
}; };
@ -64,40 +63,51 @@ impl AbstractTree for For {
} }
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> { fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
self.collection.validate(_source, context)?;
let collection_type = self.collection.expected_type(context)?; let collection_type = self.collection.expected_type(context)?;
let item_type = if let Type::List(item_type) = collection_type { let item_type = match collection_type {
item_type.as_ref().clone() Type::Any => Type::Any,
} else if let Type::Range = collection_type { Type::Collection => Type::Any,
Type::Integer Type::List => Type::Any,
} else { Type::ListOf(_) => todo!(),
Type::ListExact(_) => todo!(),
Type::Map(_) => todo!(),
Type::String => todo!(),
Type::Range => todo!(),
_ => {
return Err(ValidationError::TypeCheck { return Err(ValidationError::TypeCheck {
expected: Type::Collection, expected: Type::Collection,
actual: collection_type, actual: collection_type,
position: self.source_position, position: self.source_position,
}); });
}
}; };
let key = self.item_id.inner().clone(); let key = self.item_id.clone();
self.context.inherit_from(context)?; self.context.inherit_all_from(context)?;
self.context.set_type(key, item_type)?; self.context.set_type(key, item_type)?;
self.item_id.validate(_source, &self.context)?;
self.block.validate(_source, &self.context) self.block.validate(_source, &self.context)
} }
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> { fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
self.context.inherit_from(context)?; self.context.inherit_all_from(context)?;
let expression_run = self.collection.run(source, context)?; let expression_run = self.collection.run(source, context)?;
let key = self.item_id.inner(); let key = &self.item_id;
if let Value::Range(range) = expression_run { if let Value::Range(range) = expression_run {
if self.is_async { if self.is_async {
range.into_par_iter().try_for_each(|integer| { range.into_par_iter().try_for_each(|integer| {
self.context.add_allowance(key)?;
self.context self.context
.set_value(key.clone(), Value::Integer(integer))?; .set_value(key.clone(), Value::Integer(integer))?;
self.block.run(source, &self.context).map(|_value| ()) self.block.run(source, &self.context).map(|_value| ())
})?; })?;
} else { } else {
for i in range { for i in range {
self.context.add_allowance(key)?;
self.context.set_value(key.clone(), Value::Integer(i))?; self.context.set_value(key.clone(), Value::Integer(i))?;
self.block.run(source, &self.context)?; self.block.run(source, &self.context)?;
} }
@ -108,12 +118,14 @@ impl AbstractTree for For {
if let Value::List(list) = &expression_run { if let Value::List(list) = &expression_run {
if self.is_async { if self.is_async {
list.items().par_iter().try_for_each(|value| { list.items()?.par_iter().try_for_each(|value| {
self.context.add_allowance(key)?;
self.context.set_value(key.clone(), value.clone())?; self.context.set_value(key.clone(), value.clone())?;
self.block.run(source, &self.context).map(|_value| ()) self.block.run(source, &self.context).map(|_value| ())
})?; })?;
} else { } else {
for value in list.items().iter() { for value in list.items()?.iter() {
self.context.add_allowance(key)?;
self.context.set_value(key.clone(), value.clone())?; self.context.set_value(key.clone(), value.clone())?;
self.block.run(source, &self.context)?; self.block.run(source, &self.context)?;
} }

View File

@ -13,9 +13,6 @@ pub struct FunctionCall {
function_expression: FunctionExpression, function_expression: FunctionExpression,
arguments: Vec<Expression>, arguments: Vec<Expression>,
syntax_position: SourcePosition, syntax_position: SourcePosition,
#[serde(skip)]
context: Context,
} }
impl FunctionCall { impl FunctionCall {
@ -24,20 +21,18 @@ impl FunctionCall {
function_expression: FunctionExpression, function_expression: FunctionExpression,
arguments: Vec<Expression>, arguments: Vec<Expression>,
syntax_position: SourcePosition, syntax_position: SourcePosition,
context: Context,
) -> Self { ) -> Self {
Self { Self {
function_expression, function_expression,
arguments, arguments,
syntax_position, syntax_position,
context,
} }
} }
} }
impl AbstractTree for FunctionCall { impl AbstractTree for FunctionCall {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> { fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "function_call", node)?; SyntaxError::expect_syntax_node("function_call", node)?;
let function_node = node.child(0).unwrap(); let function_node = node.child(0).unwrap();
let function_expression = FunctionExpression::from_syntax(function_node, source, context)?; let function_expression = FunctionExpression::from_syntax(function_node, source, context)?;
@ -58,7 +53,6 @@ impl AbstractTree for FunctionCall {
function_expression, function_expression,
arguments, arguments,
syntax_position: node.range().into(), syntax_position: node.range().into(),
context: Context::new(),
}) })
} }
@ -100,6 +94,8 @@ impl AbstractTree for FunctionCall {
} }
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> { 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 function_expression_type = self.function_expression.expected_type(context)?;
let parameter_types = if let Type::Function { let parameter_types = if let Type::Function {
@ -123,6 +119,8 @@ impl AbstractTree for FunctionCall {
} }
for (index, expression) in self.arguments.iter().enumerate() { for (index, expression) in self.arguments.iter().enumerate() {
expression.validate(_source, context)?;
if let Some(expected) = parameter_types.get(index) { if let Some(expected) = parameter_types.get(index) {
let actual = expression.expected_type(context)?; let actual = expression.expected_type(context)?;
@ -142,15 +140,11 @@ impl AbstractTree for FunctionCall {
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> { fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let value = match &self.function_expression { let value = match &self.function_expression {
FunctionExpression::Identifier(identifier) => { FunctionExpression::Identifier(identifier) => {
let key = identifier.inner(); if let Some(value) = context.get_value(identifier)? {
if let Some(value) = context.get_value(key)? {
self.context
.set_value(identifier.inner().clone(), value.clone())?;
value.clone() value.clone()
} else { } else {
return Err(RuntimeError::VariableIdentifierNotFound( return Err(RuntimeError::ValidationFailure(
identifier.inner().clone(), ValidationError::VariableIdentifierNotFound(identifier.clone()),
)); ));
} }
} }
@ -172,19 +166,23 @@ impl AbstractTree for FunctionCall {
arguments.push(value); arguments.push(value);
} }
built_in_function.call(&arguments, source, &self.context) built_in_function.call(&arguments, source, context)
} }
Function::ContextDefined(function_node) => { Function::ContextDefined(function_node) => {
let call_context = Context::with_variables_from(function_node.context())?;
call_context.inherit_from(context)?;
let parameter_expression_pairs = let parameter_expression_pairs =
function_node.parameters().iter().zip(self.arguments.iter()); function_node.parameters().iter().zip(self.arguments.iter());
for (identifier, expression) in parameter_expression_pairs { for (identifier, expression) in parameter_expression_pairs {
let value = expression.run(source, context)?; let value = expression.run(source, context)?;
self.context.set_value(identifier.inner().clone(), value)?; call_context.set_value(identifier.clone(), value)?;
} }
function_node.call(source, &self.context) function_node.body().run(source, &call_context)
} }
} }
} }

View File

@ -16,7 +16,7 @@ pub enum FunctionExpression {
impl AbstractTree for FunctionExpression { impl AbstractTree for FunctionExpression {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> { fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "function_expression", node)?; SyntaxError::expect_syntax_node("function_expression", node)?;
let first_child = node.child(0).unwrap(); let first_child = node.child(0).unwrap();
let child = if first_child.is_named() { let child = if first_child.is_named() {
@ -39,8 +39,7 @@ impl AbstractTree for FunctionExpression {
return Err(SyntaxError::UnexpectedSyntaxNode { return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "identifier, function call, value or index".to_string(), expected: "identifier, function call, value or index".to_string(),
actual: child.kind().to_string(), actual: child.kind().to_string(),
location: child.start_position(), position: node.range().into(),
relevant_source: source[child.byte_range()].to_string(),
}) })
} }
}; };

View File

@ -14,23 +14,12 @@ pub struct FunctionNode {
body: Block, body: Block,
r#type: Type, r#type: Type,
syntax_position: SourcePosition, syntax_position: SourcePosition,
#[serde(skip)]
context: Context,
} }
impl FunctionNode { impl FunctionNode {
pub fn new(
parameters: Vec<Identifier>,
body: Block,
r#type: Type,
syntax_position: SourcePosition,
) -> Self {
FunctionNode {
parameters,
body,
r#type,
syntax_position,
}
}
pub fn parameters(&self) -> &Vec<Identifier> { pub fn parameters(&self) -> &Vec<Identifier> {
&self.parameters &self.parameters
} }
@ -47,6 +36,10 @@ impl FunctionNode {
&self.syntax_position &self.syntax_position
} }
pub fn context(&self) -> &Context {
&self.context
}
pub fn return_type(&self) -> &Type { pub fn return_type(&self) -> &Type {
match &self.r#type { match &self.r#type {
Type::Function { Type::Function {
@ -56,17 +49,11 @@ impl FunctionNode {
_ => &Type::None, _ => &Type::None,
} }
} }
pub fn call(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let return_value = self.body.run(source, context)?;
Ok(return_value)
}
} }
impl AbstractTree for FunctionNode { impl AbstractTree for FunctionNode {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> { fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "function", node)?; SyntaxError::expect_syntax_node("function", node)?;
let child_count = node.child_count(); let child_count = node.child_count();
let mut parameters = Vec::new(); let mut parameters = Vec::new();
@ -91,8 +78,10 @@ impl AbstractTree for FunctionNode {
let return_type_node = node.child(child_count - 2).unwrap(); let return_type_node = node.child(child_count - 2).unwrap();
let return_type = TypeSpecification::from_syntax(return_type_node, source, 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_node = node.child(child_count - 1).unwrap();
let body = Block::from_syntax(body_node, source, &context)?; let body = Block::from_syntax(body_node, source, &function_context)?;
let r#type = Type::function(parameter_types, return_type.take_inner()); let r#type = Type::function(parameter_types, return_type.take_inner());
let syntax_position = node.range().into(); let syntax_position = node.range().into();
@ -102,6 +91,7 @@ impl AbstractTree for FunctionNode {
body, body,
r#type, r#type,
syntax_position, syntax_position,
context: function_context,
}) })
} }
@ -109,19 +99,19 @@ impl AbstractTree for FunctionNode {
Ok(self.r#type().clone()) Ok(self.r#type().clone())
} }
fn validate(&self, source: &str, _context: &Context) -> Result<(), ValidationError> { fn validate(&self, source: &str, context: &Context) -> Result<(), ValidationError> {
if let Type::Function { if let Type::Function {
parameter_types, parameter_types,
return_type, return_type,
} = &self.r#type } = &self.r#type
{ {
let validation_context = Context::new(); self.context.inherit_from(context)?;
for (parameter, r#type) in self.parameters.iter().zip(parameter_types.iter()) { for (parameter, r#type) in self.parameters.iter().zip(parameter_types.iter()) {
validation_context.set_type(parameter.inner().clone(), r#type.clone())?; self.context.set_type(parameter.clone(), r#type.clone())?;
} }
let actual = self.body.expected_type(&validation_context)?; let actual = self.body.expected_type(&self.context)?;
if !return_type.accepts(&actual) { if !return_type.accepts(&actual) {
return Err(ValidationError::TypeCheck { return Err(ValidationError::TypeCheck {
@ -131,7 +121,7 @@ impl AbstractTree for FunctionNode {
}); });
} }
self.body.validate(source, &validation_context)?; self.body.validate(source, &self.context)?;
Ok(()) Ok(())
} else { } else {
@ -142,7 +132,9 @@ impl AbstractTree for FunctionNode {
} }
} }
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> { 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())); let self_as_value = Value::Function(Function::ContextDefined(self.clone()));
Ok(self_as_value) Ok(self_as_value)

View File

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

View File

@ -89,6 +89,8 @@ impl AbstractTree for IfElse {
} }
if let Some(block) = &self.else_block { if let Some(block) = &self.else_block {
block.validate(_source, context)?;
let actual = block.expected_type(context)?; let actual = block.expected_type(context)?;
if !expected.accepts(&actual) { if !expected.accepts(&actual) {
@ -109,14 +111,15 @@ impl AbstractTree for IfElse {
if if_boolean { if if_boolean {
self.if_block.run(source, context) self.if_block.run(source, context)
} else { } 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()?; let if_boolean = expression.run(source, context)?.as_boolean()?;
if if_boolean { if if_boolean {
let block = self.else_if_blocks.get(index).unwrap();
return block.run(source, context); return block.run(source, context);
} }
} }

View File

@ -2,7 +2,8 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
error::{RuntimeError, SyntaxError, ValidationError}, error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, IndexExpression, List, SourcePosition, SyntaxNode, Type, Value, AbstractTree, Context, Format, Identifier, IndexExpression, SourcePosition, SyntaxNode, Type,
Value,
}; };
/// Abstract representation of an index expression. /// Abstract representation of an index expression.
@ -12,13 +13,12 @@ use crate::{
pub struct Index { pub struct Index {
pub collection: IndexExpression, pub collection: IndexExpression,
pub index: IndexExpression, pub index: IndexExpression,
pub index_end: Option<IndexExpression>,
source_position: SourcePosition, source_position: SourcePosition,
} }
impl AbstractTree for Index { impl AbstractTree for Index {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> { fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "index", node)?; SyntaxError::expect_syntax_node("index", node)?;
let collection_node = node.child(0).unwrap(); let collection_node = node.child(0).unwrap();
let collection = IndexExpression::from_syntax(collection_node, source, context)?; let collection = IndexExpression::from_syntax(collection_node, source, context)?;
@ -26,39 +26,29 @@ impl AbstractTree for Index {
let index_node = node.child(2).unwrap(); let index_node = node.child(2).unwrap();
let index = IndexExpression::from_syntax(index_node, source, context)?; let index = IndexExpression::from_syntax(index_node, source, context)?;
let index_end_node = node.child(4);
let index_end = if let Some(index_end_node) = index_end_node {
Some(IndexExpression::from_syntax(
index_end_node,
source,
context,
)?)
} else {
None
};
Ok(Index { Ok(Index {
collection, collection,
index, index,
index_end,
source_position: SourcePosition::from(node.range()), source_position: SourcePosition::from(node.range()),
}) })
} }
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> { fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
match self.collection.expected_type(context)? { match self.collection.expected_type(context)? {
Type::List(item_type) => Ok(*item_type.clone()), Type::ListOf(item_type) => Ok(*item_type.clone()),
Type::Map(structure) => { Type::Map(map_types_option) => {
if let Some(structure) = structure { if let (Some(map_type), IndexExpression::Identifier(identifier)) =
if let IndexExpression::Identifier(identifier) = &self.index { (map_types_option, &self.index)
if let Some((_, r#type)) = structure.inner().get(identifier.inner()) { {
return Ok(r#type.clone()); if let Some(r#type) = map_type.get(&identifier) {
} Ok(r#type.clone())
} } else {
}
Ok(Type::Any) Ok(Type::Any)
} }
} else {
Ok(Type::Any)
}
}
Type::None => Ok(Type::None), Type::None => Ok(Type::None),
r#type => Ok(r#type), r#type => Ok(r#type),
} }
@ -66,10 +56,21 @@ impl AbstractTree for Index {
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> { fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
self.collection.validate(_source, _context)?; self.collection.validate(_source, _context)?;
self.index.validate(_source, _context)?;
if let Some(index_end) = &self.index_end { let collection_type = self.collection.expected_type(_context)?;
index_end.validate(_source, _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(()) Ok(())
@ -81,47 +82,45 @@ impl AbstractTree for Index {
match value { match value {
Value::List(list) => { Value::List(list) => {
let index = self.index.run(source, context)?.as_integer()? as usize; let index = self.index.run(source, context)?.as_integer()? as usize;
let item = list.items()?.get(index).cloned().unwrap_or_default();
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) Ok(item)
} }
Value::Map(map) => { Value::Map(map) => {
let map = map.inner()?; let map = map.inner();
let (key, value) = if let IndexExpression::Identifier(identifier) = &self.index { let value = if let IndexExpression::Identifier(identifier) = &self.index {
let key = identifier.inner(); if let Some(value) = map.get(identifier) {
let value = map.get(key).unwrap_or_default(); value
} else {
(key.clone(), value) return Err(RuntimeError::ValidationFailure(
ValidationError::VariableIdentifierNotFound(identifier.clone()),
));
}
} else { } else {
let index_value = self.index.run(source, context)?; let index_value = self.index.run(source, context)?;
let key = index_value.as_string()?; let identifier = Identifier::new(index_value.as_string()?);
let value = map.get(key.as_str()).unwrap_or_default();
(key.clone(), value) if let Some(value) = map.get(&identifier) {
value
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::VariableIdentifierNotFound(identifier.clone()),
));
}
}; };
if value.is_none() {
Err(RuntimeError::VariableIdentifierNotFound(key))
} else {
Ok(value.clone()) Ok(value.clone())
} }
}
Value::String(string) => { Value::String(string) => {
let index = self.index.run(source, context)?.as_integer()? as usize; let index = self.index.run(source, context)?.as_integer()? as usize;
let item = string.chars().nth(index).unwrap_or_default(); let item = string.chars().nth(index).unwrap_or_default();
Ok(Value::string(item.to_string())) Ok(Value::string(item.to_string()))
} }
_ => Err(RuntimeError::ExpectedCollection { actual: value }), _ => Err(RuntimeError::ValidationFailure(
ValidationError::ExpectedCollection { actual: value },
)),
} }
} }
} }
@ -131,10 +130,5 @@ impl Format for Index {
self.collection.format(output, indent_level); self.collection.format(output, indent_level);
output.push(':'); output.push(':');
self.index.format(output, indent_level); self.index.format(output, indent_level);
if let Some(expression) = &self.index_end {
output.push_str("..");
expression.format(output, indent_level);
}
} }
} }

View File

@ -2,8 +2,8 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
error::{RuntimeError, SyntaxError, ValidationError}, error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, AssignmentOperator, Context, Format, Index, IndexExpression, Statement, AbstractTree, AssignmentOperator, Context, Format, Identifier, Index, IndexExpression,
SyntaxNode, Type, Value, SourcePosition, Statement, SyntaxNode, Type, Value,
}; };
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
@ -11,11 +11,12 @@ pub struct IndexAssignment {
index: Index, index: Index,
operator: AssignmentOperator, operator: AssignmentOperator,
statement: Statement, statement: Statement,
position: SourcePosition,
} }
impl AbstractTree for IndexAssignment { impl AbstractTree for IndexAssignment {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> { fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "index_assignment", node)?; SyntaxError::expect_syntax_node("index_assignment", node)?;
let index_node = node.child(0).unwrap(); let index_node = node.child(0).unwrap();
let index = Index::from_syntax(index_node, source, context)?; let index = Index::from_syntax(index_node, source, context)?;
@ -30,6 +31,7 @@ impl AbstractTree for IndexAssignment {
index, index,
operator, operator,
statement, statement,
position: node.range().into(),
}) })
} }
@ -44,11 +46,14 @@ impl AbstractTree for IndexAssignment {
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> { fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let _index_collection = self.index.collection.run(source, context)?; let _index_collection = self.index.collection.run(source, context)?;
let index_key = if let IndexExpression::Identifier(identifier) = &self.index.index { let index_identifier = if let IndexExpression::Identifier(identifier) = &self.index.index {
identifier.inner() identifier
} else { } else {
return Err(RuntimeError::VariableIdentifierNotFound( let index_run = self.index.index.run(source, context)?;
self.index.index.run(source, context)?.to_string(), let expected_identifier = Identifier::new(index_run.as_string()?);
return Err(RuntimeError::ValidationFailure(
ValidationError::VariableIdentifierNotFound(expected_identifier),
)); ));
}; };
@ -56,17 +61,17 @@ impl AbstractTree for IndexAssignment {
let new_value = match self.operator { let new_value = match self.operator {
AssignmentOperator::PlusEqual => { AssignmentOperator::PlusEqual => {
if let Some(mut previous_value) = context.get_value(index_key)? { if let Some(previous_value) = context.get_value(index_identifier)? {
previous_value += value; previous_value.add(value, self.position)?
previous_value
} else { } else {
Value::none() return Err(RuntimeError::ValidationFailure(
ValidationError::VariableIdentifierNotFound(index_identifier.clone()),
));
} }
} }
AssignmentOperator::MinusEqual => { AssignmentOperator::MinusEqual => {
if let Some(mut previous_value) = context.get_value(index_key)? { if let Some(previous_value) = context.get_value(index_identifier)? {
previous_value -= value; previous_value.subtract(value, self.position)?
previous_value
} else { } else {
Value::none() Value::none()
} }
@ -74,7 +79,7 @@ impl AbstractTree for IndexAssignment {
AssignmentOperator::Equal => value, AssignmentOperator::Equal => value,
}; };
context.set_value(index_key.clone(), new_value)?; context.set_value(index_identifier.clone(), new_value)?;
Ok(Value::none()) Ok(Value::none())
} }

View File

@ -16,7 +16,7 @@ pub enum IndexExpression {
impl AbstractTree for IndexExpression { impl AbstractTree for IndexExpression {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> { fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "index_expression", node)?; SyntaxError::expect_syntax_node("index_expression", node)?;
let first_child = node.child(0).unwrap(); let first_child = node.child(0).unwrap();
let child = if first_child.is_named() { let child = if first_child.is_named() {
@ -40,8 +40,7 @@ impl AbstractTree for IndexExpression {
return Err(SyntaxError::UnexpectedSyntaxNode { return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "value, identifier, index or function call".to_string(), expected: "value, identifier, index or function call".to_string(),
actual: child.kind().to_string(), actual: child.kind().to_string(),
location: child.start_position(), position: node.range().into(),
relevant_source: source[child.byte_range()].to_string(),
}) })
} }
}; };
@ -58,13 +57,17 @@ impl AbstractTree for IndexExpression {
} }
} }
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> { fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
match self { match self {
IndexExpression::Value(value_node) => value_node.validate(_source, _context), IndexExpression::Value(value_node) => value_node.validate(_source, context),
IndexExpression::Identifier(identifier) => identifier.validate(_source, _context), IndexExpression::Identifier(identifier) => {
IndexExpression::Index(index) => index.validate(_source, _context), context.add_allowance(identifier)?;
Ok(())
}
IndexExpression::Index(index) => index.validate(_source, context),
IndexExpression::FunctionCall(function_call) => { IndexExpression::FunctionCall(function_call) => {
function_call.validate(_source, _context) function_call.validate(_source, context)
} }
} }
} }
@ -83,12 +86,8 @@ impl Format for IndexExpression {
fn format(&self, output: &mut String, indent_level: u8) { fn format(&self, output: &mut String, indent_level: u8) {
match self { match self {
IndexExpression::Value(value_node) => { IndexExpression::Value(value_node) => {
if let ValueNode::BuiltInValue(built_in_value) = value_node {
output.push_str(built_in_value.name());
} else {
value_node.format(output, indent_level); value_node.format(output, indent_level);
} }
}
IndexExpression::Identifier(identifier) => identifier.format(output, indent_level), IndexExpression::Identifier(identifier) => identifier.format(output, indent_level),
IndexExpression::FunctionCall(function_call) => { IndexExpression::FunctionCall(function_call) => {
function_call.format(output, indent_level) function_call.format(output, indent_level)

View File

@ -15,7 +15,7 @@ pub struct Logic {
impl AbstractTree for Logic { impl AbstractTree for Logic {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> { fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "logic", node)?; SyntaxError::expect_syntax_node("logic", node)?;
let first_node = node.child(0).unwrap(); let first_node = node.child(0).unwrap();
let (left_node, operator_node, right_node) = { let (left_node, operator_node, right_node) = {
@ -45,6 +45,8 @@ impl AbstractTree for Logic {
} }
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> { fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
log::info!("VALIDATE logic expression");
self.left.validate(_source, _context)?; self.left.validate(_source, _context)?;
self.right.validate(_source, _context) self.right.validate(_source, _context)
} }
@ -52,6 +54,9 @@ impl AbstractTree for Logic {
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> { fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let left = self.left.run(source, context)?; let left = self.left.run(source, context)?;
let right = self.right.run(source, context)?; let right = self.right.run(source, context)?;
log::info!("RUN logic expression: {left} {} {right}", self.operator);
let result = match self.operator { let result = match self.operator {
LogicOperator::Equal => { LogicOperator::Equal => {
if let (Ok(left_num), Ok(right_num)) = (left.as_number(), right.as_number()) { if let (Ok(left_num), Ok(right_num)) = (left.as_number(), right.as_number()) {

View File

@ -1,3 +1,5 @@
use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
@ -20,10 +22,10 @@ pub enum LogicOperator {
impl AbstractTree for LogicOperator { impl AbstractTree for LogicOperator {
fn from_syntax( fn from_syntax(
node: SyntaxNode, node: SyntaxNode,
source: &str, _source: &str,
_context: &Context, _context: &Context,
) -> Result<Self, SyntaxError> { ) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "logic_operator", node)?; SyntaxError::expect_syntax_node("logic_operator", node)?;
let operator_node = node.child(0).unwrap(); let operator_node = node.child(0).unwrap();
let operator = match operator_node.kind() { let operator = match operator_node.kind() {
@ -39,8 +41,7 @@ impl AbstractTree for LogicOperator {
return Err(SyntaxError::UnexpectedSyntaxNode { return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "==, !=, &&, ||, >, <, >= or <=".to_string(), expected: "==, !=, &&, ||, >, <, >= or <=".to_string(),
actual: operator_node.kind().to_string(), actual: operator_node.kind().to_string(),
location: operator_node.start_position(), position: node.range().into(),
relevant_source: source[operator_node.byte_range()].to_string(),
}) })
} }
}; };
@ -75,3 +76,18 @@ impl Format for LogicOperator {
} }
} }
} }
impl Display for LogicOperator {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
LogicOperator::Equal => write!(f, "="),
LogicOperator::NotEqual => write!(f, "!="),
LogicOperator::And => write!(f, "&&"),
LogicOperator::Or => write!(f, "||"),
LogicOperator::Greater => write!(f, ">"),
LogicOperator::Less => write!(f, "<"),
LogicOperator::GreaterOrEqual => write!(f, ">="),
LogicOperator::LessOrEqual => write!(f, "<="),
}
}
}

View File

@ -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) = &current_identifier {
properties.insert(identifier.clone(), (statement, current_type.clone()));
}
}
}
Ok(MapNode {
properties,
position: SourcePosition::from(node.range()),
})
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
if self.properties.is_empty() {
return Ok(Type::Map(None));
}
let mut type_map = BTreeMap::new();
for (identifier, (statement, r#type_option)) in &self.properties {
let r#type = if let Some(r#type) = type_option {
r#type.clone()
} else {
statement.expected_type(_context)?
};
type_map.insert(identifier.clone(), r#type);
}
Ok(Type::Map(Some(type_map)))
}
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
for (_key, (statement, r#type)) in &self.properties {
statement.validate(_source, context)?;
if let Some(expected) = r#type {
let actual = statement.expected_type(context)?;
if !expected.accepts(&actual) {
return Err(ValidationError::TypeCheck {
expected: expected.clone(),
actual,
position: self.position.clone(),
});
}
}
}
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
let mut map = Map::new();
for (key, (statement, _)) in &self.properties {
let value = statement.run(_source, _context)?;
map.set(key.clone(), value);
}
Ok(Value::Map(map))
}
}
impl Format for MapNode {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -3,41 +3,41 @@
//! Note that this module is called "match" but is escaped as "r#match" because //! Note that this module is called "match" but is escaped as "r#match" because
//! "match" is a keyword in Rust. //! "match" is a keyword in Rust.
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{ use crate::{
error::{RuntimeError, SyntaxError, ValidationError}, error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Expression, Format, Statement, SyntaxNode, Type, Value, AbstractTree, Context, Expression, Format, MatchPattern, Statement, Type, Value,
}; };
/// Abstract representation of a match statement. /// Abstract representation of a match statement.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Match { pub struct Match {
matcher: Expression, matcher: Expression,
options: Vec<(Expression, Statement)>, options: Vec<(MatchPattern, Statement)>,
fallback: Option<Box<Statement>>, fallback: Option<Box<Statement>>,
#[serde(skip)]
context: Context,
} }
impl AbstractTree for Match { impl AbstractTree for Match {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> { fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "match", node)?; SyntaxError::expect_syntax_node("match", node)?;
let matcher_node = node.child(1).unwrap(); let matcher_node = node.child(1).unwrap();
let matcher = Expression::from_syntax(matcher_node, source, context)?; let matcher = Expression::from_syntax(matcher_node, source, context)?;
let mut options = Vec::new(); let mut options = Vec::new();
let mut previous_expression = None; let mut previous_pattern = None;
let mut next_statement_is_fallback = false; let mut next_statement_is_fallback = false;
let mut fallback = None; let mut fallback = None;
for index in 2..node.child_count() { for index in 2..node.child_count() {
let child = node.child(index).unwrap(); let child = node.child(index).unwrap();
if child.kind() == "*" { if child.kind() == "match_pattern" {
next_statement_is_fallback = true; previous_pattern = Some(MatchPattern::from_syntax(child, source, context)?);
}
if child.kind() == "expression" {
previous_expression = Some(Expression::from_syntax(child, source, context)?);
} }
if child.kind() == "statement" { if child.kind() == "statement" {
@ -46,7 +46,7 @@ impl AbstractTree for Match {
if next_statement_is_fallback { if next_statement_is_fallback {
fallback = Some(Box::new(statement)); fallback = Some(Box::new(statement));
next_statement_is_fallback = false; next_statement_is_fallback = false;
} else if let Some(expression) = &previous_expression { } else if let Some(expression) = &previous_pattern {
options.push((expression.clone(), statement)); options.push((expression.clone(), statement));
} }
} }
@ -56,6 +56,7 @@ impl AbstractTree for Match {
matcher, matcher,
options, options,
fallback, fallback,
context: Context::default(),
}) })
} }
@ -68,9 +69,15 @@ impl AbstractTree for Match {
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> { fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
self.matcher.validate(_source, _context)?; self.matcher.validate(_source, _context)?;
for (expression, statement) in &self.options { for (match_pattern, statement) in &self.options {
expression.validate(_source, _context)?; if let MatchPattern::EnumPattern(enum_pattern) = match_pattern {
statement.validate(_source, _context)?; 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 { if let Some(statement) = &self.fallback {
@ -83,10 +90,27 @@ impl AbstractTree for Match {
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> { fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let matcher_value = self.matcher.run(source, context)?; let matcher_value = self.matcher.run(source, context)?;
for (expression, statement) in &self.options { for (pattern, statement) in &self.options {
let option_value = expression.run(source, context)?; 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); return statement.run(source, context);
} }
} }

View 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!()
}
}

View File

@ -2,7 +2,8 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
error::{RuntimeError, SyntaxError, ValidationError}, error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Expression, Format, MathOperator, SyntaxNode, Type, Value, AbstractTree, Context, Expression, Format, MathOperator, SourcePosition, SyntaxNode, Type,
Value,
}; };
/// Abstract representation of a math operation. /// Abstract representation of a math operation.
@ -14,11 +15,12 @@ pub struct Math {
left: Expression, left: Expression,
operator: MathOperator, operator: MathOperator,
right: Expression, right: Expression,
position: SourcePosition,
} }
impl AbstractTree for Math { impl AbstractTree for Math {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> { fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "math", node)?; SyntaxError::expect_syntax_node("math", node)?;
let left_node = node.child(0).unwrap(); let left_node = node.child(0).unwrap();
let left = Expression::from_syntax(left_node, source, context)?; let left = Expression::from_syntax(left_node, source, context)?;
@ -33,6 +35,7 @@ impl AbstractTree for Math {
left, left,
operator, operator,
right, right,
position: node.range().into(),
}) })
} }
@ -49,12 +52,12 @@ impl AbstractTree for Math {
let left = self.left.run(source, context)?; let left = self.left.run(source, context)?;
let right = self.right.run(source, context)?; let right = self.right.run(source, context)?;
let value = match self.operator { let value = match self.operator {
MathOperator::Add => left + right, MathOperator::Add => left.add(right, self.position)?,
MathOperator::Subtract => left - right, MathOperator::Subtract => left.subtract(right, self.position)?,
MathOperator::Multiply => left * right, MathOperator::Multiply => left.multiply(right, self.position)?,
MathOperator::Divide => left / right, MathOperator::Divide => left.divide(right, self.position)?,
MathOperator::Modulo => left % right, MathOperator::Modulo => left.modulo(right, self.position)?,
}?; };
Ok(value) Ok(value)
} }

View File

@ -17,9 +17,11 @@ pub enum MathOperator {
impl AbstractTree for MathOperator { impl AbstractTree for MathOperator {
fn from_syntax( fn from_syntax(
node: SyntaxNode, node: SyntaxNode,
source: &str, _source: &str,
_context: &Context, _context: &Context,
) -> Result<Self, SyntaxError> { ) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("math_operator", node)?;
let operator_node = node.child(0).unwrap(); let operator_node = node.child(0).unwrap();
let operator = match operator_node.kind() { let operator = match operator_node.kind() {
"+" => MathOperator::Add, "+" => MathOperator::Add,
@ -31,8 +33,7 @@ impl AbstractTree for MathOperator {
return Err(SyntaxError::UnexpectedSyntaxNode { return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "+, -, *, / or %".to_string(), expected: "+, -, *, / or %".to_string(),
actual: operator_node.kind().to_string(), actual: operator_node.kind().to_string(),
location: operator_node.start_position(), position: node.range().into(),
relevant_source: source[operator_node.byte_range()].to_string(),
}) })
} }
}; };

View File

@ -1,17 +1,13 @@
//! Abstract, executable representations of corresponding items found in Dust //! Abstract, executable representations of corresponding items found in Dust
//! source code. The types that implement [AbstractTree] are inteded to be //! source code. The types that implement [AbstractTree] are inteded to be
//! created by an [Evaluator]. //! created by an [Interpreter].
//!
//! When adding new lanugage features, first extend the grammar to recognize new
//! syntax nodes. Then add a new AbstractTree type using the existing types as
//! examples.
pub mod r#as; pub mod r#as;
pub mod assignment; pub mod assignment;
pub mod assignment_operator; pub mod assignment_operator;
pub mod block; pub mod block;
pub mod built_in_value;
pub mod command; pub mod command;
pub mod enum_defintion;
pub mod enum_pattern;
pub mod expression; pub mod expression;
pub mod r#for; pub mod r#for;
pub mod function_call; pub mod function_call;
@ -24,22 +20,26 @@ pub mod index_assignment;
pub mod index_expression; pub mod index_expression;
pub mod logic; pub mod logic;
pub mod logic_operator; pub mod logic_operator;
pub mod map_node;
pub mod r#match; pub mod r#match;
pub mod match_pattern;
pub mod math; pub mod math;
pub mod math_operator; pub mod math_operator;
pub mod new;
pub mod statement; pub mod statement;
pub mod struct_definition;
pub mod r#type; pub mod r#type;
pub mod type_definition;
pub mod type_specification; pub mod type_specification;
pub mod value_node; pub mod value_node;
pub mod r#while; pub mod r#while;
pub use { pub use {
assignment::*, assignment_operator::*, block::*, built_in_value::*, command::*, expression::*, assignment::*, assignment_operator::*, block::*, command::*, enum_defintion::*,
function_call::*, function_expression::*, function_node::*, identifier::*, if_else::*, enum_pattern::*, expression::*, function_call::*, function_expression::*, function_node::*,
index::*, index_assignment::IndexAssignment, index_expression::*, logic::*, logic_operator::*, identifier::*, if_else::*, index::*, index_assignment::IndexAssignment, index_expression::*,
math::*, math_operator::*, new::*, r#as::*, r#for::*, r#match::*, r#type::*, r#while::*, logic::*, logic_operator::*, map_node::*, match_pattern::*, math::*, math_operator::*, r#as::*,
statement::*, type_specification::*, value_node::*, r#for::*, r#match::*, r#type::*, r#while::*, statement::*, struct_definition::*,
type_definition::*, type_specification::*, value_node::*,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -50,6 +50,7 @@ use crate::{
SyntaxNode, Value, SyntaxNode, Value,
}; };
/// A detailed report of a position in the source code string.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct SourcePosition { pub struct SourcePosition {
pub start_byte: usize, pub start_byte: usize,
@ -73,6 +74,7 @@ impl From<tree_sitter::Range> for SourcePosition {
} }
} }
/// Abstraction that represents a whole, executable unit of dust code.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Root { pub struct Root {
statements: Vec<Statement>, statements: Vec<Statement>,
@ -83,7 +85,7 @@ pub struct Root {
// top-level statements in the tree. // top-level statements in the tree.
impl AbstractTree for Root { impl AbstractTree for Root {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> { fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "root", node)?; SyntaxError::expect_syntax_node("root", node)?;
let statement_count = node.child_count(); let statement_count = node.child_count();
let mut statements = Vec::with_capacity(statement_count); let mut statements = Vec::with_capacity(statement_count);
@ -100,12 +102,8 @@ impl AbstractTree for Root {
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> { fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
for statement in &self.statements { for statement in &self.statements {
if let Statement::Return(inner_statement) = statement {
return inner_statement.validate(_source, _context);
} else {
statement.validate(_source, _context)?; statement.validate(_source, _context)?;
} }
}
Ok(()) Ok(())
} }
@ -114,10 +112,10 @@ impl AbstractTree for Root {
let mut value = Value::none(); let mut value = Value::none();
for statement in &self.statements { 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);
} }
} }
@ -144,8 +142,8 @@ impl Format for Root {
/// This trait is implemented by the Evaluator's internal types to form an /// This trait is implemented by the Evaluator's internal types to form an
/// executable tree that resolves to a single value. /// executable tree that resolves to a single value.
pub trait AbstractTree: Sized + Format { pub trait AbstractTree: Sized + Format {
/// Interpret the syntax tree at the given node and return the abstraction. Returns a syntax /// Interpret the syntax tree at the given node and return the abstraction.
/// error if the source is invalid. /// Returns a syntax error if the source is invalid.
/// ///
/// This function is used to convert nodes in the Tree Sitter concrete /// This function is used to convert nodes in the Tree Sitter concrete
/// syntax tree into executable nodes in an abstract tree. This function is /// syntax tree into executable nodes in an abstract tree. This function is
@ -156,15 +154,16 @@ pub trait AbstractTree: Sized + Format {
/// node's byte range. /// node's byte range.
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError>; fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError>;
/// Return the type of the value that this abstract node will create when run. Returns a /// Return the type of the value that this abstract node will create when
/// validation error if the tree is invalid. /// run. Returns a validation error if the tree is invalid.
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError>; fn expected_type(&self, context: &Context) -> Result<Type, ValidationError>;
/// Verify the type integrity of the node. Returns a validation error if the tree is invalid. /// 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 validate(&self, source: &str, context: &Context) -> Result<(), ValidationError>;
/// Execute this node's logic and return a value. Returns a runtime error if the node cannot /// Execute this node's logic and return a value. Returns a runtime error if
/// resolve to a value. /// the node cannot resolve to a value.
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError>; fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError>;
} }

View File

@ -3,130 +3,200 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
error::{RuntimeError, SyntaxError, ValidationError}, error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Assignment, Block, Context, Expression, For, Format, IfElse, IndexAssignment, AbstractTree, Assignment, Block, Context, Expression, For, Format, IfElse, IndexAssignment,
Match, SyntaxNode, Type, Value, While, Match, SyntaxNode, Type, TypeDefinition, Value, While,
}; };
/// Abstract representation of a statement. /// Abstract representation of a statement.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] #[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>), Assignment(Box<Assignment>),
Expression(Expression), Expression(Expression),
IfElse(Box<IfElse>), IfElse(Box<IfElse>),
Match(Match), Match(Match),
While(Box<While>), While(Box<While>),
Block(Box<Block>), Block(Box<Block>),
Return(Box<Statement>),
For(Box<For>), For(Box<For>),
IndexAssignment(Box<IndexAssignment>), IndexAssignment(Box<IndexAssignment>),
TypeDefinition(TypeDefinition),
} }
impl AbstractTree for Statement { impl AbstractTree for StatementKind {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> { fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "statement", node)?; SyntaxError::expect_syntax_node("statement_kind", node)?;
let child = node.child(0).unwrap(); let child = node.child(0).unwrap();
match child.kind() { match child.kind() {
"assignment" => Ok(Statement::Assignment(Box::new( "assignment" => Ok(StatementKind::Assignment(Box::new(
Assignment::from_syntax(child, source, context)?, Assignment::from_syntax(child, source, context)?,
))), ))),
"expression" => Ok(Statement::Expression(Expression::from_syntax( "expression" => Ok(StatementKind::Expression(Expression::from_syntax(
child, source, context, child, source, context,
)?)), )?)),
"if_else" => Ok(Statement::IfElse(Box::new(IfElse::from_syntax( "if_else" => Ok(StatementKind::IfElse(Box::new(IfElse::from_syntax(
child, source, context, child, source, context,
)?))), )?))),
"while" => Ok(Statement::While(Box::new(While::from_syntax( "while" => Ok(StatementKind::While(Box::new(While::from_syntax(
child, source, context, child, source, context,
)?))), )?))),
"block" => Ok(Statement::Block(Box::new(Block::from_syntax( "block" => Ok(StatementKind::Block(Box::new(Block::from_syntax(
child, source, context, child, source, context,
)?))), )?))),
"for" => Ok(Statement::For(Box::new(For::from_syntax( "for" => Ok(StatementKind::For(Box::new(For::from_syntax(
child, source, context, child, source, context,
)?))), )?))),
"index_assignment" => Ok(Statement::IndexAssignment(Box::new( "index_assignment" => Ok(StatementKind::IndexAssignment(Box::new(
IndexAssignment::from_syntax(child, source, context)?, IndexAssignment::from_syntax(child, source, context)?,
))), ))),
"match" => Ok(Statement::Match(Match::from_syntax( "match" => Ok(StatementKind::Match(Match::from_syntax(
child, source, context, child, source, context,
)?)), )?)),
"return" => { "type_definition" => Ok(StatementKind::TypeDefinition(TypeDefinition::from_syntax(
let statement_node = child.child(1).unwrap(); child, source, context
)?)),
Ok(Statement::Return(Box::new(Statement::from_syntax(statement_node, source, context)?)))
},
_ => Err(SyntaxError::UnexpectedSyntaxNode { _ => Err(SyntaxError::UnexpectedSyntaxNode {
expected: 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(), actual: child.kind().to_string(),
location: child.start_position(), position: node.range().into(),
relevant_source: source[child.byte_range()].to_string(),
}), }),
} }
} }
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> { fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
match self { match self {
Statement::Assignment(assignment) => assignment.expected_type(context), StatementKind::Assignment(assignment) => assignment.expected_type(_context),
Statement::Expression(expression) => expression.expected_type(context), StatementKind::Expression(expression) => expression.expected_type(_context),
Statement::IfElse(if_else) => if_else.expected_type(context), StatementKind::IfElse(if_else) => if_else.expected_type(_context),
Statement::Match(r#match) => r#match.expected_type(context), StatementKind::Match(r#match) => r#match.expected_type(_context),
Statement::While(r#while) => r#while.expected_type(context), StatementKind::While(r#while) => r#while.expected_type(_context),
Statement::Block(block) => block.expected_type(context), StatementKind::Block(block) => block.expected_type(_context),
Statement::For(r#for) => r#for.expected_type(context), StatementKind::For(r#for) => r#for.expected_type(_context),
Statement::IndexAssignment(index_assignment) => index_assignment.expected_type(context), StatementKind::IndexAssignment(index_assignment) => {
Statement::Return(statement) => statement.expected_type(context), index_assignment.expected_type(_context)
}
StatementKind::TypeDefinition(type_definition) => {
type_definition.expected_type(_context)
}
} }
} }
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> { fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
match self { match self {
Statement::Assignment(assignment) => assignment.validate(_source, _context), StatementKind::Assignment(assignment) => assignment.validate(_source, _context),
Statement::Expression(expression) => expression.validate(_source, _context), StatementKind::Expression(expression) => expression.validate(_source, _context),
Statement::IfElse(if_else) => if_else.validate(_source, _context), StatementKind::IfElse(if_else) => if_else.validate(_source, _context),
Statement::Match(r#match) => r#match.validate(_source, _context), StatementKind::Match(r#match) => r#match.validate(_source, _context),
Statement::While(r#while) => r#while.validate(_source, _context), StatementKind::While(r#while) => r#while.validate(_source, _context),
Statement::Block(block) => block.validate(_source, _context), StatementKind::Block(block) => block.validate(_source, _context),
Statement::For(r#for) => r#for.validate(_source, _context), StatementKind::For(r#for) => r#for.validate(_source, _context),
Statement::IndexAssignment(index_assignment) => { StatementKind::IndexAssignment(index_assignment) => {
index_assignment.validate(_source, _context) index_assignment.validate(_source, _context)
} }
Statement::Return(statement) => statement.validate(_source, _context), StatementKind::TypeDefinition(type_definition) => {
type_definition.validate(_source, _context)
}
} }
} }
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> { fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
match self { match self {
Statement::Assignment(assignment) => assignment.run(source, context), StatementKind::Assignment(assignment) => assignment.run(_source, _context),
Statement::Expression(expression) => expression.run(source, context), StatementKind::Expression(expression) => expression.run(_source, _context),
Statement::IfElse(if_else) => if_else.run(source, context), StatementKind::IfElse(if_else) => if_else.run(_source, _context),
Statement::Match(r#match) => r#match.run(source, context), StatementKind::Match(r#match) => r#match.run(_source, _context),
Statement::While(r#while) => r#while.run(source, context), StatementKind::While(r#while) => r#while.run(_source, _context),
Statement::Block(block) => block.run(source, context), StatementKind::Block(block) => block.run(_source, _context),
Statement::For(r#for) => r#for.run(source, context), StatementKind::For(r#for) => r#for.run(_source, _context),
Statement::IndexAssignment(index_assignment) => index_assignment.run(source, context), StatementKind::IndexAssignment(index_assignment) => {
Statement::Return(statement) => statement.run(source, context), index_assignment.run(_source, _context)
}
StatementKind::TypeDefinition(type_definition) => {
type_definition.run(_source, _context)
}
} }
} }
} }
impl Format for Statement { impl Format for StatementKind {
fn format(&self, output: &mut String, indent_level: u8) { fn format(&self, output: &mut String, indent_level: u8) {
Statement::indent(output, indent_level); StatementKind::indent(output, indent_level);
match self { match self {
Statement::Assignment(assignment) => assignment.format(output, indent_level), StatementKind::Assignment(assignment) => assignment.format(output, indent_level),
Statement::Expression(expression) => expression.format(output, indent_level), StatementKind::Expression(expression) => expression.format(output, indent_level),
Statement::IfElse(if_else) => if_else.format(output, indent_level), StatementKind::IfElse(if_else) => if_else.format(output, indent_level),
Statement::Match(r#match) => r#match.format(output, indent_level), StatementKind::Match(r#match) => r#match.format(output, indent_level),
Statement::While(r#while) => r#while.format(output, indent_level), StatementKind::While(r#while) => r#while.format(output, indent_level),
Statement::Block(block) => block.format(output, indent_level), StatementKind::Block(block) => block.format(output, indent_level),
Statement::For(r#for) => r#for.format(output, indent_level), StatementKind::For(r#for) => r#for.format(output, indent_level),
Statement::IndexAssignment(index_assignment) => { StatementKind::IndexAssignment(index_assignment) => {
index_assignment.format(output, indent_level) index_assignment.format(output, indent_level)
} }
Statement::Return(statement) => statement.format(output, indent_level), StatementKind::TypeDefinition(type_definition) => {
type_definition.format(output, indent_level)
}
} }
} }
} }

View 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)) = (&current_identifier, &current_type) {
properties.insert(identifier.clone(), (None, r#type.clone()));
}
}
current_type = None;
current_identifier =
Some(Identifier::from_syntax(child_syntax_node, source, context)?);
}
if child_syntax_node.kind() == "type_specification" {
current_type = Some(
TypeSpecification::from_syntax(child_syntax_node, source, context)?
.take_inner(),
);
}
if child_syntax_node.kind() == "statement" {
current_statement =
Some(Statement::from_syntax(child_syntax_node, source, context)?);
if let Some(identifier) = &current_identifier {
let r#type = if let Some(r#type) = &current_type {
r#type.clone()
} else {
Type::None
};
properties.insert(
identifier.clone(),
(current_statement.clone(), r#type.clone()),
);
}
}
}
Ok(StructDefinition { name, properties })
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::None)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
fn run(&self, _source: &str, context: &Context) -> Result<Value, RuntimeError> {
context.set_definition(self.name.clone(), TypeDefinition::Struct(self.clone()))?;
Ok(Value::none())
}
}
impl Format for StructDefinition {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -1,10 +1,15 @@
use std::fmt::{self, Display, Formatter}; use std::{
collections::BTreeMap,
fmt::{self, Display, Formatter},
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{ use crate::{
built_in_types::BuiltInType,
error::{RuntimeError, SyntaxError, ValidationError}, error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, Identifier, Structure, SyntaxNode, Value, AbstractTree, Context, Format, Identifier, TypeSpecification, Value,
}; };
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
@ -12,25 +17,37 @@ pub enum Type {
Any, Any,
Boolean, Boolean,
Collection, Collection,
Custom(Identifier), Custom {
name: Identifier,
arguments: Vec<Type>,
},
Float, Float,
Function { Function {
parameter_types: Vec<Type>, parameter_types: Vec<Type>,
return_type: Box<Type>, return_type: Box<Type>,
}, },
Integer, Integer,
List(Box<Type>), List,
Map(Option<Structure>), ListOf(Box<Type>),
ListExact(Vec<Type>),
Map(Option<BTreeMap<Identifier, Type>>),
None, None,
Number, Number,
String, String,
Range, Range,
Option(Box<Type>),
} }
impl Type { 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 { pub fn list(item_type: Type) -> Self {
Type::List(Box::new(item_type)) Type::ListOf(Box::new(item_type))
} }
pub fn function(parameter_types: Vec<Type>, return_type: Type) -> Self { pub fn function(parameter_types: Vec<Type>, return_type: Type) -> Self {
@ -40,10 +57,6 @@ impl Type {
} }
} }
pub fn option(optional_type: Type) -> Self {
Type::Option(Box::new(optional_type))
}
/// Returns a boolean indicating whether is type is accepting of the other. /// 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 /// The types do not need to match exactly. For example, the Any variant matches all of the
@ -56,36 +69,54 @@ impl Type {
| (_, Type::Any) | (_, Type::Any)
| (Type::Boolean, Type::Boolean) | (Type::Boolean, Type::Boolean)
| (Type::Collection, Type::Collection) | (Type::Collection, Type::Collection)
| (Type::Collection, Type::List(_)) | (Type::Collection, Type::String)
| (Type::List(_), Type::Collection) | (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::Collection, Type::Map(_))
| (Type::Map(_), Type::Collection) | (Type::Map(_), Type::Collection)
| (Type::Collection, Type::String)
| (Type::String, Type::Collection) | (Type::String, Type::Collection)
| (Type::Float, Type::Float) | (Type::Float, Type::Float)
| (Type::Integer, Type::Integer) | (Type::Integer, Type::Integer)
| (Type::Map(_), Type::Map(_)) | (Type::List, Type::List)
| (Type::Map(None), Type::Map(None))
| (Type::Number, Type::Number) | (Type::Number, Type::Number)
| (Type::Number, Type::Integer) | (Type::Number, Type::Integer)
| (Type::Number, Type::Float) | (Type::Number, Type::Float)
| (Type::Integer, Type::Number) | (Type::Integer, Type::Number)
| (Type::Float, Type::Number) | (Type::Float, Type::Number)
| (Type::None, Type::None) | (Type::String, Type::String)
| (Type::String, Type::String) => true, | (Type::None, Type::None) => true,
(Type::Custom(left), Type::Custom(right)) => left == right, (Type::Map(left_types), Type::Map(right_types)) => left_types == right_types,
(Type::Option(_), Type::None) => true, (
(Type::Option(left), Type::Option(right)) => { Type::Custom {
if let Type::Any = left.as_ref() { name: left_name,
true arguments: left_arguments,
} else if left == right { },
true Type::Custom {
} else { name: right_name,
false arguments: right_arguments,
} },
} ) => left_name == right_name && left_arguments == right_arguments,
(Type::List(self_item_type), Type::List(other_item_type)) => { (Type::ListOf(self_item_type), Type::ListOf(other_item_type)) => {
self_item_type.accepts(&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 { Type::Function {
parameter_types: self_parameter_types, parameter_types: self_parameter_types,
@ -117,7 +148,7 @@ impl Type {
} }
pub fn is_list(&self) -> bool { pub fn is_list(&self) -> bool {
matches!(self, Type::List(_)) matches!(self, Type::ListOf(_))
} }
pub fn is_map(&self) -> bool { pub fn is_map(&self) -> bool {
@ -129,18 +160,64 @@ impl AbstractTree for Type {
fn from_syntax( fn from_syntax(
node: SyntaxNode, node: SyntaxNode,
_source: &str, _source: &str,
_context: &Context, context: &Context,
) -> Result<Self, SyntaxError> { ) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(_source, "type", node)?; SyntaxError::expect_syntax_node("type", node)?;
let type_node = node.child(0).unwrap(); let type_node = node.child(0).unwrap();
let r#type = match type_node.kind() { 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_node = node.child(1).unwrap();
let item_type = Type::from_syntax(item_type_node, _source, _context)?; let item_type = Type::from_syntax(item_type_node, _source, context)?;
Type::List(Box::new(item_type)) 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, "any" => Type::Any,
"bool" => Type::Boolean, "bool" => Type::Boolean,
@ -154,7 +231,7 @@ impl AbstractTree for Type {
let child = node.child(index).unwrap(); let child = node.child(index).unwrap();
if child.is_named() { if child.is_named() {
let parameter_type = Type::from_syntax(child, _source, _context)?; let parameter_type = Type::from_syntax(child, _source, context)?;
parameter_types.push(parameter_type); parameter_types.push(parameter_type);
} }
@ -162,9 +239,9 @@ impl AbstractTree for Type {
let final_node = node.child(child_count - 1).unwrap(); let final_node = node.child(child_count - 1).unwrap();
let return_type = if final_node.is_named() { let return_type = if final_node.is_named() {
Type::from_syntax(final_node, _source, _context)? Type::from_syntax(final_node, _source, context)?
} else { } else {
Type::None Type::option(None)
}; };
Type::Function { Type::Function {
@ -177,18 +254,12 @@ impl AbstractTree for Type {
"num" => Type::Number, "num" => Type::Number,
"none" => Type::None, "none" => Type::None,
"str" => Type::String, "str" => Type::String,
"option" => {
let inner_type_node = node.child(2).unwrap();
let inner_type = Type::from_syntax(inner_type_node, _source, _context)?;
Type::Option(Box::new(inner_type))
}
_ => { _ => {
return Err(SyntaxError::UnexpectedSyntaxNode { return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "any, bool, float, int, num, str, option, (, [ or {".to_string(), expected: "any, bool, float, int, num, str, list, map, custom type, (, [ or {"
.to_string(),
actual: type_node.kind().to_string(), actual: type_node.kind().to_string(),
location: type_node.start_position(), position: node.range().into(),
relevant_source: _source[type_node.byte_range()].to_string(),
}) })
} }
}; };
@ -215,8 +286,10 @@ impl Format for Type {
Type::Any => output.push_str("any"), Type::Any => output.push_str("any"),
Type::Boolean => output.push_str("bool"), Type::Boolean => output.push_str("bool"),
Type::Collection => output.push_str("collection"), Type::Collection => output.push_str("collection"),
Type::Custom {
Type::Custom(_) => todo!(), name: _,
arguments: _,
} => todo!(),
Type::Float => output.push_str("float"), Type::Float => output.push_str("float"),
Type::Function { Type::Function {
parameter_types, parameter_types,
@ -236,26 +309,19 @@ impl Format for Type {
return_type.format(output, indent_level); return_type.format(output, indent_level);
} }
Type::Integer => output.push_str("int"), Type::Integer => output.push_str("int"),
Type::List(item_type) => { Type::List => todo!(),
Type::ListOf(item_type) => {
output.push('['); output.push('[');
item_type.format(output, indent_level); item_type.format(output, indent_level);
output.push(']'); output.push(']');
} }
Type::Map(structure_option) => { Type::ListExact(_) => todo!(),
if let Some(structure) = structure_option { Type::Map(_) => {
output.push_str(&structure.to_string());
} else {
output.push_str("map"); output.push_str("map");
} }
} Type::None => output.push_str("Option::None"),
Type::None => output.push_str("none"),
Type::Number => output.push_str("num"), Type::Number => output.push_str("num"),
Type::String => output.push_str("str"), Type::String => output.push_str("str"),
Type::Option(optional_type) => {
output.push_str("option(");
optional_type.format(output, indent_level);
output.push(')');
}
Type::Range => todo!(), Type::Range => todo!(),
} }
} }
@ -267,7 +333,23 @@ impl Display for Type {
Type::Any => write!(f, "any"), Type::Any => write!(f, "any"),
Type::Boolean => write!(f, "bool"), Type::Boolean => write!(f, "bool"),
Type::Collection => write!(f, "collection"), Type::Collection => write!(f, "collection"),
Type::Custom(identifier) => write!(f, "{identifier}"), 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::Float => write!(f, "float"),
Type::Function { Type::Function {
parameter_types, parameter_types,
@ -287,14 +369,25 @@ impl Display for Type {
write!(f, " -> {return_type}") write!(f, " -> {return_type}")
} }
Type::Integer => write!(f, "int"), Type::Integer => write!(f, "int"),
Type::List(item_type) => write!(f, "[{item_type}]"), 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::Map(_) => write!(f, "map"),
Type::Number => write!(f, "num"), Type::Number => write!(f, "num"),
Type::None => write!(f, "none"), Type::None => write!(f, "none"),
Type::String => write!(f, "str"), Type::String => write!(f, "str"),
Type::Option(inner_type) => {
write!(f, "option({})", inner_type)
}
Type::Range => todo!(), Type::Range => todo!(),
} }
} }

View File

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

View File

@ -26,7 +26,7 @@ impl TypeSpecification {
impl AbstractTree for TypeSpecification { impl AbstractTree for TypeSpecification {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> { fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "type_specification", node)?; SyntaxError::expect_syntax_node("type_specification", node)?;
let type_node = node.child(1).unwrap(); let type_node = node.child(1).unwrap();
let r#type = Type::from_syntax(type_node, source, context)?; let r#type = Type::from_syntax(type_node, source, context)?;

View File

@ -1,11 +1,12 @@
use std::{cmp::Ordering, collections::BTreeMap, ops::RangeInclusive}; use std::{cmp::Ordering, ops::RangeInclusive};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{ use crate::{
error::{RuntimeError, SyntaxError, ValidationError}, error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, BuiltInValue, Context, Expression, Format, Function, FunctionNode, Identifier, AbstractTree, Context, Expression, Format, Function, FunctionNode,
List, Map, SourcePosition, Statement, Structure, SyntaxNode, Type, TypeSpecification, Value, Identifier, List, Type, Value, TypeDefinition, MapNode,
}; };
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
@ -16,16 +17,22 @@ pub enum ValueNode {
Integer(String), Integer(String),
String(String), String(String),
List(Vec<Expression>), List(Vec<Expression>),
Option(Option<Box<Expression>>), Map(MapNode),
Map(BTreeMap<String, (Statement, Option<Type>)>, SourcePosition),
BuiltInValue(BuiltInValue),
Structure(BTreeMap<String, (Option<Statement>, Type)>),
Range(RangeInclusive<i64>), Range(RangeInclusive<i64>),
Struct {
name: Identifier,
properties: MapNode,
},
Enum {
name: Identifier,
variant: Identifier,
expression: Option<Box<Expression>>,
},
} }
impl AbstractTree for ValueNode { impl AbstractTree for ValueNode {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> { fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "value", node)?; SyntaxError::expect_syntax_node("value", node)?;
let child = node.child(0).unwrap(); let child = node.child(0).unwrap();
let value_node = match child.kind() { let value_node = match child.kind() {
@ -58,107 +65,7 @@ impl AbstractTree for ValueNode {
ValueNode::List(expressions) ValueNode::List(expressions)
} }
"map" => { "map" => {
let mut child_nodes = BTreeMap::new(); ValueNode::Map(MapNode::from_syntax(child, source, context)?)
let mut current_key = "".to_string();
let mut current_type = None;
for index in 0..child.child_count() - 1 {
let child = child.child(index).unwrap();
if child.kind() == "identifier" {
current_key = Identifier::from_syntax(child, source, context)?.take_inner();
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)?;
child_nodes.insert(current_key.clone(), (statement, current_type.clone()));
}
}
ValueNode::Map(child_nodes, SourcePosition::from(child.range()))
}
"option" => {
let first_grandchild = child.child(0).unwrap();
if first_grandchild.kind() == "none" {
ValueNode::Option(None)
} else {
let expression_node = child.child(2).unwrap();
let expression = Expression::from_syntax(expression_node, source, context)?;
ValueNode::Option(Some(Box::new(expression)))
}
}
"built_in_value" => {
let built_in_value_node = child.child(0).unwrap();
ValueNode::BuiltInValue(BuiltInValue::from_syntax(
built_in_value_node,
source,
context,
)?)
}
"structure" => {
let mut btree_map = 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..child.child_count() - 1 {
let child_syntax_node = child.child(index).unwrap();
if child_syntax_node.kind() == "identifier" {
if current_statement.is_none() {
if let (Some(identifier), Some(r#type)) =
(&current_identifier, &current_type)
{
btree_map
.insert(identifier.inner().clone(), (None, r#type.clone()));
}
}
current_type = None;
current_identifier =
Some(Identifier::from_syntax(child_syntax_node, source, context)?);
}
if child_syntax_node.kind() == "type_specification" {
current_type = Some(
TypeSpecification::from_syntax(child_syntax_node, source, context)?
.take_inner(),
);
}
if child_syntax_node.kind() == "statement" {
current_statement =
Some(Statement::from_syntax(child_syntax_node, source, context)?);
// if let Some(identifier) = &current_identifier {
// let r#type = if let Some(r#type) = &current_type {
// r#type.clone()
// } else if let Some(statement) = &current_statement {
// statement.expected_type(context)?
// } else {
// Type::None
// };
// btree_map.insert(
// identifier.inner().clone(),
// (current_statement.clone(), r#type.clone()),
// );
// }
}
}
ValueNode::Structure(btree_map)
} }
"range" => { "range" => {
let mut split = source[child.byte_range()].split(".."); let mut split = source[child.byte_range()].split("..");
@ -167,14 +74,41 @@ impl AbstractTree for ValueNode {
ValueNode::Range(start..=end) ValueNode::Range(start..=end)
} }
"enum_instance" => {
let name_node = child.child(0).unwrap();
let name = Identifier::from_syntax(name_node, source, context)?;
let variant_node = child.child(2).unwrap();
let variant = Identifier::from_syntax(variant_node, source, context)?;
let expression = if let Some(expression_node) = child.child(4) {
Some(Box::new(Expression::from_syntax(expression_node, source, context)?))
} else {
None
};
ValueNode::Enum { name, variant , expression }
}
"struct_instance" => {
let name_node = child.child(0).unwrap();
let name = Identifier::from_syntax(name_node, source, context)?;
let properties_node = child.child(2).unwrap();
let properties = MapNode::from_syntax(properties_node, source, context)?;
ValueNode::Struct
{
name,
properties
}
}
_ => { _ => {
return Err(SyntaxError::UnexpectedSyntaxNode { return Err(SyntaxError::UnexpectedSyntaxNode {
expected: expected:
"string, integer, float, boolean, range, list, map, option, function or structure" "string, integer, float, boolean, range, list, map, option, function, struct or enum"
.to_string(), .to_string(),
actual: child.kind().to_string(), actual: child.kind().to_string(),
location: child.start_position(), position: node.range().into(),
relevant_source: source[child.byte_range()].to_string(),
}) })
} }
}; };
@ -186,49 +120,52 @@ impl AbstractTree for ValueNode {
let r#type = match self { let r#type = match self {
ValueNode::Boolean(_) => Type::Boolean, ValueNode::Boolean(_) => Type::Boolean,
ValueNode::Float(_) => Type::Float, ValueNode::Float(_) => Type::Float,
ValueNode::Function(function) => function.r#type().clone(), ValueNode::Function(function) => function.r#type(),
ValueNode::Integer(_) => Type::Integer, ValueNode::Integer(_) => Type::Integer,
ValueNode::String(_) => Type::String, ValueNode::String(_) => Type::String,
ValueNode::List(expressions) => { ValueNode::List(expressions) => {
let mut previous_type = None; let mut item_types = Vec::new();
for expression in expressions { for expression in expressions {
let expression_type = expression.expected_type(context)?; let expression_type = expression.expected_type(context)?;
if let Some(previous) = previous_type { item_types.push(expression_type);
if expression_type != previous {
return Ok(Type::List(Box::new(Type::Any)));
}
} }
previous_type = Some(expression_type); Type::ListExact(item_types)
} }
ValueNode::Map(map_node) => map_node.expected_type(context)?,
if let Some(previous) = previous_type { ValueNode::Struct { name, .. } => {
Type::List(Box::new(previous)) Type::custom(name.clone(), Vec::with_capacity(0))
} else {
Type::List(Box::new(Type::Any))
}
}
ValueNode::Option(option) => {
if let Some(expression) = option {
Type::Option(Box::new(expression.expected_type(context)?))
} else {
Type::None
}
}
ValueNode::Map(_, _) => Type::Map(None),
ValueNode::BuiltInValue(built_in_value) => built_in_value.expected_type(context)?,
ValueNode::Structure(node_map) => {
let mut value_map = BTreeMap::new();
for (key, (_statement_option, r#type)) in node_map {
value_map.insert(key.to_string(), (None, r#type.clone()));
}
Type::Map(Some(Structure::new(value_map)))
} }
ValueNode::Range(_) => Type::Range, 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) Ok(r#type)
@ -241,22 +178,15 @@ impl AbstractTree for ValueNode {
function_node.validate(_source, context)?; function_node.validate(_source, context)?;
} }
} }
ValueNode::Map(statements, source_position) => { ValueNode::Map(map_node) => map_node.validate(_source, context)?,
for (_key, (statement, r#type)) in statements { ValueNode::Enum { name, expression, .. } => {
if let Some(expected) = r#type { name.validate(_source, context)?;
let actual = statement.expected_type(context)?;
if !expected.accepts(&actual) { if let Some(expression) = expression {
return Err(ValidationError::TypeCheck { expression.validate(_source, context)?;
expected: expected.clone(),
actual,
position: source_position.clone(),
});
} }
} }
} _ => {},
}
_ => {}
} }
Ok(()) Ok(())
@ -284,45 +214,48 @@ impl AbstractTree for ValueNode {
Value::List(List::with_items(values)) Value::List(List::with_items(values))
} }
ValueNode::Option(option) => { ValueNode::Map(map_node) => map_node.run(source, context)?,
let option_value = if let Some(expression) = option {
Some(Box::new(expression.run(source, context)?))
} else {
None
};
Value::Option(option_value)
}
ValueNode::Map(key_statement_pairs, _) => {
let mut map = BTreeMap::new();
{
for (key, (statement, _)) in key_statement_pairs {
let value = statement.run(source, context)?;
map.insert(key.clone(), value);
}
}
Value::Map(Map::with_values(map))
}
ValueNode::BuiltInValue(built_in_value) => built_in_value.run(source, context)?,
ValueNode::Structure(node_map) => {
let mut value_map = BTreeMap::new();
for (key, (statement_option, r#type)) in node_map {
let value_option = if let Some(statement) = statement_option {
Some(statement.run(source, context)?)
} else {
None
};
value_map.insert(key.to_string(), (value_option, r#type.clone()));
}
Value::Structure(Structure::new(value_map))
}
ValueNode::Range(range) => Value::Range(range.clone()), ValueNode::Range(range) => Value::Range(range.clone()),
ValueNode::Struct { name, properties } => {
let instance = if let Some(definition) = context.get_definition(name)? {
if let TypeDefinition::Struct(struct_definition) = definition {
struct_definition.instantiate(properties, source, context)?
} else {
return Err(RuntimeError::ValidationFailure(ValidationError::ExpectedStructDefintion { actual: definition.clone() }))
}
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::TypeDefinitionNotFound(name.clone())
));
};
Value::Struct(instance)
}
ValueNode::Enum { name, variant, expression } => {
let value = if let Some(expression) = expression {
expression.run(source, context)?
} else {
Value::none()
};
let instance = if let Some(definition) = context.get_definition(name)? {
if let TypeDefinition::Enum(enum_defintion) = definition {
enum_defintion.instantiate(variant.clone(), Some(value))
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::ExpectedEnumDefintion {
actual: definition.clone()
}
));
}
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::TypeDefinitionNotFound(name.clone())
));
};
Value::Enum(instance)
},
}; };
Ok(value) Ok(value)
@ -350,63 +283,13 @@ impl Format for ValueNode {
output.push(']'); output.push(']');
} }
ValueNode::Option(option) => { ValueNode::Map(map_node) => map_node.format(output, indent_level),
if let Some(expression) = option { ValueNode::Struct { name, properties } => {
output.push_str("some("); name.format(output, indent_level);
expression.format(output, indent_level); properties.format(output, indent_level);
output.push(')');
} else {
output.push_str("none");
}
}
ValueNode::Map(nodes, _) => {
output.push_str("{\n");
for (key, (statement, type_option)) in nodes {
if let Some(r#type) = type_option {
ValueNode::indent(output, indent_level + 1);
output.push_str(key);
output.push_str(" <");
r#type.format(output, 0);
output.push_str("> = ");
statement.format(output, 0);
} else {
ValueNode::indent(output, indent_level + 1);
output.push_str(key);
output.push_str(" = ");
statement.format(output, 0);
}
output.push('\n');
}
ValueNode::indent(output, indent_level);
output.push('}');
}
ValueNode::BuiltInValue(built_in_value) => built_in_value.format(output, indent_level),
ValueNode::Structure(nodes) => {
output.push('{');
for (key, (value_option, r#type)) in nodes {
if let Some(value) = value_option {
output.push_str(" ");
output.push_str(key);
output.push_str(" <");
r#type.format(output, indent_level);
output.push_str("> = ");
value.format(output, indent_level);
} else {
output.push_str(" ");
output.push_str(key);
output.push_str(" <");
r#type.format(output, indent_level);
output.push('>');
}
}
output.push('}');
} }
ValueNode::Range(_) => todo!(), ValueNode::Range(_) => todo!(),
ValueNode::Enum { .. } => todo!(),
} }
} }
} }
@ -426,14 +309,41 @@ impl Ord for ValueNode {
(ValueNode::String(_), _) => Ordering::Greater, (ValueNode::String(_), _) => Ordering::Greater,
(ValueNode::List(left), ValueNode::List(right)) => left.cmp(right), (ValueNode::List(left), ValueNode::List(right)) => left.cmp(right),
(ValueNode::List(_), _) => Ordering::Greater, (ValueNode::List(_), _) => Ordering::Greater,
(ValueNode::Option(left), ValueNode::Option(right)) => left.cmp(right), (ValueNode::Map(left), ValueNode::Map(right)) => left.cmp(right),
(ValueNode::Option(_), _) => Ordering::Greater, (ValueNode::Map(_), _) => Ordering::Greater,
(ValueNode::Map(left, _), ValueNode::Map(right, _)) => left.cmp(right), (ValueNode::Struct{ name: left_name, properties: left_properties }, ValueNode::Struct {name: right_name, properties: right_properties} ) => {
(ValueNode::Map(_, _), _) => Ordering::Greater, let name_cmp = left_name.cmp(right_name);
(ValueNode::BuiltInValue(left), ValueNode::BuiltInValue(right)) => left.cmp(right),
(ValueNode::BuiltInValue(_), _) => Ordering::Greater, if name_cmp.is_eq() {
(ValueNode::Structure(left), ValueNode::Structure(right)) => left.cmp(right), left_properties.cmp(right_properties)
(ValueNode::Structure(_), _) => Ordering::Greater, } 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(left), ValueNode::Range(right)) => left.clone().cmp(right.clone()),
(ValueNode::Range(_), _) => Ordering::Less, (ValueNode::Range(_), _) => Ordering::Less,
} }
@ -441,7 +351,7 @@ impl Ord for ValueNode {
} }
impl PartialOrd for ValueNode { impl PartialOrd for ValueNode {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
} }
} }

View File

@ -16,7 +16,7 @@ pub struct While {
impl AbstractTree for While { impl AbstractTree for While {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> { fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "while", node)?; SyntaxError::expect_syntax_node("while", node)?;
let expression_node = node.child(1).unwrap(); let expression_node = node.child(1).unwrap();
let expression = Expression::from_syntax(expression_node, source, context)?; let expression = Expression::from_syntax(expression_node, source, context)?;
@ -32,15 +32,21 @@ impl AbstractTree for While {
} }
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> { fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
log::info!("VALIDATE while loop");
self.expression.validate(_source, context)?; self.expression.validate(_source, context)?;
self.block.validate(_source, context) self.block.validate(_source, context)
} }
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> { fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
log::info!("RUN while loop start");
while self.expression.run(source, context)?.as_boolean()? { while self.expression.run(source, context)?.as_boolean()? {
self.block.run(source, context)?; self.block.run(source, context)?;
} }
log::info!("RUN while loop end");
Ok(Value::none()) Ok(Value::none())
} }
} }

View File

@ -1,4 +1,4 @@
use std::fs::read_to_string; use std::{fs::File, io::Read};
use enum_iterator::{all, Sequence}; use enum_iterator::{all, Sequence};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -46,7 +46,11 @@ impl Callable for Fs {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?; RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let path = arguments.first().unwrap().as_string()?; let path = arguments.first().unwrap().as_string()?;
let file_content = read_to_string(path.as_str())?; 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)) Ok(Value::string(file_content))
} }

View File

@ -7,7 +7,10 @@ use std::fmt::{self, Display, Formatter};
use rand::{random, thread_rng, Rng}; use rand::{random, thread_rng, Rng};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{error::RuntimeError, Context, Format, Type, Value}; use crate::{
error::{RuntimeError, ValidationError},
Context, EnumInstance, Format, Identifier, Type, Value,
};
use self::{fs::Fs, json::Json, str::StrFunction}; use self::{fs::Fs, json::Json, str::StrFunction};
@ -19,7 +22,7 @@ pub trait Callable {
&self, &self,
arguments: &[Value], arguments: &[Value],
source: &str, source: &str,
outer_context: &Context, context: &Context,
) -> Result<Value, RuntimeError>; ) -> Result<Value, RuntimeError>;
} }
@ -87,37 +90,46 @@ impl Callable for BuiltInFunction {
&self, &self,
arguments: &[Value], arguments: &[Value],
_source: &str, _source: &str,
_outer_context: &Context, context: &Context,
) -> Result<Value, RuntimeError> { ) -> Result<Value, RuntimeError> {
match self { match self {
BuiltInFunction::AssertEqual => { BuiltInFunction::AssertEqual => {
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?; RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
let left = arguments.first().unwrap(); let left = arguments.get(0).unwrap();
let right = arguments.get(1).unwrap(); let right = arguments.get(1).unwrap();
Ok(Value::Boolean(left == right)) 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, _outer_context)
}
BuiltInFunction::Json(json_function) => {
json_function.call(arguments, _source, _outer_context)
} }
BuiltInFunction::Fs(fs_function) => fs_function.call(arguments, _source, context),
BuiltInFunction::Json(json_function) => json_function.call(arguments, _source, context),
BuiltInFunction::Length => { BuiltInFunction::Length => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?; RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
let value = arguments.first().unwrap(); let value = arguments.first().unwrap();
let length = if let Ok(list) = value.as_list() { let length = if let Ok(list) = value.as_list() {
list.items().len() list.items()?.len()
} else if let Ok(map) = value.as_map() { } else if let Ok(map) = value.as_map() {
map.inner()?.len() map.inner().len()
} else if let Ok(str) = value.as_string() { } else if let Ok(str) = value.as_string() {
str.chars().count() str.chars().count()
} else { } else {
return Err(RuntimeError::ExpectedCollection { return Err(RuntimeError::ValidationFailure(
ValidationError::ExpectedCollection {
actual: value.clone(), actual: value.clone(),
}); },
));
}; };
Ok(Value::Integer(length as i64)) Ok(Value::Integer(length as i64))
@ -147,7 +159,7 @@ impl Callable for BuiltInFunction {
let value = arguments.first().unwrap(); let value = arguments.first().unwrap();
if let Ok(list) = value.as_list() { if let Ok(list) = value.as_list() {
let items = list.items(); let items = list.items()?;
if items.len() == 0 { if items.len() == 0 {
Ok(Value::none()) Ok(Value::none())
@ -167,7 +179,7 @@ impl Callable for BuiltInFunction {
Ok(Value::Integer(random())) Ok(Value::Integer(random()))
} }
BuiltInFunction::String(string_function) => { BuiltInFunction::String(string_function) => {
string_function.call(arguments, _source, _outer_context) string_function.call(arguments, _source, context)
} }
} }
} }

View File

@ -1,7 +1,7 @@
use enum_iterator::Sequence; use enum_iterator::Sequence;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{error::RuntimeError, Context, List, Type, Value}; use crate::{error::RuntimeError, Context, EnumInstance, Identifier, List, Type, Value};
use super::Callable; use super::Callable;
@ -122,7 +122,7 @@ impl Callable for StrFunction {
} }
StrFunction::Find => Type::function( StrFunction::Find => Type::function(
vec![Type::String, Type::String], vec![Type::String, Type::String],
Type::option(Type::Integer), Type::option(Some(Type::Integer)),
), ),
StrFunction::Insert => Type::function( StrFunction::Insert => Type::function(
vec![Type::String, Type::Integer, Type::String], vec![Type::String, Type::Integer, Type::String],
@ -137,7 +137,7 @@ impl Callable for StrFunction {
StrFunction::Parse => Type::function(vec![Type::String], Type::Any), StrFunction::Parse => Type::function(vec![Type::String], Type::Any),
StrFunction::Remove => Type::function( StrFunction::Remove => Type::function(
vec![Type::String, Type::Integer], vec![Type::String, Type::Integer],
Type::option(Type::String), Type::option(Some(Type::String)),
), ),
StrFunction::ReplaceRange => Type::function( StrFunction::ReplaceRange => Type::function(
vec![Type::String, Type::list(Type::Integer), Type::String], vec![Type::String, Type::list(Type::Integer), Type::String],
@ -175,9 +175,10 @@ impl Callable for StrFunction {
StrFunction::StartsWith => { StrFunction::StartsWith => {
Type::function(vec![Type::String, Type::String], Type::Boolean) Type::function(vec![Type::String, Type::String], Type::Boolean)
} }
StrFunction::StripPrefix => { StrFunction::StripPrefix => Type::function(
Type::function(vec![Type::String, Type::String], Type::option(Type::String)) vec![Type::String, Type::String],
} Type::option(Some(Type::String)),
),
StrFunction::ToLowercase => Type::function(vec![Type::String], Type::String), StrFunction::ToLowercase => Type::function(vec![Type::String], Type::String),
StrFunction::ToUppercase => Type::function(vec![Type::String], Type::String), StrFunction::ToUppercase => Type::function(vec![Type::String], Type::String),
StrFunction::Truncate => { StrFunction::Truncate => {
@ -202,7 +203,7 @@ impl Callable for StrFunction {
&self, &self,
arguments: &[Value], arguments: &[Value],
_source: &str, _source: &str,
_outer_context: &Context, _context: &Context,
) -> Result<Value, RuntimeError> { ) -> Result<Value, RuntimeError> {
let value = match self { let value = match self {
StrFunction::AsBytes => { StrFunction::AsBytes => {
@ -233,9 +234,21 @@ impl Callable for StrFunction {
let pattern = pattern_string.as_str(); let pattern = pattern_string.as_str();
let find = string let find = string
.find(pattern) .find(pattern)
.map(|index| Box::new(Value::Integer(index as i64))); .map(|index| Value::Integer(index as i64));
Value::Option(find) 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 => { StrFunction::IsAscii => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?; RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
@ -292,9 +305,9 @@ impl Callable for StrFunction {
let string = arguments.first().unwrap().as_string()?; let string = arguments.first().unwrap().as_string()?;
if let Ok(integer) = string.parse::<i64>() { if let Ok(integer) = string.parse::<i64>() {
Value::option(Some(Value::Integer(integer))) Value::Integer(integer)
} else if let Ok(float) = string.parse::<f64>() { } else if let Ok(float) = string.parse::<f64>() {
Value::option(Some(Value::Float(float))) Value::Float(float)
} else { } else {
Value::none() Value::none()
} }
@ -321,9 +334,9 @@ impl Callable for StrFunction {
RuntimeError::expect_argument_amount(self.name(), 3, arguments.len())?; RuntimeError::expect_argument_amount(self.name(), 3, arguments.len())?;
let mut string = arguments.first().unwrap().as_string()?.clone(); let mut string = arguments.first().unwrap().as_string()?.clone();
let range = arguments.get(1).unwrap().as_list()?.items(); let range = arguments.get(1).unwrap().as_list()?.items()?;
let start = range.first().unwrap_or_default().as_integer()? as usize; let start = range[0].as_integer()? as usize;
let end = range.get(1).unwrap_or_default().as_integer()? as usize; let end = range[1].as_integer()? as usize;
let pattern = arguments.get(2).unwrap().as_string()?; let pattern = arguments.get(2).unwrap().as_string()?;
string.replace_range(start..end, pattern); string.replace_range(start..end, pattern);
@ -413,7 +426,11 @@ impl Callable for StrFunction {
])) ]))
}); });
Value::option(sections) if let Some(sections) = sections {
Value::some(sections)
} else {
Value::none()
}
} }
StrFunction::SplitTerminator => { StrFunction::SplitTerminator => {
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?; RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
@ -458,7 +475,15 @@ impl Callable for StrFunction {
.strip_prefix(prefix) .strip_prefix(prefix)
.map(|remainder| Value::string(remainder.to_string())); .map(|remainder| Value::string(remainder.to_string()));
Value::option(stripped) if let Some(value) = stripped {
Value::Enum(EnumInstance::new(
Identifier::new("Option"),
Identifier::new("Some"),
Some(value),
))
} else {
Value::none()
}
} }
StrFunction::ToLowercase => { StrFunction::ToLowercase => {
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?; RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;

View 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())))
}
}
}
}

View 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
View 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
View 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(),
}
}
}

View File

@ -1,213 +0,0 @@
use std::{
cmp::Ordering,
collections::BTreeMap,
sync::{Arc, RwLock, RwLockReadGuard},
};
use crate::{error::rw_lock_error::RwLockError, Type, Value};
#[derive(Clone, Debug)]
pub struct Context {
inner: Arc<RwLock<BTreeMap<String, ValueData>>>,
}
impl Context {
pub fn new() -> Self {
Self {
inner: Arc::new(RwLock::new(BTreeMap::new())),
}
}
pub fn inner(&self) -> Result<RwLockReadGuard<BTreeMap<String, ValueData>>, RwLockError> {
Ok(self.inner.read()?)
}
pub fn with_variables_from(other: &Context) -> Result<Context, RwLockError> {
let mut new_variables = BTreeMap::new();
for (identifier, value_data) in other.inner.read()?.iter() {
new_variables.insert(identifier.clone(), value_data.clone());
}
Ok(Context {
inner: Arc::new(RwLock::new(new_variables)),
})
}
pub fn inherit_from(&self, other: &Context) -> Result<(), RwLockError> {
let mut self_variables = self.inner.write()?;
for (identifier, value_data) in other.inner.read()?.iter() {
let existing_data = self_variables.get(identifier);
if let Some(ValueData::Value { .. }) = existing_data {
continue;
} else {
self_variables.insert(identifier.clone(), value_data.clone());
}
}
Ok(())
}
pub fn get_value(&self, key: &str) -> Result<Option<Value>, RwLockError> {
if let Some(value_data) = self.inner.read()?.get(key) {
if let ValueData::Value { inner, .. } = value_data {
return Ok(Some(inner.clone()));
}
}
Ok(None)
}
pub fn get_type(&self, key: &str) -> Result<Option<Type>, RwLockError> {
if let Some(value_data) = self.inner.read()?.get(key) {
match value_data {
ValueData::Value { inner, .. } => Ok(Some(inner.r#type())),
ValueData::ExpectedType { inner, .. } => Ok(Some(inner.clone())),
}
} else {
Ok(None)
}
}
pub fn set_value(&self, key: String, value: Value) -> Result<(), RwLockError> {
self.inner.write()?.insert(
key,
ValueData::Value {
inner: value,
runtime_uses: Arc::new(RwLock::new(0)),
},
);
Ok(())
}
pub fn set_type(&self, key: String, r#type: Type) -> Result<(), RwLockError> {
self.inner
.write()?
.insert(key, ValueData::ExpectedType { inner: r#type });
Ok(())
}
pub fn unset(&self, key: &str) -> Result<(), RwLockError> {
self.inner.write()?.remove(key);
Ok(())
}
}
impl Default for Context {
fn default() -> Self {
Context::new()
}
}
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<std::cmp::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)
}
}
#[derive(Clone, Debug)]
pub enum ValueData {
Value {
inner: Value,
runtime_uses: Arc<RwLock<u16>>,
},
ExpectedType {
inner: Type,
},
}
impl Eq for ValueData {}
impl PartialEq for ValueData {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(
ValueData::Value {
inner: left_inner,
runtime_uses: left_runtime_uses,
},
ValueData::Value {
inner: right_inner,
runtime_uses: right_runtime_uses,
},
) => {
if left_inner != right_inner {
return false;
} else {
*left_runtime_uses.read().unwrap() == *right_runtime_uses.read().unwrap()
}
}
(
ValueData::ExpectedType { inner: left_inner },
ValueData::ExpectedType { inner: right_inner },
) => left_inner == right_inner,
_ => false,
}
}
}
impl PartialOrd for ValueData {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ValueData {
fn cmp(&self, other: &Self) -> Ordering {
use Ordering::*;
match (self, other) {
(
ValueData::Value {
inner: inner_left, ..
},
ValueData::Value {
inner: inner_right, ..
},
) => inner_left.cmp(inner_right),
(ValueData::Value { .. }, _) => Greater,
(
ValueData::ExpectedType { inner: inner_left },
ValueData::ExpectedType { inner: inner_right },
) => inner_left.cmp(inner_right),
(ValueData::ExpectedType { .. }, _) => Less,
}
}
}

395
src/context/mod.rs Normal file
View 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);
}
}

View 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,
}

View 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),
}

View File

@ -7,6 +7,7 @@ pub(crate) mod rw_lock_error;
mod syntax_error; mod syntax_error;
mod validation_error; mod validation_error;
use colored::Colorize;
pub use runtime_error::RuntimeError; pub use runtime_error::RuntimeError;
pub use syntax_error::SyntaxError; pub use syntax_error::SyntaxError;
pub use validation_error::ValidationError; pub use validation_error::ValidationError;
@ -15,7 +16,7 @@ use tree_sitter::LanguageError;
use std::fmt::{self, Formatter}; use std::fmt::{self, Formatter};
#[derive(PartialEq)] #[derive(Debug, PartialEq)]
pub enum Error { pub enum Error {
Syntax(SyntaxError), Syntax(SyntaxError),
@ -28,6 +29,46 @@ pub enum Error {
Language(LanguageError), 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 { impl From<SyntaxError> for Error {
fn from(error: SyntaxError) -> Self { fn from(error: SyntaxError) -> Self {
Error::Syntax(error) Error::Syntax(error)
@ -54,20 +95,14 @@ impl From<LanguageError> for Error {
impl std::error::Error for Error {} 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 { impl fmt::Display for Error {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
use Error::*; use Error::*;
match self { match self {
Syntax(error) => write!(f, "Syntax error: {error}"), Syntax(error) => write!(f, "{error}"),
Validation(error) => write!(f, "Validation error: {error}"), Validation(error) => write!(f, "{error}"),
Runtime(error) => write!(f, "Runtime error: {error}"), Runtime(error) => write!(f, "{error}"),
ParserCancelled => write!(f, "Parsing was cancelled because the parser took too long."), ParserCancelled => write!(f, "Parsing was cancelled because the parser took too long."),
Language(_error) => write!(f, "Parser failed to load language grammar."), Language(_error) => write!(f, "Parser failed to load language grammar."),
} }

View File

@ -7,16 +7,30 @@ use std::{
time, time,
}; };
use crate::{Type, Value}; use lyneate::Report;
use super::rw_lock_error::RwLockError; use crate::{SourcePosition, Type, Value};
use super::{rw_lock_error::RwLockError, ValidationError};
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum RuntimeError { 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. /// The attempted conversion is impossible.
ConversionImpossible { ConversionImpossible {
value: Value, from: Type,
target_type: Type, to: Type,
position: SourcePosition,
}, },
Csv(String), Csv(String),
@ -31,71 +45,6 @@ pub enum RuntimeError {
Toml(toml::de::Error), Toml(toml::de::Error),
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,
},
/// Failed to read or write a map. /// Failed to read or write a map.
/// ///
/// See the [MapError] docs for more info. /// See the [MapError] docs for more info.
@ -105,18 +54,55 @@ pub enum RuntimeError {
Utf8(FromUtf8Error), Utf8(FromUtf8Error),
/// Failed to find a variable with a value for this key.
VariableIdentifierNotFound(String),
/// A built-in function was called with the wrong amount of arguments. /// A built-in function was called with the wrong amount of arguments.
ExpectedBuiltInFunctionArgumentAmount { ExpectedBuiltInFunctionArgumentAmount {
function_name: String, function_name: String,
expected: usize, expected: usize,
actual: usize, actual: usize,
}, },
ValidationFailure(ValidationError),
} }
impl RuntimeError { 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( pub fn expect_argument_amount(
function_name: &str, function_name: &str,
expected: usize, expected: usize,
@ -134,6 +120,12 @@ impl RuntimeError {
} }
} }
impl From<ValidationError> for RuntimeError {
fn from(error: ValidationError) -> Self {
RuntimeError::ValidationFailure(error)
}
}
impl From<csv::Error> for RuntimeError { impl From<csv::Error> for RuntimeError {
fn from(error: csv::Error) -> Self { fn from(error: csv::Error) -> Self {
RuntimeError::Csv(error.to_string()) RuntimeError::Csv(error.to_string())
@ -182,12 +174,6 @@ impl From<FromUtf8Error> for RuntimeError {
} }
} }
impl Display for RuntimeError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl From<RwLockError> for RuntimeError { impl From<RwLockError> for RuntimeError {
fn from(error: RwLockError) -> Self { fn from(error: RwLockError) -> Self {
RuntimeError::RwLock(error) RuntimeError::RwLock(error)
@ -199,3 +185,9 @@ impl<T> From<PoisonError<T>> for RuntimeError {
RuntimeError::RwLock(RwLockError) RuntimeError::RwLock(RwLockError)
} }
} }
impl Display for RuntimeError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}

View File

@ -1,7 +1,9 @@
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use colored::Colorize;
use lyneate::Report;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tree_sitter::{Node as SyntaxNode, Point}; use tree_sitter::Node as SyntaxNode;
use crate::SourcePosition; use crate::SourcePosition;
@ -11,7 +13,8 @@ use super::rw_lock_error::RwLockError;
pub enum SyntaxError { pub enum SyntaxError {
/// Invalid user input. /// Invalid user input.
InvalidSource { InvalidSource {
source: String, expected: String,
actual: String,
position: SourcePosition, position: SourcePosition,
}, },
@ -20,35 +23,53 @@ pub enum SyntaxError {
UnexpectedSyntaxNode { UnexpectedSyntaxNode {
expected: String, expected: String,
actual: String, actual: String,
position: SourcePosition,
#[serde(skip)]
location: Point,
relevant_source: String,
}, },
} }
impl SyntaxError { impl SyntaxError {
pub fn expect_syntax_node( pub fn create_report(&self, source: &str) -> String {
source: &str, let messages = match self {
expected: &str, SyntaxError::InvalidSource { position, .. } => self
actual: SyntaxNode, .to_string()
) -> Result<(), SyntaxError> { .split_inclusive(".")
log::info!("Converting {} to abstract node", actual.kind()); .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() { if expected == actual.kind() {
Ok(()) Ok(())
} else if actual.is_error() { } else if actual.is_error() {
Err(SyntaxError::InvalidSource { Err(SyntaxError::InvalidSource {
source: source[actual.byte_range()].to_string(), expected: expected.to_owned(),
actual: actual.kind().to_string(),
position: SourcePosition::from(actual.range()), position: SourcePosition::from(actual.range()),
}) })
} else { } else {
Err(SyntaxError::UnexpectedSyntaxNode { Err(SyntaxError::UnexpectedSyntaxNode {
expected: expected.to_string(), expected: expected.to_string(),
actual: actual.kind().to_string(), actual: actual.kind().to_string(),
location: actual.start_position(), position: SourcePosition::from(actual.range()),
relevant_source: source[actual.byte_range()].to_string(),
}) })
} }
} }
@ -62,6 +83,44 @@ impl From<RwLockError> for SyntaxError {
impl Display for SyntaxError { impl Display for SyntaxError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}") match self {
SyntaxError::InvalidSource {
expected,
actual,
position,
} => {
let actual = if actual == "ERROR" {
"unrecognized characters"
} else {
actual
};
write!(
f,
"Invalid syntax from ({}, {}) to ({}, {}). Exected {} but found {}.",
position.start_row,
position.start_column,
position.end_row,
position.end_column,
expected.bold().green(),
actual.bold().red(),
)
}
SyntaxError::RwLock(_) => todo!(),
SyntaxError::UnexpectedSyntaxNode {
expected,
actual,
position,
} => {
write!(
f,
"Interpreter Error. Tried to parse {actual} as {expected} from ({}, {}) to ({}, {}).",
position.start_row,
position.start_column,
position.end_row,
position.end_column,
)
}
}
} }
} }

View File

@ -1,22 +1,42 @@
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use colored::Colorize;
use lyneate::Report;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{Identifier, SourcePosition, Type, Value}; use crate::{Identifier, SourcePosition, Type, TypeDefinition, Value};
use super::rw_lock_error::RwLockError; use super::rw_lock_error::RwLockError;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum ValidationError { pub enum ValidationError {
/// The 'assert' macro did not resolve successfully. /// Two value are incompatible for addition.
AssertEqualFailed { CannotAdd {
expected: Value, left: Value,
actual: Value, right: Value,
position: SourcePosition, position: SourcePosition,
}, },
/// The 'assert' macro did not resolve successfully. /// Two value are incompatible for subtraction.
AssertFailed { position: SourcePosition }, 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. /// The attempted conversion is impossible.
ConversionImpossible { ConversionImpossible {
@ -24,6 +44,59 @@ pub enum ValidationError {
target_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. /// A built-in function was called with the wrong amount of arguments.
ExpectedBuiltInFunctionArgumentAmount { ExpectedBuiltInFunctionArgumentAmount {
function_name: String, function_name: String,
@ -61,11 +134,116 @@ pub enum ValidationError {
position: SourcePosition, position: SourcePosition,
}, },
/// Failed to find a variable with a value for this key. /// Failed to find a value with this key.
VariableIdentifierNotFound(Identifier), 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 { 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( pub fn expect_argument_amount(
function_name: &str, function_name: &str,
expected: usize, expected: usize,

View File

@ -2,9 +2,9 @@
//! //!
//! This module has three tools to run Dust code. //! This module has three tools to run Dust code.
//! //!
//! - [interpret] is the simples way to run Dust code inside of an application or library //! - [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 //! - [interpret_with_context] allows you to set variables on the execution context
//! - [Interpreter] is an advanced tool that can parse, verify, run and format Dust code //! - [Interpreter] is an advanced tool that can parse, validate, run and format Dust code
//! //!
//! # Examples //! # Examples
//! //!
@ -22,11 +22,11 @@
//! //!
//! ```rust //! ```rust
//! # use dust_lang::*; //! # use dust_lang::*;
//! let context = Map::new(); //! let context = Context::default();
//! //!
//! context.set("one".into(), 1.into()); //! context.set_value("one".into(), 1.into()).unwrap();
//! context.set("two".into(), 2.into()); //! context.set_value("two".into(), 2.into()).unwrap();
//! context.set("three".into(), 3.into()); //! context.set_value("three".into(), 3.into()).unwrap();
//! //!
//! let dust_code = "four = 4; one + two + three + four"; //! let dust_code = "four = 4; one + two + three + four";
//! //!
@ -35,27 +35,20 @@
//! Ok(Value::Integer(10)) //! Ok(Value::Integer(10))
//! ); //! );
//! ``` //! ```
use tree_sitter::{Node as SyntaxNode, Parser, Tree as SyntaxTree, TreeCursor}; use tree_sitter::{Parser, Tree as SyntaxTree};
use crate::{ use crate::{language, AbstractTree, Context, ContextMode, Error, Format, Root, Value};
error::SyntaxError, language, AbstractTree, Context, Error, Format, Root, SourcePosition, Value,
};
/// Interpret the given source code. Returns the value of last statement or the /// Interpret the given source code. Returns the value of last statement or the
/// first error encountered. /// first error encountered.
/// ///
/// See the [module-level docs][self] for more info. /// See the [module-level docs][self] for more info.
pub fn interpret(source: &str) -> Result<Value, Error> { pub fn interpret(source: &str) -> Result<Value, Error> {
interpret_with_context(source, Context::new()) interpret_with_context(source, Context::new(ContextMode::RemoveGarbage))
} }
/// Interpret the given source code with the given context. /// Interpret the given source code with the given context.
/// ///
/// A context is a [Map] instance, which is dust's
/// [BTreeMap][std::collections::btree_map::BTreeMap] that is used internally
/// for the `<map>` type. Any value can be set, including functions and nested
/// maps.
///
/// See the [module-level docs][self] for more info. /// See the [module-level docs][self] for more info.
pub fn interpret_with_context(source: &str, context: Context) -> Result<Value, Error> { pub fn interpret_with_context(source: &str, context: Context) -> Result<Value, Error> {
let mut interpreter = Interpreter::new(context); let mut interpreter = Interpreter::new(context);
@ -66,16 +59,27 @@ pub fn interpret_with_context(source: &str, context: Context) -> Result<Value, E
/// A source code interpreter for the Dust language. /// 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 /// The interpreter's most important functions are used to parse dust source
/// and run it and they are written in a way that forces them to be used safely. Each step in this /// code, verify it is safe and run it. They are written in a way that forces
/// 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. /// 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 { pub struct Interpreter {
parser: Parser, parser: Parser,
context: Context, context: Context,
} }
impl Interpreter { impl Interpreter {
/// Creates a new interpreter with the given variable context. /// Create a new interpreter with the given context.
pub fn new(context: Context) -> Self { pub fn new(context: Context) -> Self {
let mut parser = Parser::new(); let mut parser = Parser::new();
@ -84,17 +88,18 @@ impl Interpreter {
.expect("Language version is incompatible with tree sitter version."); .expect("Language version is incompatible with tree sitter version.");
parser.set_logger(Some(Box::new(|_log_type, message| { parser.set_logger(Some(Box::new(|_log_type, message| {
log::info!("{}", message) log::trace!("{}", message)
}))); })));
Interpreter { parser, context } Interpreter { parser, context }
} }
/// Generates a syntax tree from the source. Returns an error if the the parser is cancelled for /// Generate a syntax tree from the source. Returns an error if the the
/// taking too long. The syntax tree may contain error nodes, which represent syntax errors. /// 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 /// Tree sitter is designed to be run on every keystroke, so this is
/// function to call. /// generally a lightweight function to call.
pub fn parse(&mut self, source: &str) -> Result<SyntaxTree, Error> { pub fn parse(&mut self, source: &str) -> Result<SyntaxTree, Error> {
if let Some(tree) = self.parser.parse(source, None) { if let Some(tree) = self.parser.parse(source, None) {
Ok(tree) Ok(tree)
@ -103,40 +108,15 @@ impl Interpreter {
} }
} }
/// Checks the source for errors and generates an abstract tree. /// Check the source for errors and generate an abstract tree.
/// ///
/// The order in which this function works is: /// The order in which this function works is:
/// ///
/// - parse the source into a syntax tree /// - parse the source into a syntax tree
/// - check the syntax tree for errors
/// - generate an abstract tree from the source and syntax tree /// - generate an abstract tree from the source and syntax tree
/// - check the abstract tree for type errors /// - check the abstract tree for errors
pub fn verify(&mut self, source: &str) -> Result<Root, Error> { pub fn validate(&mut self, source: &str) -> Result<Root, Error> {
fn check_for_error(
node: SyntaxNode,
source: &str,
cursor: &mut TreeCursor,
) -> Result<(), Error> {
if node.is_error() {
Err(Error::Syntax(SyntaxError::InvalidSource {
source: source[node.byte_range()].to_string(),
position: SourcePosition::from(node.range()),
}))
} else {
for child in node.children(&mut cursor.clone()) {
check_for_error(child, source, cursor)?;
}
Ok(())
}
}
let syntax_tree = self.parse(source)?; let syntax_tree = self.parse(source)?;
let root = syntax_tree.root_node();
let mut cursor = syntax_tree.root_node().walk();
check_for_error(root, source, &mut cursor)?;
let abstract_tree = Root::from_syntax(syntax_tree.root_node(), source, &self.context)?; let abstract_tree = Root::from_syntax(syntax_tree.root_node(), source, &self.context)?;
abstract_tree.validate(source, &self.context)?; abstract_tree.validate(source, &self.context)?;
@ -144,31 +124,27 @@ impl Interpreter {
Ok(abstract_tree) Ok(abstract_tree)
} }
/// Runs the source, returning the final statement's value or first error. /// Run the source, returning the final statement's value or first error.
/// ///
/// This function [parses][Self::parse], [verifies][Self::verify] and [runs][Root::run] using /// This function [parses][Self::parse], [validates][Self::validate] and
/// the same source code. /// [runs][Root::run] using the same source code.
pub fn run(&mut self, source: &str) -> Result<Value, Error> { pub fn run(&mut self, source: &str) -> Result<Value, Error> {
self.verify(source)? let final_value = self.validate(source)?.run(source, &self.context)?;
.run(source, &self.context)
.map_err(|error| Error::Runtime(error)) Ok(final_value)
} }
/// Return an s-expression displaying a syntax tree of the source, or the ParserCancelled error /// Return an s-expression displaying a syntax tree of the source or an
/// if the parser takes too long. /// error.
pub fn syntax_tree(&mut self, source: &str) -> Result<String, Error> { pub fn syntax_tree(&mut self, source: &str) -> Result<String, Error> {
Ok(self.parse(source)?.root_node().to_sexp()) Ok(self.parse(source)?.root_node().to_sexp())
} }
/// Return formatted Dust code generated from the current abstract tree, or None if no source /// Return a formatted version of the source.
/// code has been run successfully.
///
/// You should call [verify][Interpreter::verify] before calling this function. You can only
/// create formatted source from a valid abstract tree.
pub fn format(&mut self, source: &str) -> Result<String, Error> { pub fn format(&mut self, source: &str) -> Result<String, Error> {
let mut formatted_output = String::new(); let mut formatted_output = String::new();
self.verify(source)?.format(&mut formatted_output, 0); self.validate(source)?.format(&mut formatted_output, 0);
Ok(formatted_output) Ok(formatted_output)
} }
@ -176,6 +152,6 @@ impl Interpreter {
impl Default for Interpreter { impl Default for Interpreter {
fn default() -> Self { fn default() -> Self {
Interpreter::new(Context::new()) Interpreter::new(Context::default())
} }
} }

View File

@ -5,11 +5,7 @@
//! You can use this library externally by calling either of the "interpret" //! You can use this library externally by calling either of the "interpret"
//! functions or by constructing your own Interpreter. //! functions or by constructing your own Interpreter.
pub use crate::{ pub use crate::{
abstract_tree::*, abstract_tree::*, built_in_functions::BuiltInFunction, context::*, error::Error, interpret::*,
built_in_functions::BuiltInFunction,
context::{Context, ValueData},
error::Error,
interpret::*,
value::*, value::*,
}; };
@ -17,6 +13,10 @@ pub use tree_sitter::Node as SyntaxNode;
pub mod abstract_tree; pub mod abstract_tree;
pub mod built_in_functions; pub mod built_in_functions;
pub mod built_in_identifiers;
pub mod built_in_type_definitions;
pub mod built_in_types;
pub mod built_in_values;
pub mod context; pub mod context;
pub mod error; pub mod error;
pub mod interpret; pub mod interpret;

View File

@ -1,6 +1,7 @@
//! Command line interface for the dust programming language. //! Command line interface for the dust programming language.
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use colored::Colorize;
use crossterm::event::{KeyCode, KeyModifiers}; use crossterm::event::{KeyCode, KeyModifiers};
use nu_ansi_term::{Color, Style}; use nu_ansi_term::{Color, Style};
use reedline::{ use reedline::{
@ -8,9 +9,12 @@ use reedline::{
Reedline, ReedlineEvent, ReedlineMenu, Signal, Span, SqliteBackedHistory, Suggestion, Reedline, ReedlineEvent, ReedlineMenu, Signal, Span, SqliteBackedHistory, Suggestion,
}; };
use std::{borrow::Cow, fs::read_to_string, path::PathBuf, process::Command}; use std::{borrow::Cow, fs::read_to_string, io::Write, path::PathBuf, process::Command};
use dust_lang::{built_in_values, Context, Error, Interpreter, Value, ValueData}; use dust_lang::{
built_in_values::all_built_in_values, Context, ContextMode, Error, Interpreter, Value,
ValueData,
};
/// Command-line arguments to be parsed. /// Command-line arguments to be parsed.
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
@ -20,14 +24,6 @@ struct Args {
#[arg(short, long)] #[arg(short, long)]
command: Option<String>, command: Option<String>,
/// Data to assign to the "input" variable.
#[arg(short, long)]
input: Option<String>,
/// File whose contents will be assigned to the "input" variable.
#[arg(short = 'p', long)]
input_path: Option<String>,
/// Command for alternate functionality besides running the source. /// Command for alternate functionality besides running the source.
#[command(subcommand)] #[command(subcommand)]
cli_command: Option<CliCommand>, cli_command: Option<CliCommand>,
@ -46,24 +42,18 @@ pub enum CliCommand {
} }
fn main() { fn main() {
env_logger::init(); 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 args = Args::parse();
let context = Context::new(); let context = Context::new(ContextMode::AllowGarbage);
if let Some(input) = args.input {
context
.set_value("input".to_string(), Value::string(input))
.unwrap();
}
if let Some(path) = args.input_path {
let file_contents = read_to_string(path).unwrap();
context
.set_value("input".to_string(), Value::string(file_contents))
.unwrap();
}
if args.path.is_none() && args.command.is_none() { if args.path.is_none() && args.command.is_none() {
let run_shell_result = run_shell(context); let run_shell_result = run_shell(context);
@ -111,7 +101,7 @@ fn main() {
println!("{value}") println!("{value}")
} }
} }
Err(error) => eprintln!("{error}"), Err(error) => eprintln!("{}", error.create_report(&source)),
} }
} }
@ -277,7 +267,7 @@ impl Completer for DustCompleter {
} }
} }
for built_in_value in built_in_values() { for built_in_value in all_built_in_values() {
let name = built_in_value.name(); let name = built_in_value.name();
let description = built_in_value.description(); let description = built_in_value.description();
@ -292,7 +282,7 @@ impl Completer for DustCompleter {
} }
if let Value::Map(map) = built_in_value.get() { if let Value::Map(map) = built_in_value.get() {
for (key, value) in map.inner().unwrap().iter() { for (key, value) in map.inner() {
if key.contains(last_word) { if key.contains(last_word) {
suggestions.push(Suggestion { suggestions.push(Suggestion {
value: format!("{name}:{key}"), value: format!("{name}:{key}"),
@ -306,10 +296,11 @@ impl Completer for DustCompleter {
} }
} }
for (key, value_data) in self.context.inner().unwrap().iter() { for (key, (value_data, _counter)) in self.context.inner().unwrap().iter() {
let value = match value_data { let value = match value_data {
ValueData::Value { inner, .. } => inner, ValueData::Value(value) => value,
ValueData::ExpectedType { .. } => continue, ValueData::TypeHint(_) => continue,
ValueData::TypeDefinition(_) => continue,
}; };
if key.contains(last_word) { if key.contains(last_word) {

View 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)
}
}

View File

@ -10,7 +10,7 @@ use stanza::{
table::{Cell, Content, Row, Table}, table::{Cell, Content, Row, Table},
}; };
use crate::Value; use crate::{error::rw_lock_error::RwLockError, Value};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct List(Arc<RwLock<Vec<Value>>>); pub struct List(Arc<RwLock<Vec<Value>>>);
@ -34,17 +34,18 @@ impl List {
List(Arc::new(RwLock::new(items))) List(Arc::new(RwLock::new(items)))
} }
pub fn items(&self) -> RwLockReadGuard<'_, Vec<Value>> { pub fn items(&self) -> Result<RwLockReadGuard<Vec<Value>>, RwLockError> {
self.0.read().unwrap() Ok(self.0.read()?)
} }
pub fn items_mut(&self) -> RwLockWriteGuard<'_, Vec<Value>> { pub fn items_mut(&self) -> Result<RwLockWriteGuard<Vec<Value>>, RwLockError> {
self.0.write().unwrap() Ok(self.0.write()?)
} }
pub fn as_text_table(&self) -> Table { pub fn as_text_table(&self) -> Table {
let cells: Vec<Cell> = self let cells: Vec<Cell> = self
.items() .items()
.unwrap()
.iter() .iter()
.map(|value| { .map(|value| {
if let Value::List(list) = value { if let Value::List(list) = value {
@ -73,32 +74,6 @@ impl List {
} }
} }
impl Eq for List {}
impl PartialEq for List {
fn eq(&self, other: &Self) -> bool {
let left = self.0.read().unwrap().clone().into_iter();
let right = other.0.read().unwrap().clone().into_iter();
left.eq(right)
}
}
impl Ord for List {
fn cmp(&self, other: &Self) -> Ordering {
let left = self.0.read().unwrap().clone().into_iter();
let right = other.0.read().unwrap().clone().into_iter();
left.cmp(right)
}
}
impl PartialOrd for List {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Display for List { impl Display for List {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let renderer = Console::default(); let renderer = Console::default();
@ -106,3 +81,39 @@ impl Display for List {
f.write_str(&renderer.render(&self.as_text_table())) f.write_str(&renderer.render(&self.as_text_table()))
} }
} }
impl Eq for List {}
impl PartialEq for List {
fn eq(&self, other: &Self) -> bool {
if let (Ok(left), Ok(right)) = (self.items(), other.items()) {
if left.len() != right.len() {
return false;
} else {
for (i, j) in left.iter().zip(right.iter()) {
if i != j {
return false;
}
}
}
}
true
}
}
impl PartialOrd for List {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for List {
fn cmp(&self, other: &Self) -> Ordering {
if let (Ok(left), Ok(right)) = (self.items(), other.items()) {
left.cmp(&right)
} else {
Ordering::Equal
}
}
}

View File

@ -1,3 +1,4 @@
use serde::{Deserialize, Serialize};
use stanza::{ use stanza::{
renderer::{console::Console, Renderer}, renderer::{console::Console, Renderer},
style::{HAlign, Styles}, style::{HAlign, Styles},
@ -6,52 +7,47 @@ use stanza::{
use std::{ use std::{
collections::BTreeMap, collections::BTreeMap,
fmt::{self, Display, Formatter}, fmt::{self, Display, Formatter},
sync::{Arc, RwLock, RwLockReadGuard},
}; };
use crate::{error::rw_lock_error::RwLockError, value::Value}; use crate::{Identifier, Value};
/// A collection dust variables comprised of key-value pairs. /// 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 /// The inner value is a BTreeMap in order to allow VariableMap instances to be sorted and compared
/// to one another. /// to one another.
#[derive(Clone, Debug)] #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Map { pub struct Map {
inner: Arc<RwLock<BTreeMap<String, Value>>>, inner: BTreeMap<Identifier, Value>,
} }
impl Map { impl Map {
/// Creates a new instace. /// Creates a new instace.
pub fn new() -> Self { pub fn new() -> Self {
Map { Map {
inner: Arc::new(RwLock::new(BTreeMap::new())), inner: BTreeMap::new(),
} }
} }
pub fn with_values(variables: BTreeMap<String, Value>) -> Self { pub fn with_values(variables: BTreeMap<Identifier, Value>) -> Self {
Map { Map { inner: variables }
inner: Arc::new(RwLock::new(variables)),
}
} }
pub fn inner(&self) -> Result<RwLockReadGuard<BTreeMap<String, Value>>, RwLockError> { pub fn inner(&self) -> &BTreeMap<Identifier, Value> {
Ok(self.inner.read()?) &self.inner
} }
pub fn get(&self, key: &str) -> Result<Option<Value>, RwLockError> { pub fn get(&self, key: &Identifier) -> Option<&Value> {
Ok(self.inner()?.get(key).cloned()) self.inner.get(key)
} }
pub fn set(&self, key: String, value: Value) -> Result<(), RwLockError> { pub fn set(&mut self, key: Identifier, value: Value) {
self.inner.write()?.insert(key, value); self.inner.insert(key, value);
Ok(())
} }
pub fn as_text_table(&self) -> Table { pub fn as_text_table(&self) -> Table {
let mut table = Table::with_styles(Styles::default().with(HAlign::Centred)); let mut table = Table::with_styles(Styles::default().with(HAlign::Centred));
for (key, value) in self.inner().unwrap().iter() { for (key, value) in &self.inner {
if let Value::Map(map) = value { if let Value::Map(map) = value {
table.push_row(Row::new( table.push_row(Row::new(
Styles::default(), Styles::default(),
@ -67,7 +63,7 @@ impl Map {
vec![key.into(), list.as_text_table().into()], vec![key.into(), list.as_text_table().into()],
)); ));
} else { } else {
table.push_row([key, &value.to_string()]); table.push_row([key.to_string(), value.to_string()]);
}; };
} }
@ -92,34 +88,3 @@ impl Display for Map {
f.write_str(&renderer.render(&self.as_text_table())) f.write_str(&renderer.render(&self.as_text_table()))
} }
} }
impl Eq for Map {}
impl PartialEq for Map {
fn eq(&self, other: &Self) -> bool {
let left = self.inner().unwrap();
let right = other.inner().unwrap();
if left.len() != right.len() {
return false;
}
left.iter()
.zip(right.iter())
.all(|((left_key, left_value), (right_key, right_value))| {
left_key == right_key && left_value == right_value
})
}
}
impl PartialOrd for Map {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Map {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.inner().unwrap().cmp(&other.inner().unwrap())
}
}

View File

@ -1,5 +1,9 @@
//! Types that represent runtime values. //! Types that represent runtime values.
use crate::{error::RuntimeError, Identifier, Type, TypeSpecification}; use crate::{
built_in_values::BuiltInValue,
error::{rw_lock_error::RwLockError, RuntimeError, ValidationError},
Identifier, SourcePosition, Type,
};
use serde::{ use serde::{
de::{MapAccess, SeqAccess, Visitor}, de::{MapAccess, SeqAccess, Visitor},
@ -13,15 +17,19 @@ use std::{
convert::TryFrom, convert::TryFrom,
fmt::{self, Display, Formatter}, fmt::{self, Display, Formatter},
marker::PhantomData, marker::PhantomData,
ops::{Add, AddAssign, Div, Mul, RangeInclusive, Rem, Sub, SubAssign}, ops::RangeInclusive,
}; };
pub use self::{function::Function, list::List, map::Map, structure::Structure}; pub use self::{
enum_instance::EnumInstance, function::Function, list::List, map::Map,
struct_instance::StructInstance,
};
pub mod enum_instance;
pub mod function; pub mod function;
pub mod list; pub mod list;
pub mod map; pub mod map;
pub mod structure; pub mod struct_instance;
/// Dust value representation. /// Dust value representation.
/// ///
@ -30,25 +38,31 @@ pub mod structure;
/// value that can be treated as any other. /// value that can be treated as any other.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Value { pub enum Value {
Boolean(bool),
Enum(EnumInstance),
Float(f64),
Function(Function),
Integer(i64),
List(List), List(List),
Map(Map), Map(Map),
Function(Function),
String(String),
Float(f64),
Integer(i64),
Boolean(bool),
Range(RangeInclusive<i64>), Range(RangeInclusive<i64>),
Option(Option<Box<Value>>), String(String),
Structure(Structure), Struct(StructInstance),
}
impl Default for Value {
fn default() -> Self {
Value::none()
}
} }
impl Value { impl Value {
pub fn none() -> Self {
BuiltInValue::None.get().clone()
}
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 { pub fn string<T: Into<String>>(string: T) -> Self {
Value::String(string.into()) Value::String(string.into())
} }
@ -57,68 +71,54 @@ impl Value {
Value::Range(start..=end) Value::Range(start..=end)
} }
pub fn r#type(&self) -> Type { pub fn r#type(&self) -> Result<Type, RwLockError> {
match self { let r#type = match self {
Value::List(list) => { Value::List(list) => {
let mut previous_type = None; let mut item_types = Vec::new();
for value in list.items().iter() { for value in list.items()?.iter() {
let value_type = value.r#type(); let r#type = value.r#type()?;
if let Some(previous) = &previous_type { item_types.push(r#type);
if &value_type != previous {
return Type::List(Box::new(Type::Any));
}
} }
previous_type = Some(value_type); Type::ListExact(item_types)
}
if let Some(previous) = previous_type {
Type::List(Box::new(previous))
} else {
Type::List(Box::new(Type::Any))
}
} }
Value::Map(map) => { 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.inner().unwrap().iter() { for (identifier, value) in map.inner() {
identifier_types.push(( type_map.insert(identifier.clone(), value.r#type()?);
Identifier::new(key.clone()),
TypeSpecification::new(value.r#type()),
));
} }
Type::Map(None) Type::Map(Some(type_map))
}
} }
Value::Function(function) => function.r#type().clone(), Value::Function(function) => function.r#type().clone(),
Value::String(_) => Type::String, Value::String(_) => Type::String,
Value::Float(_) => Type::Float, Value::Float(_) => Type::Float,
Value::Integer(_) => Type::Integer, Value::Integer(_) => Type::Integer,
Value::Boolean(_) => Type::Boolean, Value::Boolean(_) => Type::Boolean,
Value::Option(option) => {
if let Some(value) = option {
Type::Option(Box::new(value.r#type()))
} else {
Type::None
}
}
Value::Range(_) => todo!(), Value::Range(_) => todo!(),
Value::Structure(_) => todo!(), Value::Struct(_) => todo!(),
} Value::Enum(enum_instance) => {
} let arguments = if let Some(value) = enum_instance.value() {
vec![value.r#type()?]
} else {
Vec::with_capacity(0)
};
pub fn none() -> Self { Type::Custom {
Value::Option(None) name: enum_instance.name().clone(),
arguments,
} }
pub fn some(value: Value) -> Self {
Value::Option(Some(Box::new(value)))
} }
};
pub fn option(option: Option<Value>) -> Self { Ok(r#type)
Value::Option(option.map(Box::new))
} }
pub fn is_string(&self) -> bool { pub fn is_string(&self) -> bool {
@ -145,14 +145,6 @@ impl Value {
matches!(self, Value::List(_)) 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 { pub fn is_map(&self) -> bool {
matches!(self, Value::Map(_)) matches!(self, Value::Map(_))
} }
@ -161,83 +153,97 @@ impl Value {
matches!(self, Value::Function(_)) matches!(self, Value::Function(_))
} }
/// Borrows the value stored in `self` as `&String`, or returns `Err` if `self` is not a `Value::String`. pub fn is_none(&self) -> bool {
pub fn as_string(&self) -> Result<&String, RuntimeError> { 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 { match self {
Value::String(string) => Ok(string), Value::String(string) => Ok(string),
value => Err(RuntimeError::ExpectedString { value => Err(ValidationError::ExpectedString {
actual: value.clone(), actual: value.clone(),
}), }),
} }
} }
/// Copies the value stored in `self` as `i64`, or returns `Err` if `self` is not a `Value::Int` /// Copies the value stored in `self` as `i64`, or returns `Err` if `self`
pub fn as_integer(&self) -> Result<i64, RuntimeError> { /// is not a `Value::Int`
pub fn as_integer(&self) -> Result<i64, ValidationError> {
match self { match self {
Value::Integer(i) => Ok(*i), Value::Integer(i) => Ok(*i),
value => Err(RuntimeError::ExpectedInteger { value => Err(ValidationError::ExpectedInteger {
actual: value.clone(), actual: value.clone(),
}), }),
} }
} }
/// Copies the value stored in `self` as `f64`, or returns `Err` if `self` is not a `Primitive::Float`. /// Copies the value stored in `self` as `f64`, or returns `Err` if `self`
pub fn as_float(&self) -> Result<f64, RuntimeError> { /// is not a `Primitive::Float`.
pub fn as_float(&self) -> Result<f64, ValidationError> {
match self { match self {
Value::Float(f) => Ok(*f), Value::Float(f) => Ok(*f),
value => Err(RuntimeError::ExpectedFloat { value => Err(ValidationError::ExpectedFloat {
actual: value.clone(), actual: value.clone(),
}), }),
} }
} }
/// Copies the value stored in `self` as `f64`, or returns `Err` if `self` is not a `Primitive::Float` or `Value::Int`. /// Copies the value stored in `self` as `f64`, or returns `Err` if `self`
/// Note that this method silently converts `i64` to `f64`, if `self` is a `Value::Int`. /// is not a `Primitive::Float` or `Value::Int`.
pub fn as_number(&self) -> Result<f64, RuntimeError> { ///
/// 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 { match self {
Value::Float(f) => Ok(*f), Value::Float(f) => Ok(*f),
Value::Integer(i) => Ok(*i as f64), Value::Integer(i) => Ok(*i as f64),
value => Err(RuntimeError::ExpectedNumber { value => Err(ValidationError::ExpectedNumber {
actual: value.clone(), actual: value.clone(),
}), }),
} }
} }
/// Copies the value stored in `self` as `bool`, or returns `Err` if `self` is not a `Primitive::Boolean`. /// Copies the value stored in `self` as `bool`, or returns `Err` if `self`
pub fn as_boolean(&self) -> Result<bool, RuntimeError> { /// is not a `Primitive::Boolean`.
pub fn as_boolean(&self) -> Result<bool, ValidationError> {
match self { match self {
Value::Boolean(boolean) => Ok(*boolean), Value::Boolean(boolean) => Ok(*boolean),
value => Err(RuntimeError::ExpectedBoolean { value => Err(ValidationError::ExpectedBoolean {
actual: value.clone(), actual: value.clone(),
}), }),
} }
} }
/// Borrows the value stored in `self` as `Vec<Value>`, or returns `Err` if `self` is not a `Value::List`. /// Borrows the value stored in `self` as `Vec<Value>`, or returns `Err` if
pub fn as_list(&self) -> Result<&List, RuntimeError> { /// `self` is not a `Value::List`.
pub fn as_list(&self) -> Result<&List, ValidationError> {
match self { match self {
Value::List(list) => Ok(list), Value::List(list) => Ok(list),
value => Err(RuntimeError::ExpectedList { value => Err(ValidationError::ExpectedList {
actual: value.clone(), actual: value.clone(),
}), }),
} }
} }
/// Takes ownership of the value stored in `self` as `Vec<Value>`, or returns `Err` if `self` is not a `Value::List`. /// Takes ownership of the value stored in `self` as `Vec<Value>`, or
pub fn into_inner_list(self) -> Result<List, RuntimeError> { /// returns `Err` if `self` is not a `Value::List`.
pub fn into_inner_list(self) -> Result<List, ValidationError> {
match self { match self {
Value::List(list) => Ok(list), Value::List(list) => Ok(list),
value => Err(RuntimeError::ExpectedList { value => Err(ValidationError::ExpectedList {
actual: value.clone(), actual: value.clone(),
}), }),
} }
} }
/// Borrows the value stored in `self` as `Vec<Value>`, or returns `Err` if `self` is not a `Value::Map`. /// Borrows the value stored in `self` as `Vec<Value>`, or returns `Err` if
pub fn as_map(&self) -> Result<&Map, RuntimeError> { /// `self` is not a `Value::Map`.
pub fn as_map(&self) -> Result<&Map, ValidationError> {
match self { match self {
Value::Map(map) => Ok(map), Value::Map(map) => Ok(map),
value => Err(RuntimeError::ExpectedMap { value => Err(ValidationError::ExpectedMap {
actual: value.clone(), actual: value.clone(),
}), }),
} }
@ -245,185 +251,104 @@ impl Value {
/// Borrows the value stored in `self` as `Function`, or returns `Err` if /// Borrows the value stored in `self` as `Function`, or returns `Err` if
/// `self` is not a `Value::Function`. /// `self` is not a `Value::Function`.
pub fn as_function(&self) -> Result<&Function, RuntimeError> { pub fn as_function(&self) -> Result<&Function, ValidationError> {
match self { match self {
Value::Function(function) => Ok(function), Value::Function(function) => Ok(function),
value => Err(RuntimeError::ExpectedFunction { value => Err(ValidationError::ExpectedFunction {
actual: value.clone(), actual: value.clone(),
}), }),
} }
} }
/// Returns `Option`, or returns `Err` if `self` is not a `Value::Option`. /// Return the sum of `self` and `other`.
pub fn as_option(&self) -> Result<&Option<Box<Value>>, RuntimeError> { pub fn add(self, other: Self, position: SourcePosition) -> Result<Value, ValidationError> {
match self { match (self, other) {
Value::Option(option) => Ok(option), (Value::Float(left), Value::Float(right)) => Ok(Value::Float(left + right)),
value => Err(RuntimeError::ExpectedOption { (Value::Float(left), Value::Integer(right)) => Ok(Value::Float(left + right as f64)),
actual: value.clone(), (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::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,
}), }),
} }
} }
/// Returns `()`, or returns `Err` if `self` is not a `Value::none()`. /// Return the difference of `self` and `other`.
pub fn as_none(&self) -> Result<(), RuntimeError> { pub fn subtract(self, other: Self, position: SourcePosition) -> Result<Value, ValidationError> {
match self { match (self, other) {
Value::Option(option) => { (Value::Float(left), Value::Float(right)) => Ok(Value::Float(left - right)),
if option.is_none() { (Value::Float(left), Value::Integer(right)) => Ok(Value::Float(left - right as f64)),
Ok(()) (Value::Integer(left), Value::Float(right)) => Ok(Value::Float(left as f64 - right)),
} else { (Value::Integer(left), Value::Integer(right)) => {
Err(RuntimeError::ExpectedNone { Ok(Value::Integer(left.saturating_sub(right)))
actual: self.clone(), }
}) (left, right) => Err(ValidationError::CannotSubtract {
left,
right,
position,
}),
} }
} }
value => Err(RuntimeError::ExpectedNone {
actual: value.clone(), /// 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 { fn default() -> Self {
&Value::Option(None) Value::none()
}
}
impl Add for Value {
type Output = Result<Value, RuntimeError>;
fn add(self, other: Self) -> Self::Output {
if let (Ok(left), Ok(right)) = (self.as_integer(), other.as_integer()) {
let (sum, _) = left.overflowing_add(right);
return Ok(Value::Integer(sum));
}
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(RuntimeError::ExpectedNumberOrString {
actual: non_number_or_string,
})
}
}
impl Sub for Value {
type Output = Result<Self, RuntimeError>;
fn sub(self, other: Self) -> Self::Output {
if let (Ok(left), Ok(right)) = (self.as_integer(), other.as_integer()) {
let (difference, _) = left.overflowing_sub(right);
return Ok(Value::Integer(difference));
}
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(RuntimeError::ExpectedNumber { actual: non_number })
}
}
impl Mul for Value {
type Output = Result<Self, RuntimeError>;
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(RuntimeError::ExpectedNumber { actual: non_number })
}
}
}
impl Div for Value {
type Output = Result<Self, RuntimeError>;
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(RuntimeError::ExpectedNumber { actual: non_number })
}
}
}
impl Rem for Value {
type Output = Result<Self, RuntimeError>;
fn rem(self, other: Self) -> Self::Output {
let left = self.as_integer()?;
let right = other.as_integer()?;
let result = left % right;
Ok(Value::Integer(result))
}
}
impl AddAssign for Value {
fn add_assign(&mut self, other: Self) {
match (self, other) {
(Value::Integer(left), Value::Integer(right)) => *left += right,
(Value::Float(left), Value::Float(right)) => *left += right,
(Value::Float(left), Value::Integer(right)) => *left += right as f64,
(Value::String(left), Value::String(right)) => *left += &right,
(Value::List(list), value) => list.items_mut().push(value),
_ => {}
}
}
}
impl SubAssign for Value {
fn sub_assign(&mut self, other: Self) {
match (self, other) {
(Value::Integer(left), Value::Integer(right)) => *left -= right,
(Value::Float(left), Value::Float(right)) => *left -= right,
(Value::Float(left), Value::Integer(right)) => *left -= right as f64,
(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);
}
}
_ => {}
}
} }
} }
@ -439,9 +364,9 @@ impl PartialEq for Value {
(Value::List(left), Value::List(right)) => left == right, (Value::List(left), Value::List(right)) => left == right,
(Value::Map(left), Value::Map(right)) => left == right, (Value::Map(left), Value::Map(right)) => left == right,
(Value::Function(left), Value::Function(right)) => left == right, (Value::Function(left), Value::Function(right)) => left == right,
(Value::Option(left), Value::Option(right)) => left == right,
(Value::Structure(left), Value::Structure(right)) => left == right,
(Value::Range(left), Value::Range(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, _ => false,
} }
} }
@ -478,17 +403,17 @@ impl Ord for Value {
(Value::Map(_), _) => Ordering::Greater, (Value::Map(_), _) => Ordering::Greater,
(Value::Function(left), Value::Function(right)) => left.cmp(right), (Value::Function(left), Value::Function(right)) => left.cmp(right),
(Value::Function(_), _) => Ordering::Greater, (Value::Function(_), _) => Ordering::Greater,
(Value::Structure(left), Value::Structure(right)) => left.cmp(right), (Value::Struct(left), Value::Struct(right)) => left.cmp(right),
(Value::Structure(_), _) => Ordering::Greater, (Value::Struct(_), _) => Ordering::Greater,
(Value::Enum(left), Value::Enum(right)) => left.cmp(right),
(Value::Enum(_), _) => Ordering::Greater,
(Value::Range(left), Value::Range(right)) => { (Value::Range(left), Value::Range(right)) => {
let left_len = left.end() - left.start(); let left_len = left.end() - left.start();
let right_len = right.end() - right.start(); let right_len = right.end() - right.start();
left_len.cmp(&right_len) left_len.cmp(&right_len)
} }
(Value::Range(_), _) => Ordering::Greater, (Value::Range(_), _) => Ordering::Less,
(Value::Option(left), Value::Option(right)) => left.cmp(right),
(Value::Option(_), _) => Ordering::Less,
} }
} }
} }
@ -504,7 +429,12 @@ impl Serialize for Value {
Value::Integer(inner) => serializer.serialize_i64(*inner), Value::Integer(inner) => serializer.serialize_i64(*inner),
Value::Boolean(inner) => serializer.serialize_bool(*inner), Value::Boolean(inner) => serializer.serialize_bool(*inner),
Value::List(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())?; let mut list = serializer.serialize_tuple(items.len())?;
for value in items.iter() { for value in items.iter() {
@ -513,9 +443,8 @@ impl Serialize for Value {
list.end() list.end()
} }
Value::Option(inner) => inner.serialize(serializer),
Value::Map(map) => { Value::Map(map) => {
let entries = map.inner().unwrap(); let entries = map.inner();
let mut map = serializer.serialize_map(Some(entries.len()))?; let mut map = serializer.serialize_map(Some(entries.len()))?;
for (key, value) in entries.iter() { for (key, value) in entries.iter() {
@ -525,8 +454,9 @@ impl Serialize for Value {
map.end() map.end()
} }
Value::Function(inner) => inner.serialize(serializer), Value::Function(inner) => inner.serialize(serializer),
Value::Structure(inner) => inner.serialize(serializer), Value::Struct(inner) => inner.serialize(serializer),
Value::Range(range) => range.serialize(serializer), Value::Range(range) => range.serialize(serializer),
Value::Enum(_) => todo!(),
} }
} }
} }
@ -538,18 +468,12 @@ impl Display for Value {
Value::Float(float) => write!(f, "{float}"), Value::Float(float) => write!(f, "{float}"),
Value::Integer(int) => write!(f, "{int}"), Value::Integer(int) => write!(f, "{int}"),
Value::Boolean(boolean) => write!(f, "{boolean}"), 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::List(list) => write!(f, "{list}"),
Value::Map(map) => write!(f, "{map}"), Value::Map(map) => write!(f, "{map}"),
Value::Function(function) => write!(f, "{function}"), Value::Function(function) => write!(f, "{function}"),
Value::Structure(structure) => write!(f, "{structure}"), Value::Struct(structure) => write!(f, "{structure}"),
Value::Range(range) => write!(f, "{}..{}", range.start(), range.end()), Value::Range(range) => write!(f, "{}..{}", range.start(), range.end()),
Value::Enum(enum_instance) => write!(f, "{enum_instance}"),
} }
} }
} }
@ -609,7 +533,9 @@ impl TryFrom<Value> for String {
if let Value::String(string) = value { if let Value::String(string) = value {
Ok(string) Ok(string)
} else { } else {
Err(RuntimeError::ExpectedString { actual: value }) Err(RuntimeError::ValidationFailure(
ValidationError::ExpectedString { actual: value },
))
} }
} }
} }
@ -621,7 +547,9 @@ impl TryFrom<Value> for f64 {
if let Value::Float(value) = value { if let Value::Float(value) = value {
Ok(value) Ok(value)
} else { } else {
Err(RuntimeError::ExpectedFloat { actual: value }) Err(RuntimeError::ValidationFailure(
ValidationError::ExpectedFloat { actual: value },
))
} }
} }
} }
@ -633,7 +561,9 @@ impl TryFrom<Value> for i64 {
if let Value::Integer(value) = value { if let Value::Integer(value) = value {
Ok(value) Ok(value)
} else { } else {
Err(RuntimeError::ExpectedInteger { actual: value }) Err(RuntimeError::ValidationFailure(
ValidationError::ExpectedInteger { actual: value },
))
} }
} }
} }
@ -645,7 +575,9 @@ impl TryFrom<Value> for bool {
if let Value::Boolean(value) = value { if let Value::Boolean(value) = value {
Ok(value) Ok(value)
} else { } else {
Err(RuntimeError::ExpectedBoolean { actual: value }) Err(RuntimeError::ValidationFailure(
ValidationError::ExpectedBoolean { actual: value },
))
} }
} }
} }
@ -828,9 +760,7 @@ impl<'de> Visitor<'de> for ValueVisitor {
where where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
Ok(Value::Option(Some(Box::new(Value::deserialize( Ok(Value::Enum(EnumInstance::deserialize(deserializer)?))
deserializer,
)?))))
} }
fn visit_unit<E>(self) -> std::result::Result<Self::Value, E> fn visit_unit<E>(self) -> std::result::Result<Self::Value, E>
@ -868,13 +798,15 @@ impl<'de> Visitor<'de> for ValueVisitor {
where where
M: MapAccess<'de>, M: MapAccess<'de>,
{ {
let mut map = BTreeMap::new(); let mut map = Map::new();
while let Some((key, value)) = access.next_entry::<String, Value>()? { while let Some((key, value)) = access.next_entry::<String, Value>()? {
map.insert(key, value); let identifier = Identifier::new(&key);
map.set(identifier, value);
} }
Ok(Value::Map(Map::with_values(map))) Ok(Value::Map(map))
} }
fn visit_enum<A>(self, data: A) -> std::result::Result<Self::Value, A::Error> fn visit_enum<A>(self, data: A) -> std::result::Result<Self::Value, A::Error>

View 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()
}
}

View File

@ -1,113 +0,0 @@
use std::{
collections::BTreeMap,
fmt::{self, Display, Formatter},
marker::PhantomData,
sync::Arc,
};
use serde::{
de::{MapAccess, Visitor},
ser::SerializeMap,
Deserialize, Deserializer, Serialize, Serializer,
};
use crate::{error::rw_lock_error::RwLockError, Map, Type, Value};
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct Structure(Arc<BTreeMap<String, (Option<Value>, Type)>>);
impl Structure {
pub fn new(map: BTreeMap<String, (Option<Value>, Type)>) -> Self {
Structure(Arc::new(map))
}
pub fn from_map(map: &Map) -> Result<Self, RwLockError> {
let mut structure = BTreeMap::new();
for (key, value) in map.inner()?.iter() {
structure.insert(key.clone(), (Some(value.clone()), value.r#type()));
}
Ok(Structure(Arc::new(structure)))
}
pub fn inner(&self) -> &BTreeMap<String, (Option<Value>, Type)> {
&self.0
}
}
impl Display for Structure {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
writeln!(f, "{{")?;
for (key, (value_option, r#type)) in self.0.as_ref() {
if let Some(value) = value_option {
writeln!(f, " {key} <{}> = {value}", r#type)?;
} else {
writeln!(f, " {key} <{}>", r#type)?;
}
}
write!(f, "}}")
}
}
impl Serialize for Structure {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(self.0.len()))?;
for (key, (value, _type)) in self.0.iter() {
map.serialize_entry(key, value)?;
}
map.end()
}
}
struct StructureVisitor {
marker: PhantomData<fn() -> Structure>,
}
impl StructureVisitor {
fn new() -> Self {
StructureVisitor {
marker: PhantomData,
}
}
}
impl<'de> Visitor<'de> for StructureVisitor {
type Value = Structure;
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
formatter.write_str("key-value pairs")
}
fn visit_map<M>(self, mut access: M) -> std::result::Result<Structure, M::Error>
where
M: MapAccess<'de>,
{
let mut b_tree = BTreeMap::new();
{
while let Some((key, value)) = access.next_entry::<String, Value>()? {
let r#type = value.r#type();
b_tree.insert(key, (Some(value), r#type));
}
}
Ok(Structure::new(b_tree))
}
}
impl<'de> Deserialize<'de> for Structure {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(StructureVisitor::new())
}
}

View File

@ -24,7 +24,7 @@ fn string_as_list_error() {
interpret("'foobar' as [float]"), interpret("'foobar' as [float]"),
Err(Error::Validation(ValidationError::ConversionImpossible { Err(Error::Validation(ValidationError::ConversionImpossible {
initial_type: Type::String, initial_type: Type::String,
target_type: Type::List(Box::new(Type::Float)) target_type: Type::ListOf(Box::new(Type::Float))
})) }))
) )
} }
@ -38,8 +38,16 @@ fn conversion_runtime_error() {
assert_eq!( assert_eq!(
interpret(&format!("json:parse('{JSON}') as [map]")), interpret(&format!("json:parse('{JSON}') as [map]")),
Err(Error::Runtime(RuntimeError::ConversionImpossible { Err(Error::Runtime(RuntimeError::ConversionImpossible {
value: json_value, from: json_value.r#type().unwrap(),
target_type: Type::List(Box::new(Type::Map(None))) 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,
}
})) }))
) )
} }

View File

@ -30,17 +30,3 @@ fn async_with_return() {
Ok(Value::Integer(1)) Ok(Value::Integer(1))
); );
} }
#[test]
fn root_returns_like_block() {
assert_eq!(
interpret(
"
return 1
1 + 1
3
"
),
Ok(Value::Integer(1))
);
}

View File

@ -29,11 +29,11 @@ fn ends_with() {
fn find() { fn find() {
let result = interpret("str:find('abc', 'a')"); let result = interpret("str:find('abc', 'a')");
assert_eq!(result, Ok(Value::Option(Some(Box::new(Value::Integer(0)))))); assert_eq!(result, Ok(Value::some(Value::Integer(0))));
let result = interpret("str:find('foobar', 'b')"); let result = interpret("str:find('foobar', 'b')");
assert_eq!(result, Ok(Value::Option(Some(Box::new(Value::Integer(3)))))); assert_eq!(result, Ok(Value::some(Value::Integer(3))));
} }
#[test] #[test]

View 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())),
)))
);
}

View File

@ -1,6 +1,31 @@
use dust_lang::*; use dust_lang::{error::RuntimeError, *};
#[test] #[test]
fn args() { fn args() {
assert!(interpret("args").is_ok_and(|value| value.is_list())); 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()
}))
);
}

View File

@ -1,20 +1,14 @@
use dust_lang::{interpret, Value}; use dust_lang::{interpret, Value};
use std::fs::{remove_file, write};
#[test] #[test]
fn simple_command() { fn simple_command() {
assert_eq!(interpret("^echo hi"), Ok(Value::String("".to_string()))) assert_eq!(interpret("^echo hi"), Ok(Value::String("hi\n".to_string())))
} }
#[test] #[test]
fn assign_command_output() { fn assign_command_output() {
write("target/test.txt", "123").unwrap();
assert_eq!( assert_eq!(
interpret("x = ^cat target/test.txt; x"), interpret("x = ^ls; length(str:lines(x))"),
Ok(Value::String("123".to_string())) Ok(Value::Integer(11))
); );
remove_file("target/test.txt").unwrap();
} }

88
tests/enums.rs Normal file
View File

@ -0,0 +1,88 @@
use dust_lang::*;
#[test]
fn simple_enum() {
let result = interpret(
"
enum Foobar {
Foo,
Bar,
}
Foobar::Foo
",
);
assert_eq!(
result,
Ok(Value::Enum(EnumInstance::new(
Identifier::new("Foobar"),
Identifier::new("Foo"),
Some(Value::none())
)))
);
}
#[test]
fn nested_enum() {
let result = interpret(
"
enum Fizzbuzz {
Fizz,
Buzz,
}
enum Foobar {
Foo,
Bar<Fizzbuzz>,
}
Foobar::Bar(Fizzbuzz::Fizz)
",
);
assert_eq!(
result,
Ok(Value::Enum(EnumInstance::new(
Identifier::new("Foobar"),
Identifier::new("Bar"),
Some(Value::Enum(EnumInstance::new(
Identifier::new("Fizzbuzz"),
Identifier::new("Fizz"),
Some(Value::none())
)))
)))
);
}
#[test]
fn enum_with_argument() {
env_logger::builder().is_test(true).try_init().unwrap();
let result = interpret(
"
enum FooBar<T> {
Foo<T>
Bar
}
enum FizzBuzz {
Fizz
Buzz
}
FooBar::Bar(FizzBuzz::Fizz)
",
);
assert_eq!(
result,
Ok(Value::Enum(EnumInstance::new(
Identifier::new("FooBar"),
Identifier::new("Bar"),
Some(Value::Enum(EnumInstance::new(
Identifier::new("FizzBuzz"),
Identifier::new("Fizz"),
Some(Value::none())
)))
)))
);
}

View File

@ -32,7 +32,7 @@ fn range_for_loop() {
} }
#[test] #[test]
fn modify_list() { fn mutate_list() {
let result = interpret( let result = interpret(
" "
list = [] list = []
@ -42,39 +42,17 @@ fn modify_list() {
); );
assert_eq!( assert_eq!(
result,
Ok(Value::List(List::with_items(vec![ Ok(Value::List(List::with_items(vec![
Value::Integer(1), Value::Integer(1),
Value::Integer(2), Value::Integer(2),
Value::Integer(3), Value::Integer(3),
]))), ]))),
result
); );
} }
#[test] #[test]
fn modify_map() { fn do_not_mutate_list_items() {
let result = interpret(
"
map = {}
for i in [['x', 1] ['y', 2]] {
map:(i:0) = i:1
}
map
",
);
let map = Map::new();
map.set("x".to_string(), Value::Integer(1)).unwrap();
map.set("y".to_string(), Value::Integer(2)).unwrap();
assert_eq!(Ok(Value::Map(map)), result);
}
#[test]
fn do_not_modify_list_values() {
let result = interpret( let result = interpret(
" "
list = [1 2 3] list = [1 2 3]
@ -84,12 +62,12 @@ fn do_not_modify_list_values() {
); );
assert_eq!( assert_eq!(
result,
Ok(Value::List(List::with_items(vec![ Ok(Value::List(List::with_items(vec![
Value::Integer(1), Value::Integer(1),
Value::Integer(2), Value::Integer(2),
Value::Integer(3), Value::Integer(3),
]))), ]))),
result
); );
} }

View File

@ -2,7 +2,7 @@ use dust_lang::*;
#[test] #[test]
fn format_simple_program() { fn format_simple_program() {
let mut interpreter = Interpreter::new(Context::new()); let mut interpreter = Interpreter::new(Context::default());
assert_eq!(interpreter.format("x=1"), Ok("x = 1\n".to_string())); assert_eq!(interpreter.format("x=1"), Ok("x = 1\n".to_string()));
} }
@ -16,7 +16,7 @@ const FORMATTED_BLOCK: &str = "{
#[test] #[test]
fn format_block() { fn format_block() {
let mut interpreter = Interpreter::new(Context::new()); let mut interpreter = Interpreter::new(Context::default());
assert_eq!( assert_eq!(
interpreter.format("{1 2 3}"), interpreter.format("{1 2 3}"),
@ -34,7 +34,7 @@ const FORMATTED_MAP: &str = "{
#[test] #[test]
fn format_map() { fn format_map() {
let mut interpreter = Interpreter::new(Context::new()); let mut interpreter = Interpreter::new(Context::default());
assert_eq!( assert_eq!(
interpreter.format("{{x=1 y <int> = 2}}"), interpreter.format("{{x=1 y <int> = 2}}"),
@ -49,7 +49,7 @@ const FORMATTED_FUNCTION: &str = "(x <int>) <num> {
#[test] #[test]
fn format_function() { fn format_function() {
let mut interpreter = Interpreter::new(Context::new()); let mut interpreter = Interpreter::new(Context::default());
assert_eq!( assert_eq!(
interpreter.format("( x< int > )<num>{x/2}"), interpreter.format("( x< int > )<num>{x/2}"),
Ok(FORMATTED_FUNCTION.to_string()) Ok(FORMATTED_FUNCTION.to_string())

View File

@ -43,7 +43,7 @@ fn callback() {
#[test] #[test]
fn built_in_function_call() { fn built_in_function_call() {
assert_eq!(interpret("output('Hiya')"), Ok(Value::Option(None))); assert_eq!(interpret("output('Hiya')"), Ok(Value::none()));
} }
#[test] #[test]
@ -57,7 +57,7 @@ fn function_context_does_not_capture_normal_values() {
" "
), ),
Err(Error::Validation( Err(Error::Validation(
ValidationError::VariableIdentifierNotFound(Identifier::new("x".to_string())) ValidationError::VariableIdentifierNotFound(Identifier::new("x"))
)) ))
); );
@ -89,19 +89,19 @@ fn function_context_captures_functions() {
#[test] #[test]
fn function_context_captures_structure_definitions() { fn function_context_captures_structure_definitions() {
let map = Map::new(); let mut map = Map::new();
map.set("name".to_string(), Value::string("bob")).unwrap(); map.set(Identifier::new("name"), Value::string("bob"));
assert_eq!( assert_eq!(
interpret( interpret(
" "
User = struct { struct User {
name <str> name <str>
} }
bob = () <User> { bob = () <User> {
new User { User::{
name = 'bob' name = 'bob'
} }
} }
@ -109,31 +109,14 @@ fn function_context_captures_structure_definitions() {
bob() bob()
" "
), ),
Ok(Value::Map(map.clone())) Ok(Value::Struct(StructInstance::new("User".into(), map)))
);
assert_eq!(
interpret(
"
bob = () <User> {
new User {
name = 'bob'
}
}
User = struct {
name <str>
}
bob()
"
),
Ok(Value::Map(map))
); );
} }
#[test] #[test]
fn recursion() { fn recursion() {
env_logger::builder().is_test(true).try_init().unwrap();
assert_eq!( assert_eq!(
interpret( interpret(
" "

View File

@ -47,7 +47,7 @@ fn if_else_if_else_if_else_if_else() {
'no' 'no'
} else if 1 + 1 == 1 { } else if 1 + 1 == 1 {
'nope' 'nope'
} else if 9 / 2 == 4 { } else if 9 / 2 == 42 {
'nope' 'nope'
} else if 'foo' == 'bar' { } else if 'foo' == 'bar' {
'nope' 'nope'

View File

@ -2,16 +2,16 @@ use dust_lang::*;
#[test] #[test]
fn list_index() { fn list_index() {
let test = interpret("x = [1 [2] 3] x:1:0").unwrap(); let test = interpret("x = [1 [2] 3] x:1:0");
assert_eq!(Value::Integer(2), test); assert_eq!(Ok(Value::Integer(2)), test);
} }
#[test] #[test]
fn map_index() { fn map_index() {
let test = interpret("x = {y = {z = 2}} x:y:z").unwrap(); let test = interpret("x = {y = {z = 2}} x:y:z");
assert_eq!(Value::Integer(2), test); assert_eq!(Ok(Value::Integer(2)), test);
} }
#[test] #[test]

View File

@ -1,13 +1,13 @@
use dust_lang::*; use dust_lang::*;
#[test] #[test]
fn r#match() { fn match_value() {
let test = interpret( let test = interpret(
" "
match 1 { match 1 {
3 => false 3 -> false
2 => { false } 2 -> { false }
1 => true 1 -> true
} }
", ",
) )
@ -21,9 +21,9 @@ fn match_assignment() {
let test = interpret( let test = interpret(
" "
x = match 1 { x = match 1 {
3 => false 3 -> false
2 => { false } 2 -> { false }
1 => true 1 -> true
} }
x x
", ",
@ -32,3 +32,19 @@ fn match_assignment() {
assert_eq!(Value::Boolean(true), test); assert_eq!(Value::Boolean(true), test);
} }
#[test]
fn match_enum() {
let result = interpret(
"
foobar = Option::Some(true)
match foobar {
Option::None -> false,
Option::Some(content) -> content,
}
",
);
assert_eq!(result, Ok(Value::Boolean(true)));
}

29
tests/root.rs Normal file
View File

@ -0,0 +1,29 @@
use dust_lang::*;
#[test]
fn returns_final_statement() {
assert_eq!(
interpret(
"
1
1 + 1
3
"
),
Ok(Value::Integer(3))
);
}
#[test]
fn return_statement() {
assert_eq!(
interpret(
"
return 1
1 + 1
3
"
),
Ok(Value::Integer(1))
);
}

58
tests/structs.rs Normal file
View File

@ -0,0 +1,58 @@
use dust_lang::*;
#[test]
fn simple_struct() {
let result = interpret(
"
struct Foo {
bar <int> = 0
baz <str>
}
Foo::{
baz = 'hiya'
}
",
);
let mut map = Map::new();
map.set(Identifier::new("bar"), Value::Integer(0));
map.set(Identifier::new("baz"), Value::String("hiya".to_string()));
let expected = Ok(Value::Struct(StructInstance::new(
Identifier::new("Foo"),
map,
)));
assert_eq!(result, expected);
}
#[test]
fn nested_struct() {
let result = interpret(
"
struct Foo {
bar <Bar>
}
struct Bar {}
Foo::{
bar = Bar::{}
}
",
);
let mut foo_map = Map::new();
foo_map.set(
Identifier::new("bar"),
Value::Struct(StructInstance::new(Identifier::new("Bar"), Map::new())),
);
let expected = Ok(Value::Struct(StructInstance::new(
Identifier::new("Foo"),
foo_map,
)));
assert_eq!(result, expected)
}

View File

@ -1,43 +0,0 @@
use std::collections::BTreeMap;
use dust_lang::*;
#[test]
fn simple_structure() {
let result = interpret("struct { x <int> = 0 }");
let mut btree_map = BTreeMap::new();
btree_map.insert("x".to_string(), (Some(Value::Integer(0)), Type::Integer));
let expected = Ok(Value::Structure(Structure::new(btree_map)));
assert_eq!(expected, result);
}
#[test]
fn new_structure() {
let _result = interpret(
"
Coords = struct {
x <float> = 0.0
x <float> = 0.0
}
new Coords {
x = 1.5
y = 4.2
}
",
);
let mut map = BTreeMap::new();
map.insert("x".to_string(), (Some(Value::Integer(0)), Type::Integer));
// let expected = Value::Map(Map::from_structure(Structure::new(map)));
// assert_eq!(Ok(expected), result);
todo!()
}

View File

@ -1,9 +1,9 @@
use dust_lang::*; use dust_lang::*;
#[test] #[test]
fn empty() { fn none() {
assert_eq!(interpret("x = 9"), Ok(Value::Option(None))); assert_eq!(interpret("x = 9"), Ok(Value::none()));
assert_eq!(interpret("x = 1 + 1"), Ok(Value::Option(None))); assert_eq!(interpret("x = 1 + 1"), Ok(Value::none()));
} }
#[test] #[test]
@ -14,14 +14,14 @@ fn integer() {
} }
#[test] #[test]
fn integer_overflow() { fn integer_saturation() {
assert_eq!( assert_eq!(
interpret("9223372036854775807 + 1"), interpret("9223372036854775807 + 1"),
Ok(Value::Integer(i64::MIN)) Ok(Value::Integer(i64::MAX))
); );
assert_eq!( assert_eq!(
interpret("-9223372036854775808 - 1"), interpret("-9223372036854775808 - 1"),
Ok(Value::Integer(i64::MAX)) Ok(Value::Integer(i64::MIN))
); );
} }
@ -37,6 +37,18 @@ fn float() {
); );
} }
#[test]
fn float_saturation() {
assert_eq!(
interpret("1.7976931348623157e308 + 1"),
Ok(Value::Float(f64::MAX))
);
assert_eq!(
interpret("-1.7976931348623157e308 - 1"),
Ok(Value::Float(f64::MIN))
);
}
#[test] #[test]
fn string() { fn string() {
assert_eq!(interpret("\"one\""), Ok(Value::string("one".to_string()))); assert_eq!(interpret("\"one\""), Ok(Value::string("one".to_string())));
@ -69,11 +81,10 @@ fn empty_list() {
#[test] #[test]
fn map() { fn map() {
let map = Map::new(); let mut map = Map::new();
map.set("x".to_string(), Value::Integer(1)).unwrap(); map.set(Identifier::new("x"), Value::Integer(1));
map.set("foo".to_string(), Value::string("bar".to_string())) map.set(Identifier::new("foo"), Value::string("bar".to_string()));
.unwrap();
assert_eq!(interpret("{ x = 1, foo = 'bar' }"), Ok(Value::Map(map))); assert_eq!(interpret("{ x = 1, foo = 'bar' }"), Ok(Value::Map(map)));
} }
@ -85,11 +96,10 @@ fn empty_map() {
#[test] #[test]
fn map_types() { fn map_types() {
let map = Map::new(); let mut map = Map::new();
map.set("x".to_string(), Value::Integer(1)).unwrap(); map.set(Identifier::new("x"), Value::Integer(1));
map.set("foo".to_string(), Value::string("bar".to_string())) map.set(Identifier::new("foo"), Value::string("bar".to_string()));
.unwrap();
assert_eq!( assert_eq!(
interpret("{ x <int> = 1, foo <str> = 'bar' }"), interpret("{ x <int> = 1, foo <str> = 'bar' }"),
@ -139,20 +149,10 @@ fn function() {
panic!("Something is wrong with this test..."); panic!("Something is wrong with this test...");
}; };
assert_eq!( assert_eq!(&vec![Identifier::new("x")], function.parameters());
&vec![Identifier::new("x".to_string())],
function.parameters()
);
assert_eq!(&Type::Boolean, function.return_type()); assert_eq!(&Type::Boolean, function.return_type());
} }
#[test]
fn option() {
let result = interpret("x <option(int)> = some(1); x").unwrap();
assert_eq!(Value::Option(Some(Box::new(Value::Integer(1)))), result);
}
#[test] #[test]
fn range() { fn range() {
assert_eq!(interpret("0..100"), Ok(Value::range(0, 100))); assert_eq!(interpret("0..100"), Ok(Value::range(0, 100)));

View File

@ -2,7 +2,7 @@ use dust_lang::*;
#[test] #[test]
fn while_loop() { fn while_loop() {
assert_eq!(interpret("while false { 'foo' }"), Ok(Value::Option(None))) assert_eq!(interpret("while false { 'foo' }"), Ok(Value::none()))
} }
#[test] #[test]

View File

@ -8,12 +8,13 @@ Simple As
(root (root
(statement (statement
(statement_kind
(expression (expression
(as (as_node
(expression (expression
(value (value
(integer))) (integer)))
(type))))) (type))))))
================================================================================ ================================================================================
As Function As Function
@ -25,13 +26,14 @@ foo as (int) -> int
(root (root
(statement (statement
(statement_kind
(expression (expression
(as (as_node
(expression (expression
(identifier)) (identifier))
(type (type
(type) (type)
(type)))))) (type)))))))
================================================================================ ================================================================================
As List in For Loop As List in For Loop
@ -43,13 +45,14 @@ for i in foobar as [string] {}
(root (root
(statement (statement
(statement_kind
(for (for
(identifier) (identifier)
(expression (expression
(as (as_node
(expression (expression
(identifier)) (identifier))
(type (type
(type (type
(identifier))))) (identifier)))))
(block)))) (block)))))

View File

@ -8,12 +8,14 @@ x = y
(root (root
(statement (statement
(statement_kind
(assignment (assignment
(identifier) (identifier)
(assignment_operator) (assignment_operator)
(statement (statement
(statement_kind
(expression (expression
(identifier)))))) (identifier))))))))
================================================================================ ================================================================================
Simple Assignment with Type Simple Assignment with Type
@ -25,14 +27,16 @@ x <int> = y
(root (root
(statement (statement
(statement_kind
(assignment (assignment
(identifier) (identifier)
(type_specification (type_specification
(type)) (type))
(assignment_operator) (assignment_operator)
(statement (statement
(statement_kind
(expression (expression
(identifier)))))) (identifier))))))))
================================================================================ ================================================================================
Map Item Assignment Map Item Assignment
@ -44,6 +48,7 @@ x:y = 1
(root (root
(statement (statement
(statement_kind
(index_assignment (index_assignment
(index (index
(index_expression (index_expression
@ -52,9 +57,10 @@ x:y = 1
(identifier))) (identifier)))
(assignment_operator) (assignment_operator)
(statement (statement
(statement_kind
(expression (expression
(value (value
(integer))))))) (integer)))))))))
================================================================================ ================================================================================
List Item Assignment List Item Assignment
@ -66,6 +72,7 @@ x:9 = 'foobar'
(root (root
(statement (statement
(statement_kind
(index_assignment (index_assignment
(index (index
(index_expression (index_expression
@ -75,6 +82,7 @@ x:9 = 'foobar'
(integer)))) (integer))))
(assignment_operator) (assignment_operator)
(statement (statement
(statement_kind
(expression (expression
(value (value
(string))))))) (string)))))))))

View File

@ -8,16 +8,17 @@ async { output ('Whaddup') }
(root (root
(statement (statement
(statement_kind
(block (block
(statement (statement
(statement_kind
(expression (expression
(function_call (function_call
(function_expression (function_expression
(value (identifier))
(built_in_value)))
(expression (expression
(value (value
(string))))))))) (string)))))))))))
================================================================================ ================================================================================
Complex Async Statements Complex Async Statements
@ -37,8 +38,10 @@ async {
(root (root
(statement (statement
(statement_kind
(block (block
(statement (statement
(statement_kind
(if_else (if_else
(if (if
(expression (expression
@ -58,16 +61,19 @@ async {
(integer))))) (integer)))))
(block (block
(statement (statement
(statement_kind
(expression (expression
(value (value
(boolean)))))) (boolean)))))))
(else (else
(block (block
(statement (statement
(statement_kind
(expression (expression
(value (value
(boolean)))))))) (boolean))))))))))
(statement (statement
(statement_kind
(expression (expression
(value (value
(string))))))) (string)))))))))

View File

@ -10,16 +10,17 @@ Simple Block
(root (root
(statement (statement
(statement_kind
(block (block
(statement (statement
(statement_kind
(expression (expression
(function_call (function_call
(function_expression (function_expression
(value (identifier))
(built_in_value)))
(expression (expression
(value (value
(integer))))))))) (integer)))))))))))
================================================================================ ================================================================================
Block with Return Block with Return
@ -35,18 +36,20 @@ Block with Return
(root (root
(statement (statement
(statement_kind
(block (block
(statement (statement
(statement_kind
(expression (expression
(value (value
(integer)))) (integer)))))
(statement
(return
(statement (statement
(statement_kind
(expression (expression
(value (value
(integer)))))) (integer)))))
(statement (statement
(statement_kind
(expression (expression
(value (value
(integer))))))) (integer)))))))))

View File

@ -8,9 +8,10 @@ Simple Command
(root (root
(statement (statement
(statement_kind
(expression (expression
(command (command
(command_text))))) (command_text))))))
================================================================================ ================================================================================
Command Sequence Command Sequence
@ -22,13 +23,15 @@ Command Sequence
(root (root
(statement (statement
(expression (statement_kind
(command
(command_text))))
(statement
(expression (expression
(command (command
(command_text))))) (command_text)))))
(statement
(statement_kind
(expression
(command
(command_text))))))
================================================================================ ================================================================================
Command with Arguments Command with Arguments
@ -40,11 +43,12 @@ Command with Arguments
(root (root
(statement (statement
(statement_kind
(expression (expression
(command (command
(command_text) (command_text)
(command_argument) (command_argument)
(command_argument))))) (command_argument))))))
================================================================================ ================================================================================
Command Sequence with Arguments Command Sequence with Arguments
@ -57,74 +61,53 @@ Command Sequence with Arguments
(root (root
(statement (statement
(statement_kind
(expression (expression
(command (command
(command_text) (command_text)
(command_argument) (command_argument)
(command_argument) (command_argument)
(command_argument) (command_argument)
(command_argument))))
(statement
(expression
(command
(command_text)
(command_argument)
(command_argument))))) (command_argument)))))
================================================================================
Command Sequence with Arguments
================================================================================
^cargo run -- -c "output('hi there')"
^ls --long -a
--------------------------------------------------------------------------------
(root
(statement (statement
(statement_kind
(expression (expression
(command (command
(command_text) (command_text)
(command_argument) (command_argument)
(command_argument) (command_argument))))))
(command_argument)
(command_argument))))
(statement
(expression
(command
(command_text)
(command_argument)
(command_argument)))))
================================================================================ ================================================================================
Command Assignment Command Assignment
================================================================================ ================================================================================
ls_output = ^ls --long -a; ls_output = ^ls;
cat_output = ^cat Cargo.toml; cat_output = ^cat Cargo.toml;
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
(root (root
(statement (statement
(statement_kind
(assignment (assignment
(identifier) (identifier)
(assignment_operator) (assignment_operator)
(statement (statement
(statement_kind
(expression (expression
(command (command
(command_text) (command_text))))))))
(command_argument)
(command_argument))))))
(statement (statement
(statement_kind
(assignment (assignment
(identifier) (identifier)
(assignment_operator) (assignment_operator)
(statement (statement
(statement_kind
(expression (expression
(command (command
(command_text) (command_text)
(command_argument))))))) (command_argument)))))))))
================================================================================ ================================================================================
Command with Semicolon Command with Semicolon
@ -136,41 +119,21 @@ ls_output = ^ls --long -a; ls_output
(root (root
(statement (statement
(statement_kind
(assignment (assignment
(identifier) (identifier)
(assignment_operator) (assignment_operator)
(statement (statement
(statement_kind
(expression (expression
(command (command
(command_text) (command_text)
(command_argument) (command_argument)
(command_argument)))))) (command_argument))))))))
(statement (statement
(statement_kind
(expression (expression
(identifier)))) (identifier)))))
================================================================================
Command with Semicolon
================================================================================
ls_output = ^ls --long -a; ls_output
--------------------------------------------------------------------------------
(root
(statement
(assignment
(identifier)
(assignment_operator)
(statement
(expression
(command
(command_text)
(command_argument)
(command_argument))))))
(statement
(expression
(identifier))))
================================================================================ ================================================================================
Command with Quoted Semicolon Command with Quoted Semicolon
@ -182,14 +145,17 @@ ls_output = ^echo ';'; ls_output
(root (root
(statement (statement
(statement_kind
(assignment (assignment
(identifier) (identifier)
(assignment_operator) (assignment_operator)
(statement (statement
(statement_kind
(expression (expression
(command (command
(command_text) (command_text)
(command_argument)))))) (command_argument))))))))
(statement (statement
(statement_kind
(expression (expression
(identifier)))) (identifier)))))

View File

@ -9,8 +9,9 @@ not_a_comment
(root (root
(statement (statement
(statement_kind
(expression (expression
(identifier)))) (identifier)))))
================================================================================ ================================================================================
Partial Line Comments Partial Line Comments
@ -22,8 +23,9 @@ not_a_comment # comment
(root (root
(statement (statement
(statement_kind
(expression (expression
(identifier)))) (identifier)))))
================================================================================ ================================================================================
Multiline Comments Multiline Comments
@ -37,9 +39,11 @@ not_a_comment #
(root (root
(statement (statement
(statement_kind
(expression (expression
(identifier))) (identifier))))
(statement (statement
(statement_kind
(expression (expression
(value (value
(string))))) (string))))))

View File

@ -11,66 +11,109 @@ enum Foobar {
(root (root
(statement (statement
(statement_kind
(type_definition (type_definition
(enum_definition (enum_definition
(identifier) (identifier)
(identifier) (identifier)
(identifier))))) (identifier))))))
================================================================================ ================================================================================
Nested Enum Enum with Arguments
================================================================================ ================================================================================
enum Foobar { enum Foobar<T, U> {
Foo(str), Foo<T>,
Bar(enum BazBuff { Bar<U>,
Baz,
Buff,
})
} }
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
(root (root
(statement (statement
(statement_kind
(type_definition (type_definition
(enum_definition (enum_definition
(identifier) (identifier)
(type_arguments
(type
(identifier))
(type
(identifier)))
(identifier) (identifier)
(type_arguments
(type
(identifier)))
(identifier)
(type_arguments
(type
(identifier))))))))
================================================================================
Complex Enum
================================================================================
enum Foobar<T> {
Foo<Foo>,
Bar<int, float>,
Baz<Option<T>>,
}
--------------------------------------------------------------------------------
(root
(statement
(statement_kind
(type_definition
(enum_definition
(identifier)
(type_arguments
(type
(identifier)))
(identifier)
(type_arguments
(type
(identifier)))
(identifier)
(type_arguments
(type) (type)
(type))
(identifier) (identifier)
(type_definition (type_arguments
(enum_definition (type
(identifier) (identifier)
(identifier) (type_arguments
(identifier))))))) (type
(identifier))))))))))
================================================================================ ================================================================================
Simple Enum Instance Simple Enum Instance
================================================================================ ================================================================================
new Foobar:Foo FooBar::Foo
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
(root (root
(statement (statement
(statement_kind
(expression (expression
(value (value
(enum_instance (enum_instance
(identifier) (identifier)
(identifier)))))) (identifier)))))))
================================================================================ ================================================================================
Nested Enum Instance Nested Enum Instance
================================================================================ ================================================================================
new Foobar:Bar(new BazBuf:Baz(123)) FooBar::Bar(BazBuf::Baz(123))
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
(root (root
(statement (statement
(statement_kind
(expression (expression
(value (value
(enum_instance (enum_instance
@ -83,4 +126,4 @@ new Foobar:Bar(new BazBuf:Baz(123))
(identifier) (identifier)
(expression (expression
(value (value
(integer))))))))))) (integer))))))))))))

View File

@ -14,10 +14,12 @@ fib = (i <int>) <int> {
(root (root
(statement (statement
(statement_kind
(assignment (assignment
(identifier) (identifier)
(assignment_operator) (assignment_operator)
(statement (statement
(statement_kind
(expression (expression
(value (value
(function (function
@ -28,6 +30,7 @@ fib = (i <int>) <int> {
(type)) (type))
(block (block
(statement (statement
(statement_kind
(if_else (if_else
(if (if
(expression (expression
@ -40,12 +43,14 @@ fib = (i <int>) <int> {
(integer))))) (integer)))))
(block (block
(statement (statement
(statement_kind
(expression (expression
(value (value
(integer)))))) (integer)))))))
(else (else
(block (block
(statement (statement
(statement_kind
(expression (expression
(math (math
(expression (expression
@ -72,4 +77,4 @@ fib = (i <int>) <int> {
(math_operator) (math_operator)
(expression (expression
(value (value
(integer)))))))))))))))))))))) (integer))))))))))))))))))))))))))

View File

@ -10,6 +10,7 @@ for i in [1, 2, 3] {
(root (root
(statement (statement
(statement_kind
(for (for
(identifier) (identifier)
(expression (expression
@ -26,13 +27,13 @@ for i in [1, 2, 3] {
(integer)))))) (integer))))))
(block (block
(statement (statement
(statement_kind
(expression (expression
(function_call (function_call
(function_expression (function_expression
(value (identifier))
(built_in_value)))
(expression (expression
(identifier))))))))) (identifier)))))))))))
================================================================================ ================================================================================
Nested For Loop Nested For Loop
@ -48,22 +49,24 @@ for list in list_of_lists {
(root (root
(statement (statement
(statement_kind
(for (for
(identifier) (identifier)
(expression (expression
(identifier)) (identifier))
(block (block
(statement (statement
(statement_kind
(for (for
(identifier) (identifier)
(expression (expression
(identifier)) (identifier))
(block (block
(statement (statement
(statement_kind
(expression (expression
(function_call (function_call
(function_expression (function_expression
(value (identifier))
(built_in_value)))
(expression (expression
(identifier)))))))))))) (identifier)))))))))))))))

View File

@ -8,6 +8,7 @@ Anonymous Function
(root (root
(statement (statement
(statement_kind
(expression (expression
(value (value
(function (function
@ -15,9 +16,10 @@ Anonymous Function
(type)) (type))
(block (block
(statement (statement
(statement_kind
(expression (expression
(value (value
(string)))))))))) (string))))))))))))
================================================================================ ================================================================================
Function Assignment Function Assignment
@ -31,10 +33,12 @@ foobar = (x <int>, y <int>) <int> {
(root (root
(statement (statement
(statement_kind
(assignment (assignment
(identifier) (identifier)
(assignment_operator) (assignment_operator)
(statement (statement
(statement_kind
(expression (expression
(value (value
(function (function
@ -48,13 +52,14 @@ foobar = (x <int>, y <int>) <int> {
(type)) (type))
(block (block
(statement (statement
(statement_kind
(expression (expression
(math (math
(expression (expression
(identifier)) (identifier))
(math_operator) (math_operator)
(expression (expression
(identifier))))))))))))) (identifier))))))))))))))))
================================================================================ ================================================================================
Identifier Function Call Identifier Function Call
@ -66,13 +71,14 @@ foobar("Hiya")
(root (root
(statement (statement
(statement_kind
(expression (expression
(function_call (function_call
(function_expression (function_expression
(identifier)) (identifier))
(expression (expression
(value (value
(string))))))) (string))))))))
================================================================================ ================================================================================
Index Function Call Index Function Call
@ -84,6 +90,7 @@ foo:bar("Hiya")
(root (root
(statement (statement
(statement_kind
(expression (expression
(function_call (function_call
(function_expression (function_expression
@ -94,7 +101,7 @@ foo:bar("Hiya")
(identifier)))) (identifier))))
(expression (expression
(value (value
(string))))))) (string))))))))
================================================================================ ================================================================================
Double Function Call Double Function Call
@ -106,6 +113,7 @@ foobar()("Hiya")
(root (root
(statement (statement
(statement_kind
(expression (expression
(function_call (function_call
(function_expression (function_expression
@ -114,7 +122,7 @@ foobar()("Hiya")
(identifier)))) (identifier))))
(expression (expression
(value (value
(string))))))) (string))))))))
================================================================================ ================================================================================
Anonymous Function Call Anonymous Function Call
@ -126,6 +134,7 @@ Anonymous Function Call
(root (root
(statement (statement
(statement_kind
(expression (expression
(function_call (function_call
(function_expression (function_expression
@ -138,11 +147,12 @@ Anonymous Function Call
(type)) (type))
(block (block
(statement (statement
(statement_kind
(expression (expression
(identifier))))))) (identifier))))))))
(expression (expression
(value (value
(string))))))) (string))))))))
================================================================================ ================================================================================
Complex Function Call Complex Function Call
@ -161,6 +171,7 @@ foobar(
(root (root
(statement (statement
(statement_kind
(expression (expression
(function_call (function_call
(function_expression (function_expression
@ -176,14 +187,16 @@ foobar(
(map (map
(identifier) (identifier)
(statement (statement
(statement_kind
(expression (expression
(value (value
(integer)))) (integer)))))
(identifier) (identifier)
(statement (statement
(statement_kind
(expression (expression
(value (value
(integer))))))))))) (integer)))))))))))))
================================================================================ ================================================================================
Callback Function Call Callback Function Call
@ -195,6 +208,7 @@ x(() <bool> { true })
(root (root
(statement (statement
(statement_kind
(expression (expression
(function_call (function_call
(function_expression (function_expression
@ -206,9 +220,10 @@ x(() <bool> { true })
(type)) (type))
(block (block
(statement (statement
(statement_kind
(expression (expression
(value (value
(boolean)))))))))))) (boolean))))))))))))))
================================================================================ ================================================================================
Nested Function Call Nested Function Call
@ -220,6 +235,7 @@ from_json(read('file.json'))
(root (root
(statement (statement
(statement_kind
(expression (expression
(function_call (function_call
(function_expression (function_expression
@ -230,4 +246,4 @@ from_json(read('file.json'))
(identifier)) (identifier))
(expression (expression
(value (value
(string))))))))) (string))))))))))

View File

@ -11,14 +11,18 @@ x123x
(root (root
(statement (statement
(expression (statement_kind
(identifier)))
(statement
(expression
(identifier)))
(statement
(expression
(identifier)))
(statement
(expression (expression
(identifier)))) (identifier))))
(statement
(statement_kind
(expression
(identifier))))
(statement
(statement_kind
(expression
(identifier))))
(statement
(statement_kind
(expression
(identifier)))))

View File

@ -8,6 +8,7 @@ if true { "True" }
(root (root
(statement (statement
(statement_kind
(if_else (if_else
(if (if
(expression (expression
@ -15,9 +16,10 @@ if true { "True" }
(boolean))) (boolean)))
(block (block
(statement (statement
(statement_kind
(expression (expression
(value (value
(string))))))))) (string)))))))))))
================================================================================ ================================================================================
Complex If Complex If
@ -29,6 +31,7 @@ if 1 == 1 && 2 == 2 && 3 == 3 { "True" }
(root (root
(statement (statement
(statement_kind
(if_else (if_else
(if (if
(expression (expression
@ -66,9 +69,10 @@ if 1 == 1 && 2 == 2 && 3 == 3 { "True" }
(integer))))) (integer)))))
(block (block
(statement (statement
(statement_kind
(expression (expression
(value (value
(string))))))))) (string)))))))))))
================================================================================ ================================================================================
Nested If Nested If
@ -86,6 +90,7 @@ if true {
(root (root
(statement (statement
(statement_kind
(if_else (if_else
(if (if
(expression (expression
@ -93,6 +98,7 @@ if true {
(boolean))) (boolean)))
(block (block
(statement (statement
(statement_kind
(if_else (if_else
(if (if
(expression (expression
@ -106,15 +112,17 @@ if true {
(integer))))) (integer)))))
(block (block
(statement (statement
(statement_kind
(expression (expression
(value (value
(string)))))) (string)))))))
(else (else
(block (block
(statement (statement
(statement_kind
(expression (expression
(value (value
(string))))))))))))) (string))))))))))))))))
================================================================================ ================================================================================
If Else If Else
@ -126,6 +134,7 @@ if false { "True" } else { "False" }
(root (root
(statement (statement
(statement_kind
(if_else (if_else
(if (if
(expression (expression
@ -133,15 +142,17 @@ if false { "True" } else { "False" }
(boolean))) (boolean)))
(block (block
(statement (statement
(statement_kind
(expression (expression
(value (value
(string)))))) (string)))))))
(else (else
(block (block
(statement (statement
(statement_kind
(expression (expression
(value (value
(string))))))))) (string)))))))))))
================================================================================ ================================================================================
If Else If If Else If
@ -157,6 +168,7 @@ if 1 == 1 {
(root (root
(statement (statement
(statement_kind
(if_else (if_else
(if (if
(expression (expression
@ -170,9 +182,10 @@ if 1 == 1 {
(integer))))) (integer)))))
(block (block
(statement (statement
(statement_kind
(expression (expression
(value (value
(string)))))) (string)))))))
(else_if (else_if
(expression (expression
(logic (logic
@ -185,9 +198,10 @@ if 1 == 1 {
(integer))))) (integer)))))
(block (block
(statement (statement
(statement_kind
(expression (expression
(value (value
(string))))))))) (string)))))))))))
================================================================================ ================================================================================
If Else Else If Else If Else Else If Else
@ -207,6 +221,7 @@ if false {
(root (root
(statement (statement
(statement_kind
(if_else (if_else
(if (if
(expression (expression
@ -214,18 +229,20 @@ if false {
(boolean))) (boolean)))
(block (block
(statement (statement
(statement_kind
(expression (expression
(value (value
(string)))))) (string)))))))
(else_if (else_if
(expression (expression
(value (value
(boolean))) (boolean)))
(block (block
(statement (statement
(statement_kind
(expression (expression
(value (value
(string)))))) (string)))))))
(else_if (else_if
(expression (expression
(logic (logic
@ -244,12 +261,14 @@ if false {
(integer))))) (integer)))))
(block (block
(statement (statement
(statement_kind
(expression (expression
(value (value
(string)))))) (string)))))))
(else (else
(block (block
(statement (statement
(statement_kind
(expression (expression
(value (value
(string))))))))) (string)))))))))))

View File

@ -12,6 +12,7 @@ foobar:1:42
(root (root
(statement (statement
(statement_kind
(expression (expression
(index (index
(index_expression (index_expression
@ -22,15 +23,17 @@ foobar:1:42
(value (value
(integer))))) (integer)))))
(index_expression (index_expression
(identifier))))) (identifier))))))
(statement (statement
(statement_kind
(expression (expression
(index (index
(index_expression (index_expression
(identifier)) (identifier))
(index_expression (index_expression
(identifier))))) (identifier))))))
(statement (statement
(statement_kind
(expression (expression
(index (index
(index_expression (index_expression
@ -42,7 +45,7 @@ foobar:1:42
(integer))))) (integer)))))
(index_expression (index_expression
(value (value
(integer))))))) (integer))))))))
================================================================================ ================================================================================
Nested Indexes Nested Indexes
@ -54,6 +57,7 @@ Nested Indexes
(root (root
(statement (statement
(statement_kind
(expression (expression
(index (index
(index_expression (index_expression
@ -85,7 +89,7 @@ Nested Indexes
(value (value
(integer))))) (integer)))))
(index_expression (index_expression
(range)))))) (range)))))))
================================================================================ ================================================================================
Function Call Index Function Call Index
@ -97,6 +101,7 @@ x:(y()):0
(root (root
(statement (statement
(statement_kind
(expression (expression
(index (index
(index_expression (index_expression
@ -109,4 +114,4 @@ x:(y()):0
(identifier)))))) (identifier))))))
(index_expression (index_expression
(value (value
(integer))))))) (integer))))))))

View File

@ -8,6 +8,7 @@ List Declaration
(root (root
(statement (statement
(statement_kind
(expression (expression
(value (value
(list (list
@ -16,7 +17,7 @@ List Declaration
(string))) (string)))
(expression (expression
(value (value
(integer)))))))) (integer)))))))))
================================================================================ ================================================================================
List Nesting List Nesting
@ -28,6 +29,7 @@ List Nesting
(root (root
(statement (statement
(statement_kind
(expression (expression
(value (value
(list (list
@ -45,4 +47,4 @@ List Nesting
(list (list
(expression (expression
(value (value
(integer)))))))))))))) (integer)))))))))))))))

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