Compare commits

..

No commits in common. "main" and "memory_management" have entirely different histories.

47 changed files with 25109 additions and 27309 deletions

2
Cargo.lock generated
View File

@ -369,7 +369,7 @@ dependencies = [
[[package]] [[package]]
name = "dust-lang" name = "dust-lang"
version = "0.4.2" version = "0.4.1"
dependencies = [ dependencies = [
"cc", "cc",
"clap", "clap",

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.2" version = "0.4.1"
repository = "https://git.jeffa.io/jeff/dust.git" repository = "https://git.jeffa.io/jeff/dust.git"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"

224
README.md
View File

@ -1,100 +1,188 @@
# Dust # Dust
High-level programming language with effortless concurrency, automatic memory management, type safety and strict error handling. !!! Dust is an experimental project under active development. !!!
![Dust version of an example from The Rust Programming Language.](https://git.jeffa.io/jeff/dust/raw/branch/main/docs/assets/example_0.png) Dust is a general purpose programming language that emphasises concurrency and correctness.
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)
- [Easy to Read and Write](#easy-to-read-and-write) - [Usage](#usage)
- [Effortless Concurrency](#effortless-concurrency) - [Dust Language](#dust-language)
- [Helpful Errors](#helpful-errors) - [Installation](#installation)
- [Static analysis](#static-analysis) - [Benchmarks](#benchmarks)
- [Debugging](#debugging) - [Implementation](#implementation)
- [Automatic Memory Management](#automatic-memory-management) - [Acknowledgements](#acknowledgements)
- [Error Handling](#error-handling)
- [Installation and Usage](#installation-and-usage)
<!--toc:end--> <!--toc:end-->
## Features ## Features
### Easy to Read and Write - Simplicity: Dust is designed to be easy to learn.
- Speed: Dust is built on [Tree Sitter] and [Rust] to prioritize performance and correctness. See [Benchmarks] below.
- Concurrency: Safe, effortless parallel code using thread pools.
- Safety: Written in safe, stable Rust.
- Correctness: Type checking makes it easy to write good code.
Dust has simple, easy-to-learn syntax. ## Installation
```js ### Cargo
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
``` ```
### Effortless Concurrency ## Usage
Write multi-threaded code as easily as you would write code for a single thread. 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.
```js ```sh
async { dust -c "output('Hello world!')"
output('Will this one print first?') # Output: Hello world!
output('Or will this one?')
output('Who knows! Each "output" will run in its own thread!')
}
``` ```
### Helpful Errors Or just provide a path to the source file.
Dust shows you exactly where your code went wrong and suggests changes. ```sh
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)
}
``` ```
## Installation and Usage Run `dust --help` to see the available commands and options.
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. ```txt
General purpose programming language
To install from the git repository: Usage: dust [OPTIONS] [PATH]
```fish Arguments:
git clone https://git.jeffa.io/jeff/dust [PATH] Location of the file to run
cd dust
cargo run --release Options:
-c, --command <COMMAND> Dust source code to evaluate
-i, --input <INPUT> Data to assign to the "input" variable
-p, --input-path <INPUT_PATH> A path to file whose contents will be assigned to the "input" variable
-t, --tree Show the syntax tree
-h, --help Print help
-V, --version Print version
``` ```
To install with cargo: ## Dust Language
```fish See the [Language Reference](/docs/language.md) for more information.
cargo install dust-lang
dust
```
## Benchmarks ## Benchmarks
## Development Status 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.
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.
Currently, Dust is being prepared for version 1.0. Until then, there may be breaking changes to the language and CLI. For the first test, a file with four entries was used.
| Command | Mean [ms] | Min [ms] | Max [ms]
|:---|---:|---:|---:|
| Dust | 3.1 ± 0.5 | 2.4 | 8.4 |
| jq | 33.7 ± 2.2 | 30.0 | 61.8 |
| NodeJS | 226.4 ± 13.1 | 197.6 | 346.2 |
| Nushell | 51.6 ± 3.7 | 45.4 | 104.3 |
The second set of data is from the GitHub API, it consists of 100 commits from the jq GitHub repo.
| Command | Mean [ms] | Min [ms] | Max [ms] |
|:---|---:|---:|---:|
| Dust | 6.8 ± 0.6 | 5.7 | 12.0 | 2.20 ± 0.40 |
| jq | 43.3 ± 3.6 | 37.6 | 81.6 | 13.95 ± 2.49 |
| NodeJS | 224.9 ± 12.3 | 194.8 | 298.5 |
| Nushell | 59.2 ± 5.7 | 49.7 | 125.0 | 19.11 ± 3.55 |
This data came from CERN, it is a massive file of 100,000 entries.
| Command | Mean [ms] | Min [ms] | Max [ms] |
|:---|---:|---:|---:|
| Dust | 1080.8 ± 38.7 | 975.3 | 1326.6 |
| jq | 1305.3 ± 64.3 | 1159.7 | 1925.1 |
| NodeJS | 1850.5 ± 72.5 | 1641.9 | 2395.1 |
| Nushell | 1850.5 ± 86.2 | 1625.5 | 2400.7 |
The tests were run after 5 warmup runs and the cache was cleared before each run.
```sh
hyperfine \
--shell none \
--warmup 5 \
--prepare "rm -rf /root/.cache" \
--runs 1000 \
--parameter-list data_path seaCreatures.json,jq_data.json,dielectron.json \
--export-markdown test_output.md \
"dust -c '(length (from_json input))' -p {data_path}" \
"jq 'length' {data_path}" \
"node --eval \"require('node:fs').readFile('{data_path}',(err,data)=>{console.log(JSON.parse(data).length)})\"" \
"nu -c 'open {data_path} | length'"
```
## Implementation
Dust is formally defined as a Tree Sitter grammar in the tree-sitter-dust directory. Tree sitter generates a parser, written in C, from a set of rules defined in JavaScript. Dust itself is a rust binary that calls the C parser using FFI.
Tests are written in three places: in the Rust library, in Dust as examples and in the Tree Sitter test format. Generally, features are added by implementing and testing the syntax in the tree-sitter-dust repository, then writing library tests to evaluate the new syntax. Implementation tests run the Dust files in the "examples" directory and should be used to demonstrate and verify that features work together.
Tree Sitter generates a concrete syntax tree, which Dust traverses to create an abstract syntax tree that can run the Dust code. The CST generation is an extra step but it allows easy testing of the parser, defining the language in one file and makes the syntax easy to modify and expand. Because it uses Tree Sitter, developer-friendly features like syntax highlighting and code navigation are already available in any text editor that supports Tree Sitter.
## 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

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

View File

@ -2,7 +2,7 @@ data = json:parse(fs:read_file('examples/assets/jq_data.json'))
new_data = [] new_data = []
for commit_data in data as collection { for commit_data in data {
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 \
"target/release/dust -c 'length(json:parse(fs:read_file(\"{data_path}\")))'" \ "dust -c 'length(json:parse(input))' -p {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 \
"target/release/dust -c 'length(json:parse(fs:read_file(\"{data_path}\")))'" \ "dust -c 'length(json:parse(input))' -p {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 \
"target/release/dust -c 'length(json:parse(fs:read_file(\"{data_path}\")))'" \ "dust -c 'length(json:parse(input))' -p {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

@ -37,15 +37,9 @@ 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 self.r#type.accepts(&initial_type) { if let Type::List(item_type) = &self.r#type {
return Ok(());
}
if let Type::ListOf(item_type) = &self.r#type {
match &initial_type { match &initial_type {
Type::ListOf(expected_item_type) => { Type::List(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(),
@ -83,9 +77,7 @@ 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 self.r#type.accepts(&value.r#type()?) { let converted_value = if let Type::List(_) = self.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) => {

View File

@ -60,8 +60,6 @@ impl AbstractTree for Assignment {
self.statement.expected_type(context)? self.statement.expected_type(context)?
}; };
log::info!("Setting type: {} <{}>", self.identifier, r#type);
context.set_type(self.identifier.clone(), r#type)?; context.set_type(self.identifier.clone(), r#type)?;
} }
@ -80,7 +78,7 @@ impl AbstractTree for Assignment {
} }
} }
AssignmentOperator::PlusEqual => { AssignmentOperator::PlusEqual => {
if let Type::ListOf(expected) = type_specification.inner() { if let Type::List(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 +107,7 @@ impl AbstractTree for Assignment {
match self.operator { match self.operator {
AssignmentOperator::Equal => {} AssignmentOperator::Equal => {}
AssignmentOperator::PlusEqual => { AssignmentOperator::PlusEqual => {
if let Type::ListOf(expected) = self.identifier.expected_type(context)? { if let Type::List(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) {
@ -157,8 +155,6 @@ impl AbstractTree for Assignment {
.set_value(self.identifier.clone(), new_value.clone())?; .set_value(self.identifier.clone(), new_value.clone())?;
} }
log::info!("RUN assignment: {} = {}", self.identifier, new_value);
context.set_value(self.identifier.clone(), new_value)?; context.set_value(self.identifier.clone(), new_value)?;
Ok(Value::none()) Ok(Value::none())

View File

@ -1,4 +1,4 @@
use std::process::{self, Stdio}; use std::process;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode; use tree_sitter::Node as SyntaxNode;
@ -53,19 +53,18 @@ impl AbstractTree for Command {
} }
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> { fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(()) todo!()
} }
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::from_utf8(output)?)) Ok(Value::String(string))
} }
} }

View File

@ -9,11 +9,11 @@ use crate::{
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct EnumDefinition { pub struct EnumDefinition {
identifier: Identifier, identifier: Identifier,
variants: Vec<(Identifier, Vec<Type>)>, variants: Vec<(Identifier, Option<Type>)>,
} }
impl EnumDefinition { impl EnumDefinition {
pub fn new(identifier: Identifier, variants: Vec<(Identifier, Vec<Type>)>) -> Self { pub fn new(identifier: Identifier, variants: Vec<(Identifier, Option<Type>)>) -> Self {
Self { Self {
identifier, identifier,
variants, variants,
@ -27,10 +27,6 @@ impl EnumDefinition {
pub fn identifier(&self) -> &Identifier { pub fn identifier(&self) -> &Identifier {
&self.identifier &self.identifier
} }
pub fn variants(&self) -> &Vec<(Identifier, Vec<Type>)> {
&self.variants
}
} }
impl AbstractTree for EnumDefinition { impl AbstractTree for EnumDefinition {
@ -41,25 +37,21 @@ impl AbstractTree for EnumDefinition {
let identifier = Identifier::from_syntax(identifier_node, source, context)?; let identifier = Identifier::from_syntax(identifier_node, source, context)?;
let mut variants = Vec::new(); let mut variants = Vec::new();
let mut current_identifier: Option<Identifier> = None; let mut current_identifier = None;
let mut types = Vec::new();
for index in 3..node.child_count() - 1 { for index in 3..node.child_count() - 1 {
let child = node.child(index).unwrap(); let child = node.child(index).unwrap();
if child.kind() == "identifier" { if child.kind() == "identifier" {
if let Some(identifier) = &current_identifier {
variants.push((identifier.clone(), types));
}
current_identifier = Some(Identifier::from_syntax(child, source, context)?); current_identifier = Some(Identifier::from_syntax(child, source, context)?);
types = Vec::new();
} }
if child.kind() == "type" { if let Some(identifier) = &current_identifier {
let r#type = Type::from_syntax(child, source, context)?; if child.kind() == "type" {
let r#type = Type::from_syntax(child, source, context)?;
types.push(r#type); variants.push((identifier.clone(), Some(r#type)));
}
} }
} }
@ -73,14 +65,13 @@ impl AbstractTree for EnumDefinition {
Ok(Type::None) Ok(Type::None)
} }
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> { 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(()) Ok(())
} }
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> { fn run(&self, _source: &str, context: &Context) -> Result<Value, RuntimeError> {
context.set_definition(self.identifier.clone(), TypeDefinition::Enum(self.clone()))?;
Ok(Value::none()) Ok(Value::none())
} }
} }

View File

@ -66,22 +66,16 @@ impl AbstractTree for For {
self.collection.validate(_source, context)?; self.collection.validate(_source, context)?;
let collection_type = self.collection.expected_type(context)?; let collection_type = self.collection.expected_type(context)?;
let item_type = match collection_type { let item_type = if let Type::List(item_type) = collection_type {
Type::Any => Type::Any, item_type.as_ref().clone()
Type::Collection => Type::Any, } else if let Type::Range = collection_type {
Type::List => Type::Any, Type::Integer
Type::ListOf(_) => todo!(), } else {
Type::ListExact(_) => todo!(), return Err(ValidationError::TypeCheck {
Type::Map(_) => todo!(), expected: Type::Collection,
Type::String => todo!(), actual: collection_type,
Type::Range => todo!(), position: self.source_position,
_ => { });
return Err(ValidationError::TypeCheck {
expected: Type::Collection,
actual: collection_type,
position: self.source_position,
});
}
}; };
let key = self.item_id.clone(); let key = self.item_id.clone();
@ -100,14 +94,12 @@ impl AbstractTree for For {
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)?;
} }
@ -119,13 +111,11 @@ 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

@ -35,7 +35,7 @@ impl AbstractTree for Index {
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::ListOf(item_type) => Ok(*item_type.clone()), Type::List(item_type) => Ok(*item_type.clone()),
Type::Map(map_types_option) => { Type::Map(map_types_option) => {
if let (Some(map_type), IndexExpression::Identifier(identifier)) = if let (Some(map_type), IndexExpression::Identifier(identifier)) =
(map_types_option, &self.index) (map_types_option, &self.index)

View File

@ -57,17 +57,13 @@ 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) => { IndexExpression::Identifier(identifier) => identifier.validate(_source, _context),
context.add_allowance(identifier)?; IndexExpression::Index(index) => index.validate(_source, _context),
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)
} }
} }
} }

View File

@ -45,8 +45,6 @@ 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)
} }
@ -54,9 +52,6 @@ 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,5 +1,3 @@
use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
@ -76,18 +74,3 @@ 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

@ -20,8 +20,6 @@ impl AbstractTree for MathOperator {
_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,

View File

@ -19,7 +19,7 @@ pub enum Type {
Collection, Collection,
Custom { Custom {
name: Identifier, name: Identifier,
arguments: Vec<Type>, argument: Option<Box<Type>>,
}, },
Float, Float,
Function { Function {
@ -27,19 +27,20 @@ pub enum Type {
return_type: Box<Type>, return_type: Box<Type>,
}, },
Integer, Integer,
List, List(Box<Type>),
ListOf(Box<Type>),
ListExact(Vec<Type>),
Map(Option<BTreeMap<Identifier, Type>>), Map(Option<BTreeMap<Identifier, Type>>),
None,
Number, Number,
String, String,
Range, Range,
None,
} }
impl Type { impl Type {
pub fn custom(name: Identifier, arguments: Vec<Type>) -> Self { pub fn custom(name: Identifier, argument: Option<Type>) -> Self {
Type::Custom { name, arguments } Type::Custom {
name,
argument: argument.map(|r#type| Box::new(r#type)),
}
} }
pub fn option(inner_type: Option<Type>) -> Self { pub fn option(inner_type: Option<Type>) -> Self {
@ -47,7 +48,7 @@ impl Type {
} }
pub fn list(item_type: Type) -> Self { pub fn list(item_type: Type) -> Self {
Type::ListOf(Box::new(item_type)) Type::List(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 {
@ -69,19 +70,14 @@ 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::String) | (Type::Collection, Type::List(_))
| (Type::Collection, Type::List) | (Type::List(_), Type::Collection)
| (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::List, Type::List)
| (Type::Map(None), Type::Map(None)) | (Type::Map(None), Type::Map(None))
| (Type::Number, Type::Number) | (Type::Number, Type::Number)
| (Type::Number, Type::Integer) | (Type::Number, Type::Integer)
@ -94,28 +90,21 @@ impl Type {
( (
Type::Custom { Type::Custom {
name: left_name, name: left_name,
arguments: left_arguments, argument: left_argument,
}, },
Type::Custom { Type::Custom {
name: right_name, name: right_name,
arguments: right_arguments, argument: right_argument,
}, },
) => left_name == right_name && left_arguments == right_arguments, ) => {
(Type::ListOf(self_item_type), Type::ListOf(other_item_type)) => { if left_name != right_name {
self_item_type.accepts(&other_item_type) false
} } else {
(Type::ListExact(self_types), Type::ListExact(other_types)) => { left_argument == right_argument
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::List(self_item_type), Type::List(other_item_type)) => {
| (Type::ListOf(of_type), Type::ListExact(exact_types)) => { self_item_type.accepts(&other_item_type)
exact_types.iter().all(|r#type| r#type == of_type.as_ref())
} }
( (
Type::Function { Type::Function {
@ -148,7 +137,7 @@ impl Type {
} }
pub fn is_list(&self) -> bool { pub fn is_list(&self) -> bool {
matches!(self, Type::ListOf(_)) matches!(self, Type::List(_))
} }
pub fn is_map(&self) -> bool { pub fn is_map(&self) -> bool {
@ -169,19 +158,13 @@ impl AbstractTree for Type {
let r#type = match type_node.kind() { let r#type = match type_node.kind() {
"identifier" => { "identifier" => {
let name = Identifier::from_syntax(type_node, _source, context)?; let name = Identifier::from_syntax(type_node, _source, context)?;
let mut arguments = Vec::new(); let argument = if let Some(child) = node.child(2) {
Some(Type::from_syntax(child, _source, context)?)
} else {
None
};
for index in 2..node.child_count() - 1 { Type::custom(name, argument)
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 type_map = BTreeMap::new();
@ -208,16 +191,7 @@ impl AbstractTree for Type {
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::ListOf(Box::new(item_type)) Type::List(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,
@ -256,8 +230,7 @@ impl AbstractTree for Type {
"str" => Type::String, "str" => Type::String,
_ => { _ => {
return Err(SyntaxError::UnexpectedSyntaxNode { return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "any, bool, float, int, num, str, list, map, custom type, (, [ or {" expected: "any, bool, float, int, num, str, custom type, (, [ or {".to_string(),
.to_string(),
actual: type_node.kind().to_string(), actual: type_node.kind().to_string(),
position: node.range().into(), position: node.range().into(),
}) })
@ -288,7 +261,7 @@ impl Format for Type {
Type::Collection => output.push_str("collection"), Type::Collection => output.push_str("collection"),
Type::Custom { Type::Custom {
name: _, name: _,
arguments: _, argument: _,
} => todo!(), } => todo!(),
Type::Float => output.push_str("float"), Type::Float => output.push_str("float"),
Type::Function { Type::Function {
@ -309,13 +282,11 @@ 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 => todo!(), Type::List(item_type) => {
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::ListExact(_) => todo!(),
Type::Map(_) => { Type::Map(_) => {
output.push_str("map"); output.push_str("map");
} }
@ -333,19 +304,9 @@ 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 { name, arguments } => { Type::Custom { name, argument } => {
if !arguments.is_empty() { if let Some(argument) = argument {
write!(f, "<")?; write!(f, "{name}<{argument}>")
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 { } else {
write!(f, "{name}") write!(f, "{name}")
} }
@ -369,21 +330,7 @@ impl Display for Type {
write!(f, " -> {return_type}") write!(f, " -> {return_type}")
} }
Type::Integer => write!(f, "int"), Type::Integer => write!(f, "int"),
Type::List => write!(f, "list"), Type::List(item_type) => write!(f, "[{item_type}]"),
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"),

View File

@ -124,47 +124,43 @@ impl AbstractTree for ValueNode {
ValueNode::Integer(_) => Type::Integer, ValueNode::Integer(_) => Type::Integer,
ValueNode::String(_) => Type::String, ValueNode::String(_) => Type::String,
ValueNode::List(expressions) => { ValueNode::List(expressions) => {
let mut item_types = Vec::new(); let mut previous_type = None;
for expression in expressions { for expression in expressions {
let expression_type = expression.expected_type(context)?; let expression_type = expression.expected_type(context)?;
item_types.push(expression_type); if let Some(previous) = previous_type {
if expression_type != previous {
return Ok(Type::List(Box::new(Type::Any)));
}
}
previous_type = Some(expression_type);
} }
Type::ListExact(item_types) if let Some(previous) = previous_type {
Type::List(Box::new(previous))
} else {
Type::List(Box::new(Type::Any))
}
} }
ValueNode::Map(map_node) => map_node.expected_type(context)?, ValueNode::Map(map_node) => map_node.expected_type(context)?,
ValueNode::Struct { name, .. } => { ValueNode::Struct { name, .. } => {
Type::custom(name.clone(), Vec::with_capacity(0)) Type::Custom { name: name.clone(), argument: None }
} }
ValueNode::Range(_) => Type::Range, ValueNode::Range(_) => Type::Range,
ValueNode::Enum { name, variant, expression: _ } => { ValueNode::Enum { name, variant: _, expression } => {
let types: Vec<Type> = if let Some(type_definition) = context.get_definition(name)? { if let Some(expression) = expression {
if let TypeDefinition::Enum(enum_definition) = type_definition { Type::Custom {
let types = enum_definition.variants().into_iter().find_map(|(identifier, types)| { name: name.clone(),
if identifier == variant { argument: Some(Box::new(expression.expected_type(context)?))
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 { } else {
return Err(ValidationError::VariableIdentifierNotFound(name.clone())); Type::Custom{
}; name: name.clone(),
argument: None,
Type::custom(name.clone(), types.clone()) }
}
}, },
}; };
@ -179,13 +175,6 @@ impl AbstractTree for ValueNode {
} }
} }
ValueNode::Map(map_node) => map_node.validate(_source, context)?, ValueNode::Map(map_node) => map_node.validate(_source, context)?,
ValueNode::Enum { name, expression, .. } => {
name.validate(_source, context)?;
if let Some(expression) = expression {
expression.validate(_source, context)?;
}
}
_ => {}, _ => {},
} }
@ -351,7 +340,7 @@ impl Ord for ValueNode {
} }
impl PartialOrd for ValueNode { impl PartialOrd for ValueNode {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
} }
} }

View File

@ -32,21 +32,15 @@ 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::File, io::Read}; use std::fs::read_to_string;
use enum_iterator::{all, Sequence}; use enum_iterator::{all, Sequence};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -46,11 +46,7 @@ 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 mut file = File::open(path)?; let file_content = read_to_string(path.as_str())?;
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

@ -73,7 +73,13 @@ impl Callable for BuiltInFunction {
fn r#type(&self) -> Type { fn r#type(&self) -> Type {
match self { match self {
BuiltInFunction::AssertEqual => Type::function(vec![Type::Any, Type::Any], Type::None), BuiltInFunction::AssertEqual => Type::function(
vec![Type::Any, Type::Any],
Type::Custom {
name: Identifier::new("Result"),
argument: None,
},
),
BuiltInFunction::Fs(fs_function) => fs_function.r#type(), BuiltInFunction::Fs(fs_function) => fs_function.r#type(),
BuiltInFunction::Json(json_function) => json_function.r#type(), BuiltInFunction::Json(json_function) => json_function.r#type(),
BuiltInFunction::Length => Type::function(vec![Type::Collection], Type::Integer), BuiltInFunction::Length => Type::function(vec![Type::Collection], Type::Integer),

View File

@ -33,8 +33,8 @@ impl BuiltInTypeDefinition {
let definition = TypeDefinition::Enum(EnumDefinition::new( let definition = TypeDefinition::Enum(EnumDefinition::new(
Identifier::new(self.name()), Identifier::new(self.name()),
vec![ vec![
(Identifier::new("Some"), vec![Type::Any]), (Identifier::new("Some"), Some(Type::Any)),
(Identifier::new("None"), Vec::with_capacity(0)), (Identifier::new("None"), None),
], ],
)); ));
@ -44,8 +44,8 @@ impl BuiltInTypeDefinition {
let definition = TypeDefinition::Enum(EnumDefinition::new( let definition = TypeDefinition::Enum(EnumDefinition::new(
Identifier::new(self.name()), Identifier::new(self.name()),
vec![ vec![
(Identifier::new("Ok"), vec![Type::Any]), (Identifier::new("Ok"), Some(Type::Any)),
(Identifier::new("Error"), vec![Type::Any]), (Identifier::new("Err"), Some(Type::Any)),
], ],
)); ));

View File

@ -19,9 +19,15 @@ impl BuiltInType {
match self { match self {
BuiltInType::Option(content_type) => OPTION.get_or_init(|| { BuiltInType::Option(content_type) => OPTION.get_or_init(|| {
if let Some(content_type) = content_type { if let Some(content_type) = content_type {
Type::custom(Identifier::new("Option"), vec![content_type.clone()]) Type::Custom {
name: Identifier::new("Option"),
argument: Some(Box::new(content_type.clone())),
}
} else { } else {
Type::custom(Identifier::new("Option"), Vec::with_capacity(0)) Type::Custom {
name: Identifier::new("Option"),
argument: None,
}
} }
}), }),
} }

View File

@ -264,11 +264,13 @@ impl Context {
/// Set a value to a key. /// Set a value to a key.
pub fn set_value(&self, key: Identifier, value: Value) -> Result<(), RwLockError> { pub fn set_value(&self, key: Identifier, value: Value) -> Result<(), RwLockError> {
log::info!("Setting value: {key} = {value}");
let mut map = self.inner.write()?; let mut map = self.inner.write()?;
let old_data = map.remove(&key); let old_data = map.remove(&key);
if let Some((_, old_counter)) = old_data { if let Some((_, old_counter)) = old_data {
map.insert(key, (ValueData::Value(value), old_counter)); map.insert(key, (ValueData::Value(value), old_counter.clone()));
} else { } else {
map.insert(key, (ValueData::Value(value), UsageCounter::new())); map.insert(key, (ValueData::Value(value), UsageCounter::new()));
} }
@ -281,6 +283,8 @@ impl Context {
/// This allows the interpreter to check a value's type before the value /// This allows the interpreter to check a value's type before the value
/// actually exists by predicting what the abstract tree will produce. /// actually exists by predicting what the abstract tree will produce.
pub fn set_type(&self, key: Identifier, r#type: Type) -> Result<(), RwLockError> { pub fn set_type(&self, key: Identifier, r#type: Type) -> Result<(), RwLockError> {
log::info!("Setting type: {key} <{}>", r#type);
self.inner self.inner
.write()? .write()?
.insert(key, (ValueData::TypeHint(r#type), UsageCounter::new())); .insert(key, (ValueData::TypeHint(r#type), UsageCounter::new()));

View File

@ -16,7 +16,7 @@ use tree_sitter::LanguageError;
use std::fmt::{self, Formatter}; use std::fmt::{self, Formatter};
#[derive(Debug, PartialEq)] #[derive(PartialEq)]
pub enum Error { pub enum Error {
Syntax(SyntaxError), Syntax(SyntaxError),
@ -95,6 +95,12 @@ 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::*;

View File

@ -1,6 +1,5 @@
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use colored::Colorize;
use lyneate::Report; use lyneate::Report;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode; use tree_sitter::Node as SyntaxNode;
@ -13,8 +12,6 @@ use super::rw_lock_error::RwLockError;
pub enum SyntaxError { pub enum SyntaxError {
/// Invalid user input. /// Invalid user input.
InvalidSource { InvalidSource {
expected: String,
actual: String,
position: SourcePosition, position: SourcePosition,
}, },
@ -30,25 +27,25 @@ pub enum SyntaxError {
impl SyntaxError { impl SyntaxError {
pub fn create_report(&self, source: &str) -> String { pub fn create_report(&self, source: &str) -> String {
let messages = match self { let messages = match self {
SyntaxError::InvalidSource { position, .. } => self SyntaxError::InvalidSource { position } => {
.to_string()
.split_inclusive(".")
.map(|message_part| {
(
position.start_byte..position.end_byte,
message_part.to_string(),
(255, 200, 100),
)
})
.collect(),
SyntaxError::RwLock(_) => todo!(),
SyntaxError::UnexpectedSyntaxNode { position, .. } => {
vec![( vec![(
position.start_byte..position.end_byte, position.start_byte..position.end_byte,
self.to_string(), format!(
"Invalid syntax from ({}, {}) to ({}, {}).",
position.start_row,
position.start_column,
position.end_row,
position.end_column,
),
(255, 200, 100), (255, 200, 100),
)] )]
} }
SyntaxError::RwLock(_) => todo!(),
SyntaxError::UnexpectedSyntaxNode {
expected: _,
actual: _,
position: _,
} => todo!(),
}; };
Report::new_byte_spanned(source, messages).display_str() Report::new_byte_spanned(source, messages).display_str()
@ -61,8 +58,6 @@ impl SyntaxError {
Ok(()) Ok(())
} else if actual.is_error() { } else if actual.is_error() {
Err(SyntaxError::InvalidSource { Err(SyntaxError::InvalidSource {
expected: expected.to_owned(),
actual: actual.kind().to_string(),
position: SourcePosition::from(actual.range()), position: SourcePosition::from(actual.range()),
}) })
} else { } else {
@ -84,43 +79,16 @@ 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 {
match self { match self {
SyntaxError::InvalidSource { SyntaxError::InvalidSource { position } => write!(f, "Invalid syntax at {position:?}."),
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::RwLock(_) => todo!(),
SyntaxError::UnexpectedSyntaxNode { SyntaxError::UnexpectedSyntaxNode {
expected, expected,
actual, actual,
position, position,
} => { } => write!(
write!( f,
f, "Unexpected syntax node. Expected {expected} but got {actual} at {position:?}."
"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,6 +1,5 @@
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use colored::Colorize;
use lyneate::Report; use lyneate::Report;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -224,17 +223,10 @@ impl ValidationError {
position, position,
} => vec![( } => vec![(
position.start_byte..position.end_byte, position.start_byte..position.end_byte,
format!( format!("Type {actual} is incompatible with {expected}."),
"Type {} is incompatible with {}.",
actual.to_string().bold().red(),
expected.to_string().bold().green()
),
(200, 200, 200), (200, 200, 200),
)], )],
ValidationError::TypeCheckExpectedFunction { ValidationError::TypeCheckExpectedFunction { actual: _, position: _ } => todo!(),
actual: _,
position: _,
} => todo!(),
ValidationError::VariableIdentifierNotFound(_) => todo!(), ValidationError::VariableIdentifierNotFound(_) => todo!(),
ValidationError::TypeDefinitionNotFound(_) => todo!(), ValidationError::TypeDefinitionNotFound(_) => todo!(),
ValidationError::ExpectedEnumDefintion { actual: _ } => todo!(), ValidationError::ExpectedEnumDefintion { actual: _ } => todo!(),

View File

@ -74,15 +74,25 @@ impl Value {
pub fn r#type(&self) -> Result<Type, RwLockError> { pub fn r#type(&self) -> Result<Type, RwLockError> {
let r#type = match self { let r#type = match self {
Value::List(list) => { Value::List(list) => {
let mut item_types = Vec::new(); let mut previous_type = None;
for value in list.items()?.iter() { for value in list.items()?.iter() {
let r#type = value.r#type()?; let value_type = value.r#type();
item_types.push(r#type); if let Some(previous) = &previous_type {
if &value_type != previous {
return Ok(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) => {
if map.inner().is_empty() { if map.inner().is_empty() {
@ -104,18 +114,7 @@ impl Value {
Value::Boolean(_) => Type::Boolean, Value::Boolean(_) => Type::Boolean,
Value::Range(_) => todo!(), Value::Range(_) => todo!(),
Value::Struct(_) => todo!(), Value::Struct(_) => todo!(),
Value::Enum(enum_instance) => { Value::Enum(_) => todo!(),
let arguments = if let Some(value) = enum_instance.value() {
vec![value.r#type()?]
} else {
Vec::with_capacity(0)
};
Type::Custom {
name: enum_instance.name().clone(),
arguments,
}
}
}; };
Ok(r#type) Ok(r#type)

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::ListOf(Box::new(Type::Float)) target_type: Type::List(Box::new(Type::Float))
})) }))
) )
} }
@ -39,14 +39,14 @@ fn conversion_runtime_error() {
interpret(&format!("json:parse('{JSON}') as [map]")), interpret(&format!("json:parse('{JSON}') as [map]")),
Err(Error::Runtime(RuntimeError::ConversionImpossible { Err(Error::Runtime(RuntimeError::ConversionImpossible {
from: json_value.r#type().unwrap(), from: json_value.r#type().unwrap(),
to: Type::ListOf(Box::new(Type::Map(None))), to: Type::List(Box::new(Type::Map(None))),
position: SourcePosition { position: SourcePosition {
start_byte: 0, start_byte: 0,
end_byte: 33, end_byte: 0,
start_row: 1, start_row: 0,
start_column: 0, start_column: 0,
end_row: 1, end_row: 0,
end_column: 33, end_column: 0
} }
})) }))
) )

View File

@ -1,27 +1,5 @@
use dust_lang::*; 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] #[test]
fn option() { fn option() {
assert_eq!( assert_eq!(
@ -35,12 +13,14 @@ fn option() {
assert_eq!( assert_eq!(
interpret( interpret(
" "
Option::Some(1) opt <Option<int>> = Option::Some(1);
opt
" "
), ),
Ok(Value::Enum(EnumInstance::new( Ok(Value::Enum(EnumInstance::new(
Identifier::new("Option"), Identifier::new("Option"),
Identifier::new("Some"), Identifier::new("None"),
Some(Value::Integer(1)), Some(Value::Integer(1)),
))) )))
); );
@ -59,7 +39,8 @@ fn result() {
assert_eq!( assert_eq!(
interpret( interpret(
" "
Result::Error('uh-oh!') result <Result<int, str>> = Result::Error('uh-oh!')
result
" "
), ),
Ok(Value::Enum(EnumInstance::new( Ok(Value::Enum(EnumInstance::new(

View File

@ -1,4 +1,4 @@
use dust_lang::{error::RuntimeError, *}; use dust_lang::*;
#[test] #[test]
fn args() { fn args() {
@ -23,9 +23,10 @@ fn assert_equal() {
); );
assert_eq!( assert_eq!(
interpret("assert_equal(true, false)"), interpret("assert_equal(true, false)"),
Err(Error::Runtime(RuntimeError::AssertEqualFailed { Ok(Value::Enum(EnumInstance::new(
left: true.into(), Identifier::new("Result"),
right: false.into() Identifier::new("Error"),
})) Some(Value::none()),
)))
); );
} }

View File

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

View File

@ -33,7 +33,7 @@ fn nested_enum() {
} }
enum Foobar { enum Foobar {
Foo, Foo,
Bar<Fizzbuzz>, Bar(Fizzbuzz),
} }
Foobar::Bar(Fizzbuzz::Fizz) Foobar::Bar(Fizzbuzz::Fizz)
@ -53,36 +53,3 @@ fn nested_enum() {
))) )))
); );
} }
#[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

@ -101,7 +101,7 @@ fn function_context_captures_structure_definitions() {
} }
bob = () <User> { bob = () <User> {
User::{ new User {
name = 'bob' name = 'bob'
} }
} }

View File

@ -10,7 +10,7 @@ Simple As
(statement (statement
(statement_kind (statement_kind
(expression (expression
(as_node (as
(expression (expression
(value (value
(integer))) (integer)))
@ -28,7 +28,7 @@ foo as (int) -> int
(statement (statement
(statement_kind (statement_kind
(expression (expression
(as_node (as
(expression (expression
(identifier)) (identifier))
(type (type
@ -49,7 +49,7 @@ for i in foobar as [string] {}
(for (for
(identifier) (identifier)
(expression (expression
(as_node (as
(expression (expression
(identifier)) (identifier))
(type (type

View File

@ -59,6 +59,33 @@ Command Sequence with Arguments
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
(root
(statement
(statement_kind
(expression
(command
(command_text)
(command_argument)
(command_argument)
(command_argument)
(command_argument)))))
(statement
(statement_kind
(expression
(command
(command_text)
(command_argument)
(command_argument))))))
================================================================================
Command Sequence with Arguments
================================================================================
^cargo run -- -c "output('hi there')"
^ls --long -a
--------------------------------------------------------------------------------
(root (root
(statement (statement
(statement_kind (statement_kind
@ -81,7 +108,7 @@ Command Sequence with Arguments
Command Assignment Command Assignment
================================================================================ ================================================================================
ls_output = ^ls; ls_output = ^ls --long -a;
cat_output = ^cat Cargo.toml; cat_output = ^cat Cargo.toml;
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -96,7 +123,9 @@ cat_output = ^cat Cargo.toml;
(statement_kind (statement_kind
(expression (expression
(command (command
(command_text)))))))) (command_text)
(command_argument)
(command_argument))))))))
(statement (statement
(statement_kind (statement_kind
(assignment (assignment
@ -117,6 +146,32 @@ ls_output = ^ls --long -a; ls_output
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
(root
(statement
(statement_kind
(assignment
(identifier)
(assignment_operator)
(statement
(statement_kind
(expression
(command
(command_text)
(command_argument)
(command_argument))))))))
(statement
(statement_kind
(expression
(identifier)))))
================================================================================
Command with Semicolon
================================================================================
ls_output = ^ls --long -a; ls_output
--------------------------------------------------------------------------------
(root (root
(statement (statement
(statement_kind (statement_kind

View File

@ -19,12 +19,15 @@ enum Foobar {
(identifier)))))) (identifier))))))
================================================================================ ================================================================================
Enum with Arguments Nested Enum
================================================================================ ================================================================================
enum Foobar<T, U> { enum Foobar {
Foo<T>, Foo(str),
Bar<U>, Bar(enum BazBuff {
Baz,
Buff,
})
} }
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -35,62 +38,20 @@ enum Foobar<T, U> {
(type_definition (type_definition
(enum_definition (enum_definition
(identifier) (identifier)
(type_arguments
(type
(identifier))
(type
(identifier)))
(identifier) (identifier)
(type_arguments (type)
(type
(identifier)))
(identifier) (identifier)
(type_arguments (type_definition
(type (enum_definition
(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))
(identifier)
(type_arguments
(type
(identifier) (identifier)
(type_arguments (identifier)
(type (identifier))))))))
(identifier))))))))))
================================================================================ ================================================================================
Simple Enum Instance Simple Enum Instance
================================================================================ ================================================================================
FooBar::Foo Foobar::Foo
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

View File

@ -28,69 +28,3 @@ x <{ y <int> }> = { y = 2 }
(expression (expression
(value (value
(integer)))))))))))))) (integer))))))))))))))
================================================================================
List Of
================================================================================
x <list<str>> = ['foo', 'bar', 'baz']
--------------------------------------------------------------------------------
(root
(statement
(statement_kind
(assignment
(identifier)
(type_specification
(type
(type)))
(assignment_operator)
(statement
(statement_kind
(expression
(value
(list
(expression
(value
(string)))
(expression
(value
(string)))
(expression
(value
(string))))))))))))
================================================================================
Exact List
================================================================================
foo <[int, float, int]> = [1, 2.0, 3]
--------------------------------------------------------------------------------
(root
(statement
(statement_kind
(assignment
(identifier)
(type_specification
(type
(type)
(type)
(type)))
(assignment_operator)
(statement
(statement_kind
(expression
(value
(list
(expression
(value
(integer)))
(expression
(value
(float)))
(expression
(value
(integer))))))))))))

View File

@ -31,7 +31,6 @@ module.exports = grammar({
$.for, $.for,
$.if_else, $.if_else,
$.index_assignment, $.index_assignment,
$.loop_node,
$.match, $.match,
$.pipe, $.pipe,
$.while, $.while,
@ -52,7 +51,7 @@ module.exports = grammar({
_expression_kind: $ => _expression_kind: $ =>
prec.right( prec.right(
choice( choice(
$.as_node, $.as,
$.function_call, $.function_call,
$.identifier, $.identifier,
$.index, $.index,
@ -73,7 +72,7 @@ module.exports = grammar({
), ),
), ),
as_node: $ => as: $ =>
seq($.expression, 'as', $.type), seq($.expression, 'as', $.type),
pipe: $ => pipe: $ =>
@ -102,7 +101,7 @@ module.exports = grammar({
), ),
), ),
command_text: $ => /[^\s;]+/, command_text: $ => /\S+/,
command_argument: $ => command_argument: $ =>
choice( choice(
@ -328,12 +327,6 @@ module.exports = grammar({
), ),
), ),
loop_node: $ =>
seq(
'loop',
$.block,
),
while: $ => while: $ =>
seq( seq(
'while', 'while',
@ -361,21 +354,7 @@ module.exports = grammar({
'collection', 'collection',
'float', 'float',
'int', 'int',
'list',
'map', 'map',
'none',
'num',
'str',
// Custom type
$.identifier,
// Custom type with arguments
seq(
$.identifier,
$.type_arguments
),
// Map with exact fields
seq( seq(
'{', '{',
repeat1( repeat1(
@ -386,23 +365,22 @@ module.exports = grammar({
), ),
'}', '}',
), ),
'none',
// List of 'num',
seq('list', '<', $.type, '>'), 'str',
$.identifier,
// Exact list
seq( seq(
'[', $.identifier,
repeat( '<',
repeat1(
seq( seq(
$.type, $.type,
optional(','), optional(','),
), ),
), ),
']', '>',
), ),
seq('[', $.type, ']'),
// Function
seq( seq(
'(', '(',
repeat( repeat(
@ -469,31 +447,27 @@ module.exports = grammar({
$.struct_definition, $.struct_definition,
), ),
type_arguments: $ =>
seq(
'<',
repeat1(
seq(
$.type,
optional(','),
),
),
'>',
),
enum_definition: $ => enum_definition: $ =>
prec.right( prec.right(
seq( seq(
'enum', 'enum',
$.identifier, $.identifier,
optional($.type_arguments),
repeat( repeat(
seq( seq(
'{', '{',
repeat1( repeat1(
seq( seq(
$.identifier, $.identifier,
optional($.type_arguments), optional(
seq(
'(',
choice(
$.type,
$.type_definition,
),
')',
),
),
optional(','), optional(','),
), ),
), ),

View File

@ -1,33 +1,20 @@
(statement) @statement (expression) @expression
[
(expression)
(function_expression)
(index_expression)
] @expression
(value) @value (value) @value
(identifier) @variable (identifier) @variable
(value) @value (value) @value
(string) @string (string) @string
[ [
(integer) (integer)
(float) (float)
] @number ] @number
[
(command) (function) @function
(function)
] @function
(range) @range
(boolean) @boolean (boolean) @boolean
(list) @list (list) @list
(map) @map
(struct_definition) @struct ["," ":" ";"] @punctuation.delimiter
(enum_definition) @enum
(block) @block
["," ";"] @punctuation.delimiter
[ [
"[" "["
@ -40,32 +27,30 @@
")" ")"
] @punctuation.bracket ] @punctuation.bracket
(type) @type [
(type)
(type_specification)
] @type
(assignment_operator) @operator.assignment (assignment_operator) @operator.assignment
(logic_operator) @operator.logic (logic_operator) @operator.logic
(math_operator) @operator.math (math_operator) @operator.math
[
"async"
"else"
"else if"
"false"
"for"
"if"
"in"
"match"
"self"
"true"
"while"
"->"
"=>"
] @keyword
"as" @keyword (built_in_function) @function.builtin
"async" @keyword
"break" @keyword
"else" @keyword
"else if" @keyword
"enum" @keyword
"false" @keyword
"for" @keyword
"if" @keyword
"in" @keyword
"match" @keyword
"return" @keyword
"struct" @keyword
"true" @keyword
"while" @keyword
"->" @keyword
":" @keyword
"::" @keyword
"^" @keyword
"loop" @keyword
(function_call) @function.call (function_call) @function.call

View File

@ -93,10 +93,6 @@
"type": "SYMBOL", "type": "SYMBOL",
"name": "index_assignment" "name": "index_assignment"
}, },
{
"type": "SYMBOL",
"name": "loop_node"
},
{ {
"type": "SYMBOL", "type": "SYMBOL",
"name": "match" "name": "match"
@ -150,7 +146,7 @@
"members": [ "members": [
{ {
"type": "SYMBOL", "type": "SYMBOL",
"name": "as_node" "name": "as"
}, },
{ {
"type": "SYMBOL", "type": "SYMBOL",
@ -211,7 +207,7 @@
} }
} }
}, },
"as_node": { "as": {
"type": "SEQ", "type": "SEQ",
"members": [ "members": [
{ {
@ -297,7 +293,7 @@
}, },
"command_text": { "command_text": {
"type": "PATTERN", "type": "PATTERN",
"value": "[^\\s;]+" "value": "\\S+"
}, },
"command_argument": { "command_argument": {
"type": "CHOICE", "type": "CHOICE",
@ -982,19 +978,6 @@
] ]
} }
}, },
"loop_node": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "loop"
},
{
"type": "SYMBOL",
"name": "block"
}
]
},
"while": { "while": {
"type": "SEQ", "type": "SEQ",
"members": [ "members": [
@ -1089,43 +1072,10 @@
"type": "STRING", "type": "STRING",
"value": "int" "value": "int"
}, },
{
"type": "STRING",
"value": "list"
},
{ {
"type": "STRING", "type": "STRING",
"value": "map" "value": "map"
}, },
{
"type": "STRING",
"value": "none"
},
{
"type": "STRING",
"value": "num"
},
{
"type": "STRING",
"value": "str"
},
{
"type": "SYMBOL",
"name": "identifier"
},
{
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "identifier"
},
{
"type": "SYMBOL",
"name": "type_arguments"
}
]
},
{ {
"type": "SEQ", "type": "SEQ",
"members": [ "members": [
@ -1155,36 +1105,35 @@
} }
] ]
}, },
{
"type": "STRING",
"value": "none"
},
{
"type": "STRING",
"value": "num"
},
{
"type": "STRING",
"value": "str"
},
{
"type": "SYMBOL",
"name": "identifier"
},
{ {
"type": "SEQ", "type": "SEQ",
"members": [ "members": [
{ {
"type": "STRING", "type": "SYMBOL",
"value": "list" "name": "identifier"
}, },
{ {
"type": "STRING", "type": "STRING",
"value": "<" "value": "<"
}, },
{ {
"type": "SYMBOL", "type": "REPEAT1",
"name": "type"
},
{
"type": "STRING",
"value": ">"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "["
},
{
"type": "REPEAT",
"content": { "content": {
"type": "SEQ", "type": "SEQ",
"members": [ "members": [
@ -1207,6 +1156,23 @@
] ]
} }
}, },
{
"type": "STRING",
"value": ">"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "["
},
{
"type": "SYMBOL",
"name": "type"
},
{ {
"type": "STRING", "type": "STRING",
"value": "]" "value": "]"
@ -1420,43 +1386,6 @@
} }
] ]
}, },
"type_arguments": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "<"
},
{
"type": "REPEAT1",
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "type"
},
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "BLANK"
}
]
}
]
}
},
{
"type": "STRING",
"value": ">"
}
]
},
"enum_definition": { "enum_definition": {
"type": "PREC_RIGHT", "type": "PREC_RIGHT",
"value": 0, "value": 0,
@ -1471,18 +1400,6 @@
"type": "SYMBOL", "type": "SYMBOL",
"name": "identifier" "name": "identifier"
}, },
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "type_arguments"
},
{
"type": "BLANK"
}
]
},
{ {
"type": "REPEAT", "type": "REPEAT",
"content": { "content": {
@ -1505,8 +1422,30 @@
"type": "CHOICE", "type": "CHOICE",
"members": [ "members": [
{ {
"type": "SYMBOL", "type": "SEQ",
"name": "type_arguments" "members": [
{
"type": "STRING",
"value": "("
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "type"
},
{
"type": "SYMBOL",
"name": "type_definition"
}
]
},
{
"type": "STRING",
"value": ")"
}
]
}, },
{ {
"type": "BLANK" "type": "BLANK"

View File

@ -1,6 +1,6 @@
[ [
{ {
"type": "as_node", "type": "as",
"named": true, "named": true,
"fields": {}, "fields": {},
"children": { "children": {
@ -141,7 +141,11 @@
"named": true "named": true
}, },
{ {
"type": "type_arguments", "type": "type",
"named": true
},
{
"type": "type_definition",
"named": true "named": true
} }
] ]
@ -190,7 +194,7 @@
"required": true, "required": true,
"types": [ "types": [
{ {
"type": "as_node", "type": "as",
"named": true "named": true
}, },
{ {
@ -471,21 +475,6 @@
"named": true, "named": true,
"fields": {} "fields": {}
}, },
{
"type": "loop_node",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "block",
"named": true
}
]
}
},
{ {
"type": "map", "type": "map",
"named": true, "named": true,
@ -660,10 +649,6 @@
"type": "index_assignment", "type": "index_assignment",
"named": true "named": true
}, },
{
"type": "loop_node",
"named": true
},
{ {
"type": "match", "type": "match",
"named": true "named": true
@ -746,10 +731,6 @@
"type": "type", "type": "type",
"named": true "named": true
}, },
{
"type": "type_arguments",
"named": true
},
{ {
"type": "type_specification", "type": "type_specification",
"named": true "named": true
@ -757,21 +738,6 @@
] ]
} }
}, },
{
"type": "type_arguments",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "type",
"named": true
}
]
}
},
{ {
"type": "type_definition", "type": "type_definition",
"named": true, "named": true,
@ -1064,14 +1030,6 @@
"type": "integer", "type": "integer",
"named": true "named": true
}, },
{
"type": "list",
"named": false
},
{
"type": "loop",
"named": false
},
{ {
"type": "map", "type": "map",
"named": false "named": false

File diff suppressed because it is too large Load Diff