Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
b7d152be91 |
1805
Cargo.lock
generated
1805
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
52
Cargo.toml
52
Cargo.toml
@ -1,16 +1,50 @@
|
|||||||
[workspace]
|
[package]
|
||||||
members = ["dust-lang", "dust-shell"]
|
name = "dust-lang"
|
||||||
default-members = ["dust-lang"]
|
description = "General purpose programming language"
|
||||||
resolver = "2"
|
version = "0.4.1"
|
||||||
|
repository = "https://git.jeffa.io/jeff/dust.git"
|
||||||
[workspace.package]
|
|
||||||
authors = ["Jeff Anderson"]
|
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
readme = "README.md"
|
authors = ["Jeff Anderson"]
|
||||||
repository = "https://git.jeffa.io/jeff/dust.git"
|
default-run = "dust"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "dust"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 1
|
opt-level = 1
|
||||||
[profile.dev.package."*"]
|
[profile.dev.package."*"]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4.4.4", features = ["derive"] }
|
||||||
|
csv = "1.2.2"
|
||||||
|
libc = "0.2.148"
|
||||||
|
log = "0.4.20"
|
||||||
|
rand = "0.8.5"
|
||||||
|
rayon = "1.8.0"
|
||||||
|
reqwest = { version = "0.11.20", features = ["blocking", "json"] }
|
||||||
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
|
serde_json = "1.0.107"
|
||||||
|
toml = "0.8.1"
|
||||||
|
tree-sitter = "0.20.10"
|
||||||
|
enum-iterator = "1.4.1"
|
||||||
|
env_logger = "0.10"
|
||||||
|
reedline = { version = "0.28.0", features = ["clipboard", "sqlite"] }
|
||||||
|
crossterm = "0.27.0"
|
||||||
|
nu-ansi-term = "0.49.0"
|
||||||
|
humantime = "2.1.0"
|
||||||
|
stanza = "0.5.1"
|
||||||
|
colored = "2.1.0"
|
||||||
|
lyneate = "0.2.1"
|
||||||
|
|
||||||
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
|
env_logger = "0.10"
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
getrandom = { version = "0.2", features = ["js"] }
|
||||||
|
wasm-bindgen-futures = "0.4"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
cc = "1.0"
|
||||||
|
75
README.md
75
README.md
@ -1,50 +1,59 @@
|
|||||||
# Dust
|
# Dust
|
||||||
|
|
||||||
Dust is a high-level interpreted programming language with static types that focuses on ease of use,
|
High-level programming language with effortless concurrency, automatic memory management, type safety and strict error handling.
|
||||||
performance and correctness.
|
|
||||||
|
|
||||||
## Implementation
|
![Dust version of an example from The Rust Programming Language.](https://git.jeffa.io/jeff/dust/docs/assets/example_0.png)
|
||||||
|
|
||||||
Dust is implemented in Rust and is divided into several parts, primarily the lexer, compiler, and
|
<!--toc:start-->
|
||||||
virtual machine. All of Dust's components are designed with performance in mind and the codebase
|
- [Dust](#dust)
|
||||||
uses as few dependencies as possible.
|
- [Easy to Read and Write](#easy-to-read-and-write)
|
||||||
|
- [Effortless Concurrency](#effortless-concurrency)
|
||||||
|
- [Helpful Errors](#helpful-errors)
|
||||||
|
- [Debugging](#debugging)
|
||||||
|
- [Automatic Memory Management](#automatic-memory-management)
|
||||||
|
- [Installation and Usage](#installation-and-usage)
|
||||||
|
<!--toc:end-->
|
||||||
|
|
||||||
### Lexer
|
## Easy to Read and Write
|
||||||
|
|
||||||
The lexer emits tokens from the source code. Dust makes extensive use of Rust's zero-copy
|
Dust has simple, easy-to-learn syntax.
|
||||||
capabilities to avoid unnecessary allocations when creating tokens. A token, depending on its type,
|
|
||||||
may contain a reference to some data from the source code. The data is only copied in the case of an
|
|
||||||
error, because it improves the usability of the codebase for errors to own their data when possible.
|
|
||||||
In a successfully executed program, no part of the source code is copied unless it is a string
|
|
||||||
literal or identifier.
|
|
||||||
|
|
||||||
### Compiler
|
```js
|
||||||
|
output('Hello world!')
|
||||||
|
```
|
||||||
|
|
||||||
The compiler creates a chunk, which contains all of the data needed by the virtual machine to run a
|
## Effortless Concurrency
|
||||||
Dust program. It does so by emitting bytecode instructions, constants and locals while parsing the
|
|
||||||
tokens, which are generated one at a time by the lexer.
|
|
||||||
|
|
||||||
#### Parsing
|
Write multi-threaded code as easily as you would write code for a single thread.
|
||||||
|
|
||||||
Dust's compiler uses a custom Pratt parser, a kind of recursive descent parser, to translate a
|
```js
|
||||||
sequence of tokens into a chunk.
|
async {
|
||||||
|
output('Will this one print first?')
|
||||||
|
output('Or will this one?')
|
||||||
|
output('Who knows! Each "output" will run in its own thread!')
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### Optimizing
|
## Helpful Errors
|
||||||
|
|
||||||
When generating instructions for a register-based virtual machine, there are opportunities to
|
Dust shows you exactly where your code went wrong and suggests changes.
|
||||||
optimize the generated code, usually by consolidating register use or reusing registers within an
|
|
||||||
expression. While it is best to output optimal code in the first place, it is not always possible.
|
|
||||||
Dust's compiler has a simple peephole optimizer that can be used to modify isolated sections of the
|
|
||||||
instruction list through a mutable reference.
|
|
||||||
|
|
||||||
### Instructions
|
![Example of syntax error output.](https://git.jeffa.io/jeff/dust/docs/assets/syntax_error.png)
|
||||||
|
|
||||||
### Virtual Machine
|
## Static analysis
|
||||||
|
|
||||||
## Previous Implementations
|
Your code is always validated for safety before it is run. Other interpreted languages can fail halfway through, but Dust is able to avoid runtime errors by analyzing the program *before* it is run
|
||||||
|
|
||||||
## Inspiration
|
![Example of type error output.](https://git.jeffa.io/jeff/dust/docs/assets/type_error.png)
|
||||||
|
|
||||||
- [The Implementation of Lua 5.0](https://www.lua.org/doc/jucs05.pdf)
|
## Debugging
|
||||||
- [A No-Frills Introduction to Lua 5.1 VM Instructions](https://www.mcours.net/cours/pdf/hasclic3/hasssclic818.pdf)
|
|
||||||
- [Crafting Interpreters](https://craftinginterpreters.com/)
|
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.
|
||||||
|
|
||||||
|
![Example of debug output.](https://git.jeffa.io/jeff/dust/docs/assets/debugging.png)
|
||||||
|
|
||||||
|
## Automatic Memory Management
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
## Installation and Usage
|
||||||
|
17
build.rs
Normal file
17
build.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
fn main() {
|
||||||
|
let src_dir = std::path::Path::new("tree-sitter-dust/src");
|
||||||
|
let mut c_config = cc::Build::new();
|
||||||
|
|
||||||
|
c_config.include(src_dir);
|
||||||
|
c_config
|
||||||
|
.flag_if_supported("-Wno-unused-parameter")
|
||||||
|
.flag_if_supported("-Wno-unused-but-set-variable")
|
||||||
|
.flag_if_supported("-Wno-trigraphs");
|
||||||
|
|
||||||
|
let parser_path = src_dir.join("parser.c");
|
||||||
|
|
||||||
|
c_config.file(&parser_path);
|
||||||
|
c_config.compile("parser");
|
||||||
|
|
||||||
|
println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap());
|
||||||
|
}
|
BIN
docs/assets/debugging.png
Normal file
BIN
docs/assets/debugging.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
BIN
docs/assets/example_0.png
Normal file
BIN
docs/assets/example_0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
BIN
docs/assets/syntax_error.png
Normal file
BIN
docs/assets/syntax_error.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
BIN
docs/assets/type_error.png
Normal file
BIN
docs/assets/type_error.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
515
docs/language.md
Normal file
515
docs/language.md
Normal file
@ -0,0 +1,515 @@
|
|||||||
|
# Dust Language Reference
|
||||||
|
|
||||||
|
!!! This is a **work in progress** and has incomplete information. !!!
|
||||||
|
|
||||||
|
This is an in-depth description of the syntax and abstractions used by the Dust language. It is not
|
||||||
|
necessary to read or understand all of it before you start using Dust. Instead, refer to it when
|
||||||
|
you need help with the syntax or understanding how the code is run.
|
||||||
|
|
||||||
|
Each section of this document corresponds to a node in the concrete syntax tree. Creating this tree
|
||||||
|
is the first step in interpreting Dust code. Second, the syntax tree is traversed and an abstract
|
||||||
|
tree is generated. Each node in the syntax tree corresponds to a node in the abstract tree. Third,
|
||||||
|
the abstract tree is verified to ensure that it will not generate any values that violate the type
|
||||||
|
restrictions. Finally, the abstract tree is run, beginning at the [root](#root).
|
||||||
|
|
||||||
|
You may reference the [grammar file](tree-sitter-dust/grammar.js) and the [Tree Sitter docs]
|
||||||
|
(https://tree-sitter.github.io/) while reading this guide to understand how the language is parsed.
|
||||||
|
|
||||||
|
<!--toc:start-->
|
||||||
|
- [Dust Language Reference](#dust-language-reference)
|
||||||
|
- [Root](#root)
|
||||||
|
- [Values](#values)
|
||||||
|
- [Boolean](#boolean)
|
||||||
|
- [Integer](#integer)
|
||||||
|
- [Float](#float)
|
||||||
|
- [Range](#range)
|
||||||
|
- [String](#string)
|
||||||
|
- [List](#list)
|
||||||
|
- [Map](#map)
|
||||||
|
- [Function](#function)
|
||||||
|
- [Option](#option)
|
||||||
|
- [Structure](#structure)
|
||||||
|
- [Types](#types)
|
||||||
|
- [Basic Types](#basic-types)
|
||||||
|
- [Number](#number)
|
||||||
|
- [Any](#any)
|
||||||
|
- [None](#none)
|
||||||
|
- [List Type](#list-type)
|
||||||
|
- [Map Type](#map-type)
|
||||||
|
- [Iter](#iter)
|
||||||
|
- [Function Type](#function-type)
|
||||||
|
- [Option Type](#option-type)
|
||||||
|
- [Custom Types](#custom-types)
|
||||||
|
- [Statements](#statements)
|
||||||
|
- [Assignment](#assignment)
|
||||||
|
- [Blocks](#blocks)
|
||||||
|
- [Synchronous Blocks](#synchronous-blocks)
|
||||||
|
- [Asynchronous Blocks](#asynchronous-blocks)
|
||||||
|
- [Break](#break)
|
||||||
|
- [For Loop](#for-loop)
|
||||||
|
- [While Loop](#while-loop)
|
||||||
|
- [If/Else](#ifelse)
|
||||||
|
- [Match](#match)
|
||||||
|
- [Pipe](#pipe)
|
||||||
|
- [Expression](#expression)
|
||||||
|
- [Expressions](#expressions)
|
||||||
|
- [Identifier](#identifier)
|
||||||
|
- [Index](#index)
|
||||||
|
- [Logic](#logic)
|
||||||
|
- [Math](#math)
|
||||||
|
- [Value](#value)
|
||||||
|
- [New](#new)
|
||||||
|
- [Command](#command)
|
||||||
|
- [Built-In Values](#built-in-values)
|
||||||
|
- [Comments](#comments)
|
||||||
|
<!--toc:end-->
|
||||||
|
|
||||||
|
## Root
|
||||||
|
|
||||||
|
The root node represents all of the source code. It is a sequence of [statements](#statements) that
|
||||||
|
are executed synchronously, in order. The output of the program is always the result of the final
|
||||||
|
statement or the first error encountered.
|
||||||
|
|
||||||
|
## Values
|
||||||
|
|
||||||
|
There are ten kinds of value in Dust. Some are very simple and are parsed directly from the source
|
||||||
|
code, some are collections and others are used in special ways, like functions and structures. All
|
||||||
|
values can be assinged to an [identifier](#identifiers).
|
||||||
|
|
||||||
|
Dust does not have a null type. Absent values are represented with the `none` value, which is a
|
||||||
|
kind of [option](#option). You may not create a variable without a value and no variable can ever
|
||||||
|
be in an 'undefined' state during execution.
|
||||||
|
|
||||||
|
### Boolean
|
||||||
|
|
||||||
|
Booleans are true or false. They are represented by the literal tokens `true` and `false`.
|
||||||
|
|
||||||
|
### Integer
|
||||||
|
|
||||||
|
Integers are whole numbers that may be positive, negative or zero. Internally, an integer is a
|
||||||
|
signed 64-bit value.
|
||||||
|
|
||||||
|
```dust
|
||||||
|
42
|
||||||
|
```
|
||||||
|
|
||||||
|
Integers always **overflow** when their maximum or minimum value is reached. Overflowing means that
|
||||||
|
if the value is too high or low for the 64-bit integer, it will wrap around. You can use the built-
|
||||||
|
in values `int:max` and `int:min` to get the highest and lowest possible values.
|
||||||
|
|
||||||
|
```dust
|
||||||
|
assert_equal(int:max + 1, int:min)
|
||||||
|
assert_equal(int:min - 1, int:max)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Float
|
||||||
|
|
||||||
|
A float is a numeric value with a decimal. Floats are 64-bit and, like integers, will **overflow**
|
||||||
|
at their bounds.
|
||||||
|
|
||||||
|
```dust
|
||||||
|
42.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Range
|
||||||
|
|
||||||
|
A range represents a contiguous sequence of integers. Dust ranges are **inclusive** so both the high
|
||||||
|
and low bounds will be represented.
|
||||||
|
|
||||||
|
```dust
|
||||||
|
0..100
|
||||||
|
```
|
||||||
|
|
||||||
|
### String
|
||||||
|
|
||||||
|
A string is a **utf-8** sequence used to represent text. Strings can be wrapped in single or double quotes as well as backticks.
|
||||||
|
|
||||||
|
```dust
|
||||||
|
'42'
|
||||||
|
"42"
|
||||||
|
`42`
|
||||||
|
'forty-two'
|
||||||
|
```
|
||||||
|
|
||||||
|
### List
|
||||||
|
|
||||||
|
A list is **collection** of values stored as a sequence and accessible by [indexing](#index) their position with an integer. Lists indexes begin at zero for the first item.
|
||||||
|
|
||||||
|
```dust
|
||||||
|
[ 42 'forty-two' ]
|
||||||
|
[ 123, 'one', 'two', 'three' ]
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the commas are optional, including trailing commas.
|
||||||
|
|
||||||
|
```dust
|
||||||
|
[1 2 3 4 5]:2
|
||||||
|
# Output: 3
|
||||||
|
```
|
||||||
|
|
||||||
|
### Map
|
||||||
|
|
||||||
|
Maps are flexible collections with arbitrary **key-value pairs**, similar to JSON objects. A map is
|
||||||
|
created with a pair of curly braces and its entries are variables declared inside those braces. Map
|
||||||
|
contents can be accessed using a colon `:`. Commas may optionally be included after the key-value
|
||||||
|
pairs.
|
||||||
|
|
||||||
|
```dust
|
||||||
|
reminder = {
|
||||||
|
message = "Buy milk"
|
||||||
|
tags = ["groceries", "home"]
|
||||||
|
}
|
||||||
|
|
||||||
|
reminder:message
|
||||||
|
# Output: Buy milk
|
||||||
|
```
|
||||||
|
|
||||||
|
Internally a map is represented by a B-tree. The implicit advantage of using a B-tree instead of a
|
||||||
|
hash map is that a B-tree is sorted and therefore can be easily compared to another. Maps are also
|
||||||
|
used by the interpreter as the data structure for holding variables. You can even inspect the active
|
||||||
|
**execution context** by calling the built-in `context()` function.
|
||||||
|
|
||||||
|
The map stores each [identifier](#identifiers)'s key with a value and the value's type. For internal
|
||||||
|
use by the interpreter, a type can be set to a key without a value. This makes it possible to check
|
||||||
|
the types of values before they are computed.
|
||||||
|
|
||||||
|
### Function
|
||||||
|
|
||||||
|
A function encapsulates a section of the abstract tree so that it can be run seperately and with
|
||||||
|
different arguments. The function body is a [block](#block), so adding `async` will cause the body
|
||||||
|
to run like any other `async` block. Unlike some languages, there are no concepts like futures or
|
||||||
|
async functions in Dust.
|
||||||
|
|
||||||
|
Functions are **first-class values** in Dust, so they can be assigned to variables like any other
|
||||||
|
value.
|
||||||
|
|
||||||
|
```dust
|
||||||
|
# This simple function has no arguments and no return value.
|
||||||
|
say_hi = () <none> {
|
||||||
|
output("hi") # The "output" function is a built-in that prints to stdout.
|
||||||
|
}
|
||||||
|
|
||||||
|
# This function has one argument and will return a value.
|
||||||
|
add_one = (number <num>) <num> {
|
||||||
|
number + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
say_hi()
|
||||||
|
assert_equal(add_one(3), 4)
|
||||||
|
```
|
||||||
|
|
||||||
|
Functions can also be **anonymous**. This is useful for using **callbacks** (i.e. functions that are
|
||||||
|
called by another function).
|
||||||
|
|
||||||
|
```dust
|
||||||
|
# Use a callback to retain only the numeric characters in a string.
|
||||||
|
str:retain(
|
||||||
|
'a1b2c3'
|
||||||
|
(char <str>) <bool> {
|
||||||
|
is_some(int:parse(char))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option
|
||||||
|
|
||||||
|
An option represents a value that may not be present. It has two variants: **some** and **none**.
|
||||||
|
|
||||||
|
```dust
|
||||||
|
say_something = (message <option(str)>) <str> {
|
||||||
|
either_or(message, "hiya")
|
||||||
|
}
|
||||||
|
|
||||||
|
say_something(some("goodbye"))
|
||||||
|
# goodbye
|
||||||
|
|
||||||
|
say_something(none)
|
||||||
|
# hiya
|
||||||
|
```
|
||||||
|
|
||||||
|
Dust includes built-in functions to work with option values: `is_none`, `is_some` and `either_or`.
|
||||||
|
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
A structure is a **concrete type value**. It is a value, like any other, and can be [assigned]
|
||||||
|
(#assignment) to an [identifier](#identifier). It can then be instantiated as a [map](#map) that
|
||||||
|
will only allow the variables present in the structure. Default values may be provided for each
|
||||||
|
variable in the structure, which will be propagated to the map it creates. Values without defaults
|
||||||
|
must be given a value during instantiation.
|
||||||
|
|
||||||
|
```dust
|
||||||
|
struct User {
|
||||||
|
name <str>
|
||||||
|
email <str>
|
||||||
|
id <int> = generate_id()
|
||||||
|
}
|
||||||
|
|
||||||
|
bob = new User {
|
||||||
|
name = "Bob"
|
||||||
|
email = "bob@example.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
# The variable "bob" is a structured map.
|
||||||
|
```
|
||||||
|
|
||||||
|
A map created by using [new](#new) is called a **structured map**. In other languages it may be
|
||||||
|
called a "homomorphic mapped type". Dust will generate errors if you try to set any values on the
|
||||||
|
structured map that are not allowed by the structure.
|
||||||
|
|
||||||
|
## Types
|
||||||
|
|
||||||
|
Dust enforces strict type checking. To make the language easier to write, **type inference** is used
|
||||||
|
to allow variables to be declared without specifying the type. Instead, the interpreter will figure
|
||||||
|
it out and set the strictest type possible.
|
||||||
|
|
||||||
|
To make the type-setting syntax easier to distinguish from the rest of your code, a **type
|
||||||
|
specification** is wrapped in pointed brackets. So variable assignment using types looks like this:
|
||||||
|
|
||||||
|
```dust
|
||||||
|
my_float <float> = 666.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Basic Types
|
||||||
|
|
||||||
|
The simple types, and their notation are:
|
||||||
|
|
||||||
|
- boolean `bool`
|
||||||
|
- integer `int`
|
||||||
|
- float `float`
|
||||||
|
- string `str`
|
||||||
|
|
||||||
|
### Number
|
||||||
|
|
||||||
|
The `num` type may represent a value of type `int` or `float`.
|
||||||
|
|
||||||
|
### Any
|
||||||
|
|
||||||
|
The `any` type does not enforce type bounds.
|
||||||
|
|
||||||
|
### None
|
||||||
|
|
||||||
|
The `none` type indicates that no value should be found after executing the statement or block, with
|
||||||
|
one expection: the `none` variant of the `option` type.
|
||||||
|
|
||||||
|
### List Type
|
||||||
|
|
||||||
|
A list's contents can be specified to create type-safe lists. The `list(str)` type would only allow
|
||||||
|
string values. Writing `list` without the parentheses and content type is equivalent to writing
|
||||||
|
`list(any)`.
|
||||||
|
|
||||||
|
### Map Type
|
||||||
|
|
||||||
|
The `map` type is unstructured and can hold any key-value pair.
|
||||||
|
|
||||||
|
### Iter
|
||||||
|
|
||||||
|
The `iter` type refers to types that can be used with a [for loop](#for-loop). These include `list`,
|
||||||
|
`range`, `string` and `map`.
|
||||||
|
|
||||||
|
### Function Type
|
||||||
|
|
||||||
|
A function's type specification is more complex than other types. A function value must always have
|
||||||
|
its arguments and return type specified when the **function value** is created.
|
||||||
|
|
||||||
|
```dust
|
||||||
|
my_function = (number <int>, text <str>) <none> {
|
||||||
|
output(number)
|
||||||
|
output(text)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
But what if we need to specify a **function type** without creating the function value? This is
|
||||||
|
necessary when using callbacks or defining structures that have functions set at instantiation.
|
||||||
|
|
||||||
|
```dust
|
||||||
|
use_adder = (adder <(int) -> int>, number <int>) -> <int> {
|
||||||
|
adder(number)
|
||||||
|
}
|
||||||
|
|
||||||
|
use_adder(
|
||||||
|
(i <int>) <int> { i + 2 }
|
||||||
|
40
|
||||||
|
)
|
||||||
|
|
||||||
|
# Output: 42
|
||||||
|
```
|
||||||
|
|
||||||
|
```dust
|
||||||
|
struct Message {
|
||||||
|
send_n_times <(str, int) -> none>
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout_message = new Message {
|
||||||
|
send_n_times = (content <str>, n <int>) <none> {
|
||||||
|
for _ in 0..n {
|
||||||
|
output(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option Type
|
||||||
|
|
||||||
|
The `option(type)` type is expected to be either `some(value)` or `none`. The type of the value
|
||||||
|
inside the `some` is always specified.
|
||||||
|
|
||||||
|
```dust
|
||||||
|
result <option(str)> = none
|
||||||
|
|
||||||
|
for file in fs:read_dir("./") {
|
||||||
|
if file:size > 100 {
|
||||||
|
result = some(file:path)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output(result)
|
||||||
|
```
|
||||||
|
|
||||||
|
```dust
|
||||||
|
get_line_break_index(text <str>) <some(int)> {
|
||||||
|
str:find(text, '\n')
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Types
|
||||||
|
|
||||||
|
Custom types such as **structures** are referenced by their variable identifier.
|
||||||
|
|
||||||
|
```dust
|
||||||
|
File = struct {
|
||||||
|
path <str>
|
||||||
|
size <int>
|
||||||
|
type <str>
|
||||||
|
}
|
||||||
|
|
||||||
|
print_file_info(file <File>) <none> {
|
||||||
|
info = file:path
|
||||||
|
+ '\n'
|
||||||
|
+ file:size
|
||||||
|
+ '\n'
|
||||||
|
+ file:type
|
||||||
|
|
||||||
|
output(info)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Statements
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
### Assignment
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
### Blocks
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
#### Synchronous Blocks
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
#### Asynchronous Blocks
|
||||||
|
|
||||||
|
```dust
|
||||||
|
# An async block will run each statement in its own thread.
|
||||||
|
async {
|
||||||
|
output(random_integer())
|
||||||
|
output(random_float())
|
||||||
|
output(random_boolean())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```dust
|
||||||
|
data = async {
|
||||||
|
output("Reading a file...")
|
||||||
|
read("examples/assets/faithful.csv")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Break
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
### For Loop
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
```dust
|
||||||
|
list = [ 1, 2, 3 ]
|
||||||
|
|
||||||
|
for number in list {
|
||||||
|
output(number + 1)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### While Loop
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
A **while** loop continues until a predicate is false.
|
||||||
|
|
||||||
|
```dust
|
||||||
|
i = 0
|
||||||
|
while i < 10 {
|
||||||
|
output(i)
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### If/Else
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
### Match
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
### Pipe
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
### Expression
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
## Expressions
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
#### Identifier
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
#### Index
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
#### Logic
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
#### Math
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
#### Value
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
#### New
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
#### Command
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
## Built-In Values
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
## Comments
|
||||||
|
|
||||||
|
TODO
|
@ -1,23 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "dust-lang"
|
|
||||||
description = "Interpreter library for the Dust programming language"
|
|
||||||
version = "0.5.0"
|
|
||||||
authors.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
readme.workspace = true
|
|
||||||
repository.workspace = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
annotate-snippets = "0.11.4"
|
|
||||||
colored = "2.1.0"
|
|
||||||
log = "0.4.22"
|
|
||||||
rand = "0.8.5"
|
|
||||||
serde = { version = "1.0.203", features = ["derive"] }
|
|
||||||
serde_json = "1.0.117"
|
|
||||||
getrandom = { version = "0.2", features = [
|
|
||||||
"js",
|
|
||||||
] } # Indirect dependency, for wasm builds
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
env_logger = "0.11.5"
|
|
@ -1,402 +0,0 @@
|
|||||||
//! In-memory representation of a Dust program or function.
|
|
||||||
//!
|
|
||||||
//! A chunk consists of a sequence of instructions and their positions, a list of constants, and a
|
|
||||||
//! list of locals that can be executed by the Dust virtual machine. Chunks have a name when they
|
|
||||||
//! belong to a named function.
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
cmp::Ordering,
|
|
||||||
fmt::{self, Debug, Display, Formatter},
|
|
||||||
};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::{Disassembler, Instruction, Operation, Span, Type, Value};
|
|
||||||
|
|
||||||
/// In-memory representation of a Dust program or function.
|
|
||||||
///
|
|
||||||
/// See the [module-level documentation](index.html) for more information.
|
|
||||||
#[derive(Clone, PartialOrd, Ord, Serialize, Deserialize)]
|
|
||||||
pub struct Chunk {
|
|
||||||
name: Option<String>,
|
|
||||||
pub is_poisoned: bool,
|
|
||||||
|
|
||||||
instructions: Vec<(Instruction, Span)>,
|
|
||||||
constants: Vec<Value>,
|
|
||||||
locals: Vec<Local>,
|
|
||||||
|
|
||||||
current_scope: Scope,
|
|
||||||
block_index: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Chunk {
|
|
||||||
pub fn new(name: Option<String>) -> Self {
|
|
||||||
Self {
|
|
||||||
name,
|
|
||||||
is_poisoned: false,
|
|
||||||
instructions: Vec::new(),
|
|
||||||
constants: Vec::new(),
|
|
||||||
locals: Vec::new(),
|
|
||||||
current_scope: Scope::default(),
|
|
||||||
block_index: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_data(
|
|
||||||
name: Option<String>,
|
|
||||||
instructions: Vec<(Instruction, Span)>,
|
|
||||||
constants: Vec<Value>,
|
|
||||||
locals: Vec<Local>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
name,
|
|
||||||
is_poisoned: false,
|
|
||||||
instructions,
|
|
||||||
constants,
|
|
||||||
locals,
|
|
||||||
current_scope: Scope::default(),
|
|
||||||
block_index: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn name(&self) -> Option<&String> {
|
|
||||||
self.name.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_name(&mut self, name: String) {
|
|
||||||
self.name = Some(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.instructions.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.instructions.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn constants(&self) -> &Vec<Value> {
|
|
||||||
&self.constants
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn constants_mut(&mut self) -> &mut Vec<Value> {
|
|
||||||
&mut self.constants
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn take_constants(self) -> Vec<Value> {
|
|
||||||
self.constants
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn instructions(&self) -> &Vec<(Instruction, Span)> {
|
|
||||||
&self.instructions
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn instructions_mut(&mut self) -> &mut Vec<(Instruction, Span)> {
|
|
||||||
&mut self.instructions
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_instruction(&self, index: usize) -> Result<&(Instruction, Span), ChunkError> {
|
|
||||||
self.instructions
|
|
||||||
.get(index)
|
|
||||||
.ok_or(ChunkError::InstructionIndexOutOfBounds { index })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn locals(&self) -> &Vec<Local> {
|
|
||||||
&self.locals
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn locals_mut(&mut self) -> &mut Vec<Local> {
|
|
||||||
&mut self.locals
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_local(&self, index: u8) -> Result<&Local, ChunkError> {
|
|
||||||
self.locals
|
|
||||||
.get(index as usize)
|
|
||||||
.ok_or(ChunkError::LocalIndexOutOfBounds {
|
|
||||||
index: index as usize,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_local_mut(&mut self, index: u8) -> Result<&mut Local, ChunkError> {
|
|
||||||
self.locals
|
|
||||||
.get_mut(index as usize)
|
|
||||||
.ok_or(ChunkError::LocalIndexOutOfBounds {
|
|
||||||
index: index as usize,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn current_scope(&self) -> Scope {
|
|
||||||
self.current_scope
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_constant(&self, index: u8) -> Result<&Value, ChunkError> {
|
|
||||||
self.constants
|
|
||||||
.get(index as usize)
|
|
||||||
.ok_or(ChunkError::ConstantIndexOutOfBounds {
|
|
||||||
index: index as usize,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_or_get_constant(&mut self, value: Value) -> u8 {
|
|
||||||
if let Some(index) = self
|
|
||||||
.constants
|
|
||||||
.iter()
|
|
||||||
.position(|constant| constant == &value)
|
|
||||||
{
|
|
||||||
return index as u8;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.constants.push(value);
|
|
||||||
|
|
||||||
(self.constants.len() - 1) as u8
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_identifier(&self, local_index: u8) -> Option<String> {
|
|
||||||
self.locals.get(local_index as usize).and_then(|local| {
|
|
||||||
self.constants
|
|
||||||
.get(local.identifier_index as usize)
|
|
||||||
.map(|value| value.to_string())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn begin_scope(&mut self) {
|
|
||||||
self.block_index += 1;
|
|
||||||
self.current_scope.block_index = self.block_index;
|
|
||||||
self.current_scope.depth += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn end_scope(&mut self) {
|
|
||||||
self.current_scope.depth -= 1;
|
|
||||||
|
|
||||||
if self.current_scope.depth == 0 {
|
|
||||||
self.current_scope.block_index = 0;
|
|
||||||
} else {
|
|
||||||
self.current_scope.block_index -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn expect_not_poisoned(&self) -> Result<(), ChunkError> {
|
|
||||||
if self.is_poisoned {
|
|
||||||
Err(ChunkError::PoisonedChunk)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_constant_type(&self, constant_index: u8) -> Option<Type> {
|
|
||||||
self.constants
|
|
||||||
.get(constant_index as usize)
|
|
||||||
.map(|value| value.r#type())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_local_type(&self, local_index: u8) -> Option<Type> {
|
|
||||||
self.locals.get(local_index as usize)?.r#type.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_register_type(&self, register_index: u8) -> Option<Type> {
|
|
||||||
let local_type_option = self
|
|
||||||
.locals
|
|
||||||
.iter()
|
|
||||||
.find(|local| local.register_index == register_index)
|
|
||||||
.map(|local| local.r#type.clone());
|
|
||||||
|
|
||||||
if let Some(local_type) = local_type_option {
|
|
||||||
return local_type;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.instructions
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.find_map(|(index, (instruction, _))| {
|
|
||||||
if let Operation::LoadList = instruction.operation() {
|
|
||||||
if instruction.a() == register_index {
|
|
||||||
let mut length = (instruction.c() - instruction.b() + 1) as usize;
|
|
||||||
let mut item_type = Type::Any;
|
|
||||||
let distance_to_end = self.len() - index;
|
|
||||||
|
|
||||||
for (instruction, _) in self
|
|
||||||
.instructions()
|
|
||||||
.iter()
|
|
||||||
.rev()
|
|
||||||
.skip(distance_to_end)
|
|
||||||
.take(length)
|
|
||||||
{
|
|
||||||
if let Operation::Close = instruction.operation() {
|
|
||||||
length -= (instruction.c() - instruction.b()) as usize;
|
|
||||||
} else if let Type::Any = item_type {
|
|
||||||
item_type = instruction.yielded_type(self).unwrap_or(Type::Any);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Some(Type::List {
|
|
||||||
item_type: Box::new(item_type),
|
|
||||||
length,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if instruction.yields_value() && instruction.a() == register_index {
|
|
||||||
instruction.yielded_type(self)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn return_type(&self) -> Option<Type> {
|
|
||||||
let returns_value = self
|
|
||||||
.instructions()
|
|
||||||
.last()
|
|
||||||
.map(|(instruction, _)| {
|
|
||||||
debug_assert!(matches!(instruction.operation(), Operation::Return));
|
|
||||||
|
|
||||||
instruction.b_as_boolean()
|
|
||||||
})
|
|
||||||
.unwrap_or(false);
|
|
||||||
|
|
||||||
if returns_value {
|
|
||||||
self.instructions.iter().rev().find_map(|(instruction, _)| {
|
|
||||||
if instruction.yields_value() {
|
|
||||||
instruction.yielded_type(self)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disassembler(&self) -> Disassembler {
|
|
||||||
Disassembler::new(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Chunk {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
let disassembler = self.disassembler().styled(false);
|
|
||||||
|
|
||||||
write!(f, "{}", disassembler.disassemble())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for Chunk {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
let disassembly = self.disassembler().styled(false).disassemble();
|
|
||||||
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
write!(f, "\n{}", disassembly)
|
|
||||||
} else {
|
|
||||||
write!(f, "{}", disassembly)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for Chunk {}
|
|
||||||
|
|
||||||
impl PartialEq for Chunk {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.instructions == other.instructions
|
|
||||||
&& self.constants == other.constants
|
|
||||||
&& self.locals == other.locals
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A scoped variable.
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
|
||||||
pub struct Local {
|
|
||||||
/// The index of the identifier in the constants table.
|
|
||||||
pub identifier_index: u8,
|
|
||||||
|
|
||||||
/// The expected type of the local's value.
|
|
||||||
pub r#type: Option<Type>,
|
|
||||||
|
|
||||||
/// Whether the local is mutable.
|
|
||||||
pub is_mutable: bool,
|
|
||||||
|
|
||||||
/// Scope where the variable was declared.
|
|
||||||
pub scope: Scope,
|
|
||||||
|
|
||||||
/// Expected location of a local's value.
|
|
||||||
pub register_index: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Local {
|
|
||||||
/// Creates a new Local instance.
|
|
||||||
pub fn new(
|
|
||||||
identifier_index: u8,
|
|
||||||
r#type: Option<Type>,
|
|
||||||
mutable: bool,
|
|
||||||
scope: Scope,
|
|
||||||
register_index: u8,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
identifier_index,
|
|
||||||
r#type,
|
|
||||||
is_mutable: mutable,
|
|
||||||
scope,
|
|
||||||
register_index,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Variable locality, as defined by its depth and block index.
|
|
||||||
///
|
|
||||||
/// The `block index` is a unique identifier for a block within a chunk. It is used to differentiate
|
|
||||||
/// between blocks that are not nested together but have the same depth, i.e. sibling scopes. If the
|
|
||||||
/// `block_index` is 0, then the scope is the root scope of the chunk. The `block_index` is always 0
|
|
||||||
/// when the `depth` is 0. See [Chunk::begin_scope][] and [Chunk::end_scope][] to see how scopes are
|
|
||||||
/// incremented and decremented.
|
|
||||||
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
|
||||||
pub struct Scope {
|
|
||||||
/// Level of block nesting.
|
|
||||||
pub depth: u8,
|
|
||||||
/// Index of the block in the chunk.
|
|
||||||
pub block_index: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scope {
|
|
||||||
pub fn new(depth: u8, block_index: u8) -> Self {
|
|
||||||
Self { depth, block_index }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn contains(&self, other: &Self) -> bool {
|
|
||||||
match self.depth.cmp(&other.depth) {
|
|
||||||
Ordering::Less => false,
|
|
||||||
Ordering::Greater => self.block_index >= other.block_index,
|
|
||||||
Ordering::Equal => self.block_index == other.block_index,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Scope {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "({}, {})", self.depth, self.block_index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Errors that can occur when using a [`Chunk`].
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum ChunkError {
|
|
||||||
ConstantIndexOutOfBounds { index: usize },
|
|
||||||
InstructionIndexOutOfBounds { index: usize },
|
|
||||||
LocalIndexOutOfBounds { index: usize },
|
|
||||||
PoisonedChunk,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for ChunkError {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
ChunkError::ConstantIndexOutOfBounds { index } => {
|
|
||||||
write!(f, "Constant index {} out of bounds", index)
|
|
||||||
}
|
|
||||||
ChunkError::InstructionIndexOutOfBounds { index } => {
|
|
||||||
write!(f, "Instruction index {} out of bounds", index)
|
|
||||||
}
|
|
||||||
ChunkError::LocalIndexOutOfBounds { index } => {
|
|
||||||
write!(f, "Local index {} out of bounds", index)
|
|
||||||
}
|
|
||||||
ChunkError::PoisonedChunk => write!(f, "Chunk is poisoned"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,366 +0,0 @@
|
|||||||
//! Tool for disassembling chunks into a human-readable format.
|
|
||||||
//!
|
|
||||||
//! A disassembler can be created by calling [Chunk::disassembler][] or by instantiating one with
|
|
||||||
//! [Disassembler::new][].
|
|
||||||
//!
|
|
||||||
//! # Options
|
|
||||||
//!
|
|
||||||
//! The disassembler can be customized with the 'styled' option, which will apply ANSI color codes
|
|
||||||
//! to the output.
|
|
||||||
//!
|
|
||||||
//! # Output
|
|
||||||
//!
|
|
||||||
//! The output of [Disassembler::disassemble] is a string that can be printed to the console or
|
|
||||||
//! written to a file. Below is an example of the disassembly for a simple "Hello, world!" program.
|
|
||||||
//!
|
|
||||||
//! ```text
|
|
||||||
//! ┌──────────────────────────────────────────────────────────────────────────────┐
|
|
||||||
//! │ <file name omitted> │
|
|
||||||
//! │ │
|
|
||||||
//! │ write_line("Hello, world!") │
|
|
||||||
//! │ │
|
|
||||||
//! │ 3 instructions, 1 constants, 0 locals, returns none │
|
|
||||||
//! │ │
|
|
||||||
//! │ Instructions │
|
|
||||||
//! │ ------------ │
|
|
||||||
//! │ i BYTECODE OPERATION INFO TYPE POSITION │
|
|
||||||
//! │--- -------- ------------- -------------------- ---------------- ------------ │
|
|
||||||
//! │ 0 03 LOAD_CONSTANT R0 = C0 str (11, 26) │
|
|
||||||
//! │ 1 1390117 CALL_NATIVE write_line(R0) (0, 27) │
|
|
||||||
//! │ 2 18 RETURN (27, 27) │
|
|
||||||
//! │┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈│
|
|
||||||
//! │ Locals │
|
|
||||||
//! │ ------ │
|
|
||||||
//! │ i IDENTIFIER TYPE MUTABLE SCOPE REGISTER │
|
|
||||||
//! │ --- ---------- ---------------- ------- ------- -------- │
|
|
||||||
//! │┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈│
|
|
||||||
//! │ Constants │
|
|
||||||
//! │ --------- │
|
|
||||||
//! │ i VALUE │
|
|
||||||
//! │ --- --------------- │
|
|
||||||
//! │ 0 Hello, world! │
|
|
||||||
//! └──────────────────────────────────────────────────────────────────────────────┘
|
|
||||||
//! ```
|
|
||||||
use std::env::current_exe;
|
|
||||||
|
|
||||||
use colored::Colorize;
|
|
||||||
|
|
||||||
use crate::{Chunk, ConcreteValue, Local, Value};
|
|
||||||
|
|
||||||
const INSTRUCTION_HEADER: [&str; 4] = [
|
|
||||||
"Instructions",
|
|
||||||
"------------",
|
|
||||||
" i BYTECODE OPERATION INFO TYPE POSITION ",
|
|
||||||
"--- -------- ------------- -------------------- ---------------- ----------",
|
|
||||||
];
|
|
||||||
|
|
||||||
const CONSTANT_HEADER: [&str; 4] = [
|
|
||||||
"Constants",
|
|
||||||
"---------",
|
|
||||||
" i VALUE ",
|
|
||||||
"--- ---------------",
|
|
||||||
];
|
|
||||||
|
|
||||||
const LOCAL_HEADER: [&str; 4] = [
|
|
||||||
"Locals",
|
|
||||||
"------",
|
|
||||||
" i IDENTIFIER TYPE MUTABLE SCOPE REGISTER",
|
|
||||||
"--- ---------- ---------------- ------- ------- --------",
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Builder that constructs a human-readable representation of a chunk.
|
|
||||||
///
|
|
||||||
/// See the [module-level documentation](index.html) for more information.
|
|
||||||
pub struct Disassembler<'a> {
|
|
||||||
output: String,
|
|
||||||
chunk: &'a Chunk,
|
|
||||||
source: Option<&'a str>,
|
|
||||||
|
|
||||||
// Options
|
|
||||||
styled: bool,
|
|
||||||
indent: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Disassembler<'a> {
|
|
||||||
pub fn new(chunk: &'a Chunk) -> Self {
|
|
||||||
Self {
|
|
||||||
output: String::new(),
|
|
||||||
chunk,
|
|
||||||
source: None,
|
|
||||||
styled: false,
|
|
||||||
indent: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The default width of the disassembly output. To correctly align the output, this should
|
|
||||||
/// return the width of the longest line that the disassembler is guaranteed to produce.
|
|
||||||
pub fn default_width() -> usize {
|
|
||||||
let longest_line = INSTRUCTION_HEADER[3];
|
|
||||||
|
|
||||||
longest_line.chars().count().max(80)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn source(mut self, source: &'a str) -> Self {
|
|
||||||
self.source = Some(source);
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn styled(mut self, styled: bool) -> Self {
|
|
||||||
self.styled = styled;
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn indent(mut self, indent: usize) -> Self {
|
|
||||||
self.indent = indent;
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push(
|
|
||||||
&mut self,
|
|
||||||
text: &str,
|
|
||||||
center: bool,
|
|
||||||
style_bold: bool,
|
|
||||||
style_dim: bool,
|
|
||||||
add_border: bool,
|
|
||||||
) {
|
|
||||||
let width = Disassembler::default_width();
|
|
||||||
let characters = text.chars().collect::<Vec<char>>();
|
|
||||||
let content_width = if add_border { width - 2 } else { width };
|
|
||||||
let (line_characters, remainder) = characters
|
|
||||||
.split_at_checked(content_width)
|
|
||||||
.unwrap_or((characters.as_slice(), &[]));
|
|
||||||
let (left_pad_length, right_pad_length) = {
|
|
||||||
let extra_space = content_width.saturating_sub(characters.len());
|
|
||||||
|
|
||||||
if center {
|
|
||||||
(extra_space / 2, extra_space / 2 + extra_space % 2)
|
|
||||||
} else {
|
|
||||||
(0, extra_space)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let content = if style_bold {
|
|
||||||
line_characters
|
|
||||||
.iter()
|
|
||||||
.collect::<String>()
|
|
||||||
.bold()
|
|
||||||
.to_string()
|
|
||||||
} else if style_dim {
|
|
||||||
line_characters
|
|
||||||
.iter()
|
|
||||||
.collect::<String>()
|
|
||||||
.dimmed()
|
|
||||||
.to_string()
|
|
||||||
} else {
|
|
||||||
line_characters.iter().collect::<String>()
|
|
||||||
};
|
|
||||||
let length_before_content = self.output.chars().count();
|
|
||||||
|
|
||||||
for _ in 0..self.indent {
|
|
||||||
self.output.push_str("│ ");
|
|
||||||
}
|
|
||||||
|
|
||||||
if add_border {
|
|
||||||
self.output.push('│');
|
|
||||||
}
|
|
||||||
|
|
||||||
self.output.push_str(&" ".repeat(left_pad_length));
|
|
||||||
self.output.push_str(&content);
|
|
||||||
self.output.push_str(&" ".repeat(right_pad_length));
|
|
||||||
|
|
||||||
let length_after_content = self.output.chars().count();
|
|
||||||
let line_length = length_after_content - length_before_content;
|
|
||||||
|
|
||||||
if line_length < content_width - 1 {
|
|
||||||
self.output
|
|
||||||
.push_str(&" ".repeat(content_width - line_length));
|
|
||||||
}
|
|
||||||
|
|
||||||
if add_border {
|
|
||||||
self.output.push('│');
|
|
||||||
}
|
|
||||||
|
|
||||||
self.output.push('\n');
|
|
||||||
|
|
||||||
if !remainder.is_empty() {
|
|
||||||
self.push(
|
|
||||||
remainder.iter().collect::<String>().as_str(),
|
|
||||||
center,
|
|
||||||
style_bold,
|
|
||||||
style_dim,
|
|
||||||
add_border,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_header(&mut self, header: &str) {
|
|
||||||
self.push(header, true, self.styled, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_details(&mut self, details: &str) {
|
|
||||||
self.push(details, true, false, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_border(&mut self, border: &str) {
|
|
||||||
self.push(border, false, false, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_empty(&mut self) {
|
|
||||||
self.push("", false, false, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disassemble(mut self) -> String {
|
|
||||||
let width = Disassembler::default_width();
|
|
||||||
let top_border = "┌".to_string() + &"─".repeat(width - 2) + "┐";
|
|
||||||
let section_border = "│".to_string() + &"┈".repeat(width - 2) + "│";
|
|
||||||
let bottom_border = "└".to_string() + &"─".repeat(width - 2) + "┘";
|
|
||||||
let name_display = self
|
|
||||||
.chunk
|
|
||||||
.name()
|
|
||||||
.map(|identifier| identifier.to_string())
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
current_exe()
|
|
||||||
.map(|path| path.to_string_lossy().to_string())
|
|
||||||
.unwrap_or("Chunk Disassembly".to_string())
|
|
||||||
});
|
|
||||||
|
|
||||||
self.push_border(&top_border);
|
|
||||||
self.push_header(&name_display);
|
|
||||||
|
|
||||||
if let Some(source) = self.source {
|
|
||||||
self.push_empty();
|
|
||||||
self.push_details(
|
|
||||||
&source
|
|
||||||
.replace(" ", "")
|
|
||||||
.replace("\n\n", " ")
|
|
||||||
.replace('\n', " "),
|
|
||||||
);
|
|
||||||
self.push_empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
let info_line = format!(
|
|
||||||
"{} instructions, {} constants, {} locals, returns {}",
|
|
||||||
self.chunk.len(),
|
|
||||||
self.chunk.constants().len(),
|
|
||||||
self.chunk.locals().len(),
|
|
||||||
self.chunk
|
|
||||||
.return_type()
|
|
||||||
.map(|r#type| r#type.to_string())
|
|
||||||
.unwrap_or("none".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
self.push(&info_line, true, false, true, true);
|
|
||||||
self.push_empty();
|
|
||||||
|
|
||||||
for line in INSTRUCTION_HEADER {
|
|
||||||
self.push_header(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (index, (instruction, position)) in self.chunk.instructions().iter().enumerate() {
|
|
||||||
let bytecode = format!("{:02X}", u32::from(instruction));
|
|
||||||
let operation = instruction.operation().to_string();
|
|
||||||
let info = instruction.disassembly_info(self.chunk);
|
|
||||||
let type_display = instruction
|
|
||||||
.yielded_type(self.chunk)
|
|
||||||
.map(|r#type| {
|
|
||||||
let type_string = r#type.to_string();
|
|
||||||
|
|
||||||
if type_string.len() > 16 {
|
|
||||||
format!("{type_string:.13}...")
|
|
||||||
} else {
|
|
||||||
type_string
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or(String::with_capacity(0));
|
|
||||||
let position = position.to_string();
|
|
||||||
|
|
||||||
let instruction_display = format!(
|
|
||||||
"{index:^3} {bytecode:>8} {operation:13} {info:^20} {type_display:^16} {position:10}"
|
|
||||||
);
|
|
||||||
|
|
||||||
self.push_details(&instruction_display);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.push_border(§ion_border);
|
|
||||||
|
|
||||||
for line in LOCAL_HEADER {
|
|
||||||
self.push_header(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (
|
|
||||||
index,
|
|
||||||
Local {
|
|
||||||
identifier_index,
|
|
||||||
r#type,
|
|
||||||
scope,
|
|
||||||
register_index,
|
|
||||||
is_mutable: mutable,
|
|
||||||
},
|
|
||||||
) in self.chunk.locals().iter().enumerate()
|
|
||||||
{
|
|
||||||
let identifier_display = self
|
|
||||||
.chunk
|
|
||||||
.constants()
|
|
||||||
.get(*identifier_index as usize)
|
|
||||||
.map(|value| value.to_string())
|
|
||||||
.unwrap_or_else(|| "unknown".to_string());
|
|
||||||
let type_display = r#type
|
|
||||||
.as_ref()
|
|
||||||
.map(|r#type| {
|
|
||||||
let type_string = r#type.to_string();
|
|
||||||
|
|
||||||
if type_string.len() > 16 {
|
|
||||||
format!("{type_string:.13}...")
|
|
||||||
} else {
|
|
||||||
type_string
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or("unknown".to_string());
|
|
||||||
let local_display = format!(
|
|
||||||
"{index:^3} {identifier_display:10} {type_display:16} {mutable:7} {scope:7} {register_index:8}"
|
|
||||||
);
|
|
||||||
|
|
||||||
self.push_details(&local_display);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.push_border(§ion_border);
|
|
||||||
|
|
||||||
for line in CONSTANT_HEADER {
|
|
||||||
self.push_header(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (index, value) in self.chunk.constants().iter().enumerate() {
|
|
||||||
let value_display = {
|
|
||||||
let value_string = value.to_string();
|
|
||||||
|
|
||||||
if value_string.len() > 15 {
|
|
||||||
format!("{value_string:.12}...")
|
|
||||||
} else {
|
|
||||||
value_string
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let constant_display = format!("{index:^3} {value_display:^15}");
|
|
||||||
|
|
||||||
self.push_details(&constant_display);
|
|
||||||
|
|
||||||
if let Value::Concrete(ConcreteValue::Function(function)) = value {
|
|
||||||
let function_disassembly = function
|
|
||||||
.chunk()
|
|
||||||
.disassembler()
|
|
||||||
.styled(self.styled)
|
|
||||||
.indent(self.indent + 1)
|
|
||||||
.disassemble();
|
|
||||||
|
|
||||||
self.output.push_str(&function_disassembly);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.push_border(&bottom_border);
|
|
||||||
|
|
||||||
let _ = self.output.trim_end_matches('\n');
|
|
||||||
|
|
||||||
self.output
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
//! Top-level Dust errors with source code annotations.
|
|
||||||
use annotate_snippets::{Level, Renderer, Snippet};
|
|
||||||
|
|
||||||
use crate::{vm::VmError, CompileError, Span};
|
|
||||||
|
|
||||||
/// A top-level error that can occur during the execution of Dust code.
|
|
||||||
///
|
|
||||||
/// This error can display nicely formatted messages with source code annotations.
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum DustError<'src> {
|
|
||||||
Compile {
|
|
||||||
error: CompileError,
|
|
||||||
source: &'src str,
|
|
||||||
},
|
|
||||||
Runtime {
|
|
||||||
error: VmError,
|
|
||||||
source: &'src str,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'src> DustError<'src> {
|
|
||||||
pub fn report(&self) -> String {
|
|
||||||
let mut report = String::new();
|
|
||||||
let renderer = Renderer::styled();
|
|
||||||
|
|
||||||
match self {
|
|
||||||
DustError::Runtime { error, source } => {
|
|
||||||
let position = error.position();
|
|
||||||
let label = format!("{}: {}", VmError::title(), error.description());
|
|
||||||
let details = error
|
|
||||||
.details()
|
|
||||||
.unwrap_or_else(|| "While running this code".to_string());
|
|
||||||
let message = Level::Error.title(&label).snippet(
|
|
||||||
Snippet::source(source)
|
|
||||||
.fold(false)
|
|
||||||
.annotation(Level::Error.span(position.0..position.1).label(&details)),
|
|
||||||
);
|
|
||||||
|
|
||||||
report.push_str(&renderer.render(message).to_string());
|
|
||||||
}
|
|
||||||
DustError::Compile { error, source } => {
|
|
||||||
let position = error.position();
|
|
||||||
let label = format!("{}: {}", CompileError::title(), error.description());
|
|
||||||
let details = error
|
|
||||||
.details()
|
|
||||||
.unwrap_or_else(|| "While parsing this code".to_string());
|
|
||||||
let message = Level::Error.title(&label).snippet(
|
|
||||||
Snippet::source(source)
|
|
||||||
.fold(false)
|
|
||||||
.annotation(Level::Error.span(position.0..position.1).label(&details)),
|
|
||||||
);
|
|
||||||
|
|
||||||
report.push_str(&renderer.render(message).to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
report
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait AnnotatedError {
|
|
||||||
fn title() -> &'static str;
|
|
||||||
fn description(&self) -> &'static str;
|
|
||||||
fn details(&self) -> Option<String>;
|
|
||||||
fn position(&self) -> Span;
|
|
||||||
}
|
|
@ -1,224 +0,0 @@
|
|||||||
//! Formatting tools
|
|
||||||
use std::mem::replace;
|
|
||||||
|
|
||||||
use colored::{ColoredString, Colorize, CustomColor};
|
|
||||||
|
|
||||||
use crate::{CompileError, DustError, LexError, Lexer, Token};
|
|
||||||
|
|
||||||
pub fn format(source: &str, line_numbers: bool, colored: bool) -> Result<String, DustError> {
|
|
||||||
let lexer = Lexer::new(source);
|
|
||||||
let formatted = Formatter::new(lexer)
|
|
||||||
.line_numbers(line_numbers)
|
|
||||||
.colored(colored)
|
|
||||||
.format()
|
|
||||||
.map_err(|error| DustError::Compile {
|
|
||||||
error: CompileError::Lex(error),
|
|
||||||
source,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(formatted)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Formatter<'src> {
|
|
||||||
lexer: Lexer<'src>,
|
|
||||||
output_lines: Vec<(String, LineKind, usize)>,
|
|
||||||
next_line: String,
|
|
||||||
indent: usize,
|
|
||||||
|
|
||||||
current_token: Token<'src>,
|
|
||||||
previous_token: Token<'src>,
|
|
||||||
|
|
||||||
// Options
|
|
||||||
line_numbers: bool,
|
|
||||||
colored: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'src> Formatter<'src> {
|
|
||||||
pub fn new(mut lexer: Lexer<'src>) -> Self {
|
|
||||||
let (current_token, _) = lexer.next_token().unwrap();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
lexer,
|
|
||||||
output_lines: Vec::new(),
|
|
||||||
next_line: String::new(),
|
|
||||||
indent: 0,
|
|
||||||
current_token,
|
|
||||||
previous_token: Token::Eof,
|
|
||||||
line_numbers: false,
|
|
||||||
colored: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn line_numbers(mut self, line_numbers: bool) -> Self {
|
|
||||||
self.line_numbers = line_numbers;
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn colored(mut self, colored: bool) -> Self {
|
|
||||||
self.colored = colored;
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn format(&mut self) -> Result<String, LexError> {
|
|
||||||
let mut line_kind = LineKind::Empty;
|
|
||||||
|
|
||||||
self.advance()?;
|
|
||||||
|
|
||||||
while self.current_token != Token::Eof {
|
|
||||||
use Token::*;
|
|
||||||
|
|
||||||
if self.current_token.is_expression() && line_kind != LineKind::Assignment {
|
|
||||||
line_kind = LineKind::Expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.current_token {
|
|
||||||
Boolean(boolean) => {
|
|
||||||
self.push_colored(boolean.red());
|
|
||||||
}
|
|
||||||
Byte(byte) => {
|
|
||||||
self.push_colored(byte.green());
|
|
||||||
}
|
|
||||||
Character(character) => {
|
|
||||||
self.push_colored(
|
|
||||||
character
|
|
||||||
.to_string()
|
|
||||||
.custom_color(CustomColor::new(225, 150, 150)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Float(float) => {
|
|
||||||
self.push_colored(float.yellow());
|
|
||||||
}
|
|
||||||
Identifier(identifier) => {
|
|
||||||
self.push_colored(identifier.blue());
|
|
||||||
self.next_line.push(' ');
|
|
||||||
}
|
|
||||||
Integer(integer) => {
|
|
||||||
self.push_colored(integer.cyan());
|
|
||||||
}
|
|
||||||
String(string) => {
|
|
||||||
self.push_colored(string.magenta());
|
|
||||||
}
|
|
||||||
LeftCurlyBrace => {
|
|
||||||
self.next_line.push_str(self.current_token.as_str());
|
|
||||||
self.commit_line(LineKind::OpenBlock);
|
|
||||||
|
|
||||||
self.indent += 1;
|
|
||||||
}
|
|
||||||
RightCurlyBrace => {
|
|
||||||
self.commit_line(LineKind::CloseBlock);
|
|
||||||
self.next_line.push_str(self.current_token.as_str());
|
|
||||||
|
|
||||||
self.indent -= 1;
|
|
||||||
}
|
|
||||||
Semicolon => {
|
|
||||||
if line_kind != LineKind::Assignment {
|
|
||||||
line_kind = LineKind::Statement;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.next_line.push_str(self.current_token.as_str());
|
|
||||||
self.commit_line(line_kind);
|
|
||||||
}
|
|
||||||
Let => {
|
|
||||||
line_kind = LineKind::Assignment;
|
|
||||||
|
|
||||||
self.push_colored(self.current_token.as_str().bold());
|
|
||||||
self.next_line.push(' ');
|
|
||||||
}
|
|
||||||
Break | Loop | Return | While => {
|
|
||||||
line_kind = LineKind::Statement;
|
|
||||||
|
|
||||||
self.push_colored(self.current_token.as_str().bold());
|
|
||||||
self.next_line.push(' ');
|
|
||||||
}
|
|
||||||
token => {
|
|
||||||
self.next_line.push_str(token.as_str());
|
|
||||||
self.next_line.push(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut previous_index = 0;
|
|
||||||
let mut current_index = 1;
|
|
||||||
|
|
||||||
while current_index < self.output_lines.len() {
|
|
||||||
let (_, previous, _) = &self.output_lines[previous_index];
|
|
||||||
let (_, current, _) = &self.output_lines[current_index];
|
|
||||||
|
|
||||||
match (previous, current) {
|
|
||||||
(LineKind::Empty, _)
|
|
||||||
| (_, LineKind::Empty)
|
|
||||||
| (LineKind::OpenBlock, _)
|
|
||||||
| (_, LineKind::CloseBlock) => {}
|
|
||||||
(left, right) if left == right => {}
|
|
||||||
_ => {
|
|
||||||
self.output_lines
|
|
||||||
.insert(current_index, ("".to_string(), LineKind::Empty, 0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
previous_index += 1;
|
|
||||||
current_index += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let formatted = String::with_capacity(
|
|
||||||
self.output_lines
|
|
||||||
.iter()
|
|
||||||
.fold(0, |total, (line, _, _)| total + line.len()),
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(self.output_lines.iter().enumerate().fold(
|
|
||||||
formatted,
|
|
||||||
|acc, (index, (line, _, indent))| {
|
|
||||||
let index = if index == 0 {
|
|
||||||
format!("{:<3}| ", index + 1).dimmed()
|
|
||||||
} else {
|
|
||||||
format!("\n{:<3}| ", index + 1).dimmed()
|
|
||||||
};
|
|
||||||
let left_pad = " ".repeat(*indent);
|
|
||||||
|
|
||||||
format!("{}{}{}{}", acc, index, left_pad, line)
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn advance(&mut self) -> Result<(), LexError> {
|
|
||||||
if self.lexer.is_eof() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let (new_token, position) = self.lexer.next_token()?;
|
|
||||||
|
|
||||||
log::info!(
|
|
||||||
"Parsing {} at {}",
|
|
||||||
new_token.to_string().bold(),
|
|
||||||
position.to_string()
|
|
||||||
);
|
|
||||||
|
|
||||||
self.previous_token = replace(&mut self.current_token, new_token);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_colored(&mut self, colored: ColoredString) {
|
|
||||||
self.next_line.push_str(&format!("{}", colored));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn commit_line(&mut self, line_kind: LineKind) {
|
|
||||||
self.output_lines
|
|
||||||
.push((self.next_line.clone(), line_kind, self.indent));
|
|
||||||
self.next_line.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
pub enum LineKind {
|
|
||||||
Empty,
|
|
||||||
Assignment,
|
|
||||||
Expression,
|
|
||||||
Statement,
|
|
||||||
OpenBlock,
|
|
||||||
CloseBlock,
|
|
||||||
}
|
|
@ -1,902 +0,0 @@
|
|||||||
//! An operation and its arguments for the Dust virtual machine.
|
|
||||||
//!
|
|
||||||
//! Each instruction is a 32-bit unsigned integer that is divided into five fields:
|
|
||||||
//! - Bits 0-6: The operation code.
|
|
||||||
//! - Bit 7: A flag indicating whether the B argument is a constant.
|
|
||||||
//! - Bit 8: A flag indicating whether the C argument is a constant.
|
|
||||||
//! - Bits 9-16: The A argument,
|
|
||||||
//! - Bits 17-24: The B argument.
|
|
||||||
//! - Bits 25-32: The C argument.
|
|
||||||
//!
|
|
||||||
//! Be careful when working with instructions directly. When modifying an instruction, be sure to
|
|
||||||
//! account for the fact that setting the A, B, or C arguments to 0 will have no effect. It is
|
|
||||||
//! usually best to remove instructions and insert new ones in their place instead of mutating them.
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::{Chunk, NativeFunction, Operation, Type};
|
|
||||||
|
|
||||||
/// An operation and its arguments for the Dust virtual machine.
|
|
||||||
///
|
|
||||||
/// See the [module-level documentation](index.html) for more information.
|
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
|
||||||
pub struct Instruction(u32);
|
|
||||||
|
|
||||||
impl Instruction {
|
|
||||||
pub fn with_operation(operation: Operation) -> Instruction {
|
|
||||||
Instruction(operation as u32)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn r#move(to_register: u8, from_register: u8) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::Move as u32);
|
|
||||||
|
|
||||||
instruction.set_a(to_register);
|
|
||||||
instruction.set_b(from_register);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn close(from_register: u8, to_register: u8) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::Close as u32);
|
|
||||||
|
|
||||||
instruction.set_b(from_register);
|
|
||||||
instruction.set_c(to_register);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_boolean(to_register: u8, value: bool, skip: bool) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::LoadBoolean as u32);
|
|
||||||
|
|
||||||
instruction.set_a(to_register);
|
|
||||||
instruction.set_b_to_boolean(value);
|
|
||||||
instruction.set_c_to_boolean(skip);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_constant(to_register: u8, constant_index: u8, skip: bool) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::LoadConstant as u32);
|
|
||||||
|
|
||||||
instruction.set_a(to_register);
|
|
||||||
instruction.set_b(constant_index);
|
|
||||||
instruction.set_c_to_boolean(skip);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_list(to_register: u8, start_register: u8) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::LoadList as u32);
|
|
||||||
|
|
||||||
instruction.set_a(to_register);
|
|
||||||
instruction.set_b(start_register);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_self(to_register: u8) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::LoadSelf as u32);
|
|
||||||
|
|
||||||
instruction.set_a(to_register);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn define_local(to_register: u8, local_index: u8, is_mutable: bool) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::DefineLocal as u32);
|
|
||||||
|
|
||||||
instruction.set_a(to_register);
|
|
||||||
instruction.set_b(local_index);
|
|
||||||
instruction.set_c(if is_mutable { 1 } else { 0 });
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_local(to_register: u8, local_index: u8) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::GetLocal as u32);
|
|
||||||
|
|
||||||
instruction.set_a(to_register);
|
|
||||||
instruction.set_b(local_index);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_local(from_register: u8, local_index: u8) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::SetLocal as u32);
|
|
||||||
|
|
||||||
instruction.set_a(from_register);
|
|
||||||
instruction.set_b(local_index);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::Add as u32);
|
|
||||||
|
|
||||||
instruction.set_a(to_register);
|
|
||||||
instruction.set_b(left_index);
|
|
||||||
instruction.set_c(right_index);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn subtract(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::Subtract as u32);
|
|
||||||
|
|
||||||
instruction.set_a(to_register);
|
|
||||||
instruction.set_b(left_index);
|
|
||||||
instruction.set_c(right_index);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn multiply(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::Multiply as u32);
|
|
||||||
|
|
||||||
instruction.set_a(to_register);
|
|
||||||
instruction.set_b(left_index);
|
|
||||||
instruction.set_c(right_index);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn divide(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::Divide as u32);
|
|
||||||
|
|
||||||
instruction.set_a(to_register);
|
|
||||||
instruction.set_b(left_index);
|
|
||||||
instruction.set_c(right_index);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn modulo(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::Modulo as u32);
|
|
||||||
|
|
||||||
instruction.set_a(to_register);
|
|
||||||
instruction.set_b(left_index);
|
|
||||||
instruction.set_c(right_index);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn test(to_register: u8, test_value: bool) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::Test as u32);
|
|
||||||
|
|
||||||
instruction.set_a(to_register);
|
|
||||||
instruction.set_c_to_boolean(test_value);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn test_set(to_register: u8, argument_index: u8, test_value: bool) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::TestSet as u32);
|
|
||||||
|
|
||||||
instruction.set_a(to_register);
|
|
||||||
instruction.set_b(argument_index);
|
|
||||||
instruction.set_c_to_boolean(test_value);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn equal(comparison_boolean: bool, left_index: u8, right_index: u8) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::Equal as u32);
|
|
||||||
|
|
||||||
instruction.set_a_to_boolean(comparison_boolean);
|
|
||||||
instruction.set_b(left_index);
|
|
||||||
instruction.set_c(right_index);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn less(comparison_boolean: bool, left_index: u8, right_index: u8) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::Less as u32);
|
|
||||||
|
|
||||||
instruction.set_a_to_boolean(comparison_boolean);
|
|
||||||
instruction.set_b(left_index);
|
|
||||||
instruction.set_c(right_index);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn less_equal(comparison_boolean: bool, left_index: u8, right_index: u8) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::LessEqual as u32);
|
|
||||||
|
|
||||||
instruction.set_a_to_boolean(comparison_boolean);
|
|
||||||
instruction.set_b(left_index);
|
|
||||||
instruction.set_c(right_index);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn negate(to_register: u8, from_index: u8) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::Negate as u32);
|
|
||||||
|
|
||||||
instruction.set_a(to_register);
|
|
||||||
instruction.set_b(from_index);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn not(to_register: u8, from_index: u8) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::Not as u32);
|
|
||||||
|
|
||||||
instruction.set_a(to_register);
|
|
||||||
instruction.set_b(from_index);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn jump(jump_offset: u8, is_positive: bool) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::Jump as u32);
|
|
||||||
|
|
||||||
instruction.set_b(jump_offset);
|
|
||||||
instruction.set_c_to_boolean(is_positive);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn call(to_register: u8, function_register: u8, argument_count: u8) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::Call as u32);
|
|
||||||
|
|
||||||
instruction.set_a(to_register);
|
|
||||||
instruction.set_b(function_register);
|
|
||||||
instruction.set_c(argument_count);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn call_native(
|
|
||||||
to_register: u8,
|
|
||||||
native_fn: NativeFunction,
|
|
||||||
argument_count: u8,
|
|
||||||
) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::CallNative as u32);
|
|
||||||
let native_fn_byte = native_fn as u8;
|
|
||||||
|
|
||||||
instruction.set_a(to_register);
|
|
||||||
instruction.set_b(native_fn_byte);
|
|
||||||
instruction.set_c(argument_count);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn r#return(should_return_value: bool) -> Instruction {
|
|
||||||
let mut instruction = Instruction(Operation::Return as u32);
|
|
||||||
|
|
||||||
instruction.set_b_to_boolean(should_return_value);
|
|
||||||
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn operation(&self) -> Operation {
|
|
||||||
Operation::from((self.0 & 0b0000_0000_0011_1111) as u8)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_operation(&mut self, operation: Operation) {
|
|
||||||
self.0 |= u8::from(operation) as u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn data(&self) -> (Operation, u8, u8, u8, bool, bool) {
|
|
||||||
(
|
|
||||||
self.operation(),
|
|
||||||
self.a(),
|
|
||||||
self.b(),
|
|
||||||
self.c(),
|
|
||||||
self.b_is_constant(),
|
|
||||||
self.c_is_constant(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn a(&self) -> u8 {
|
|
||||||
(self.0 >> 24) as u8
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn b(&self) -> u8 {
|
|
||||||
(self.0 >> 16) as u8
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn c(&self) -> u8 {
|
|
||||||
(self.0 >> 8) as u8
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn a_as_boolean(&self) -> bool {
|
|
||||||
self.a() != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn b_as_boolean(&self) -> bool {
|
|
||||||
self.b() != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn c_as_boolean(&self) -> bool {
|
|
||||||
self.c() != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_a_to_boolean(&mut self, boolean: bool) -> &mut Self {
|
|
||||||
self.set_a(if boolean { 1 } else { 0 });
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_b_to_boolean(&mut self, boolean: bool) -> &mut Self {
|
|
||||||
self.set_b(if boolean { 1 } else { 0 });
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_c_to_boolean(&mut self, boolean: bool) -> &mut Self {
|
|
||||||
self.set_c(if boolean { 1 } else { 0 });
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_a(&mut self, to_register: u8) {
|
|
||||||
self.0 |= (to_register as u32) << 24;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_b(&mut self, argument: u8) {
|
|
||||||
self.0 |= (argument as u32) << 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_c(&mut self, argument: u8) {
|
|
||||||
self.0 |= (argument as u32) << 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn b_is_constant(&self) -> bool {
|
|
||||||
self.0 & 0b1000_0000 != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn c_is_constant(&self) -> bool {
|
|
||||||
self.0 & 0b0100_0000 != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_b_is_constant(&mut self) -> &mut Self {
|
|
||||||
self.0 |= 0b1000_0000;
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_c_is_constant(&mut self) -> &mut Self {
|
|
||||||
self.0 |= 0b0100_0000;
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn yields_value(&self) -> bool {
|
|
||||||
match self.operation() {
|
|
||||||
Operation::Add
|
|
||||||
| Operation::Call
|
|
||||||
| Operation::Divide
|
|
||||||
| Operation::GetLocal
|
|
||||||
| Operation::LoadBoolean
|
|
||||||
| Operation::LoadConstant
|
|
||||||
| Operation::LoadList
|
|
||||||
| Operation::LoadSelf
|
|
||||||
| Operation::Modulo
|
|
||||||
| Operation::Multiply
|
|
||||||
| Operation::Negate
|
|
||||||
| Operation::Not
|
|
||||||
| Operation::Subtract => true,
|
|
||||||
Operation::CallNative => {
|
|
||||||
let native_function = NativeFunction::from(self.b());
|
|
||||||
|
|
||||||
native_function.r#type().return_type.is_some()
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn yielded_type(&self, chunk: &Chunk) -> Option<Type> {
|
|
||||||
use Operation::*;
|
|
||||||
|
|
||||||
match self.operation() {
|
|
||||||
Add | Divide | Modulo | Multiply | Subtract => {
|
|
||||||
if self.b_is_constant() {
|
|
||||||
chunk.get_constant_type(self.b())
|
|
||||||
} else {
|
|
||||||
chunk.get_register_type(self.b())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LoadBoolean | Not => Some(Type::Boolean),
|
|
||||||
Negate => {
|
|
||||||
if self.b_is_constant() {
|
|
||||||
chunk.get_constant_type(self.b())
|
|
||||||
} else {
|
|
||||||
chunk.get_register_type(self.b())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LoadConstant => chunk.get_constant_type(self.b()),
|
|
||||||
LoadList => chunk.get_register_type(self.a()),
|
|
||||||
GetLocal => chunk.get_local_type(self.b()),
|
|
||||||
CallNative => {
|
|
||||||
let native_function = NativeFunction::from(self.b());
|
|
||||||
|
|
||||||
native_function.r#type().return_type.map(|boxed| *boxed)
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disassembly_info(&self, chunk: &Chunk) -> String {
|
|
||||||
let format_arguments = || {
|
|
||||||
let first_argument = if self.b_is_constant() {
|
|
||||||
format!("C{}", self.b())
|
|
||||||
} else {
|
|
||||||
format!("R{}", self.b())
|
|
||||||
};
|
|
||||||
let second_argument = if self.c_is_constant() {
|
|
||||||
format!("C{}", self.c())
|
|
||||||
} else {
|
|
||||||
format!("R{}", self.c())
|
|
||||||
};
|
|
||||||
|
|
||||||
(first_argument, second_argument)
|
|
||||||
};
|
|
||||||
|
|
||||||
match self.operation() {
|
|
||||||
Operation::Move => format!("R{} = R{}", self.a(), self.b()),
|
|
||||||
Operation::Close => {
|
|
||||||
let from_register = self.b();
|
|
||||||
let to_register = self.c().saturating_sub(1);
|
|
||||||
|
|
||||||
format!("R{from_register}..=R{to_register}")
|
|
||||||
}
|
|
||||||
Operation::LoadBoolean => {
|
|
||||||
let to_register = self.a();
|
|
||||||
let boolean = self.b_as_boolean();
|
|
||||||
let jump = self.c_as_boolean();
|
|
||||||
|
|
||||||
if jump {
|
|
||||||
format!("R{to_register} = {boolean} && SKIP")
|
|
||||||
} else {
|
|
||||||
format!("R{to_register} = {boolean}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Operation::LoadConstant => {
|
|
||||||
let register_index = self.a();
|
|
||||||
let constant_index = self.b();
|
|
||||||
let jump = self.c_as_boolean();
|
|
||||||
|
|
||||||
if jump {
|
|
||||||
format!("R{register_index} = C{constant_index} && SKIP")
|
|
||||||
} else {
|
|
||||||
format!("R{register_index} = C{constant_index}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Operation::LoadList => {
|
|
||||||
let to_register = self.a();
|
|
||||||
let first_index = self.b();
|
|
||||||
let last_index = self.c();
|
|
||||||
|
|
||||||
format!("R{to_register} = [R{first_index}..=R{last_index}]",)
|
|
||||||
}
|
|
||||||
Operation::LoadSelf => {
|
|
||||||
let to_register = self.a();
|
|
||||||
let name = chunk
|
|
||||||
.name()
|
|
||||||
.map(|idenifier| idenifier.as_str())
|
|
||||||
.unwrap_or("self");
|
|
||||||
|
|
||||||
format!("R{to_register} = {name}")
|
|
||||||
}
|
|
||||||
Operation::DefineLocal => {
|
|
||||||
let to_register = self.a();
|
|
||||||
let local_index = self.b();
|
|
||||||
let identifier_display = match chunk.get_identifier(local_index) {
|
|
||||||
Some(identifier) => identifier.to_string(),
|
|
||||||
None => "???".to_string(),
|
|
||||||
};
|
|
||||||
let mutable_display = if self.c_as_boolean() { "mut" } else { "" };
|
|
||||||
|
|
||||||
format!("R{to_register} = L{local_index} {mutable_display} {identifier_display}")
|
|
||||||
}
|
|
||||||
Operation::GetLocal => {
|
|
||||||
let local_index = self.b();
|
|
||||||
|
|
||||||
format!("R{} = L{}", self.a(), local_index)
|
|
||||||
}
|
|
||||||
Operation::SetLocal => {
|
|
||||||
let local_index = self.b();
|
|
||||||
let identifier_display = match chunk.get_identifier(local_index) {
|
|
||||||
Some(identifier) => identifier.to_string(),
|
|
||||||
None => "???".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
format!("L{} = R{} {}", local_index, self.a(), identifier_display)
|
|
||||||
}
|
|
||||||
Operation::Add => {
|
|
||||||
let to_register = self.a();
|
|
||||||
let (first_argument, second_argument) = format_arguments();
|
|
||||||
|
|
||||||
format!("R{to_register} = {first_argument} + {second_argument}",)
|
|
||||||
}
|
|
||||||
Operation::Subtract => {
|
|
||||||
let to_register = self.a();
|
|
||||||
let (first_argument, second_argument) = format_arguments();
|
|
||||||
|
|
||||||
format!("R{to_register} = {first_argument} - {second_argument}",)
|
|
||||||
}
|
|
||||||
Operation::Multiply => {
|
|
||||||
let to_register = self.a();
|
|
||||||
let (first_argument, second_argument) = format_arguments();
|
|
||||||
|
|
||||||
format!("R{to_register} = {first_argument} * {second_argument}",)
|
|
||||||
}
|
|
||||||
Operation::Divide => {
|
|
||||||
let to_register = self.a();
|
|
||||||
let (first_argument, second_argument) = format_arguments();
|
|
||||||
|
|
||||||
format!("R{to_register} = {first_argument} / {second_argument}",)
|
|
||||||
}
|
|
||||||
Operation::Modulo => {
|
|
||||||
let to_register = self.a();
|
|
||||||
let (first_argument, second_argument) = format_arguments();
|
|
||||||
|
|
||||||
format!("R{to_register} = {first_argument} % {second_argument}",)
|
|
||||||
}
|
|
||||||
Operation::Test => {
|
|
||||||
let to_register = self.a();
|
|
||||||
let test_value = self.c_as_boolean();
|
|
||||||
|
|
||||||
format!("if R{to_register} != {test_value} {{ SKIP }}")
|
|
||||||
}
|
|
||||||
Operation::TestSet => {
|
|
||||||
let to_register = self.a();
|
|
||||||
let argument = format!("R{}", self.b());
|
|
||||||
let test_value = self.c_as_boolean();
|
|
||||||
let bang = if test_value { "" } else { "!" };
|
|
||||||
|
|
||||||
format!("if {bang}R{to_register} {{ R{to_register} = R{argument} }}",)
|
|
||||||
}
|
|
||||||
Operation::Equal => {
|
|
||||||
let comparison_symbol = if self.a_as_boolean() { "==" } else { "!=" };
|
|
||||||
|
|
||||||
let (first_argument, second_argument) = format_arguments();
|
|
||||||
|
|
||||||
format!("if {first_argument} {comparison_symbol} {second_argument} {{ SKIP }}")
|
|
||||||
}
|
|
||||||
Operation::Less => {
|
|
||||||
let comparison_symbol = if self.a_as_boolean() { "<" } else { ">=" };
|
|
||||||
let (first_argument, second_argument) = format_arguments();
|
|
||||||
|
|
||||||
format!("if {first_argument} {comparison_symbol} {second_argument} {{ SKIP }}")
|
|
||||||
}
|
|
||||||
Operation::LessEqual => {
|
|
||||||
let comparison_symbol = if self.a_as_boolean() { "<=" } else { ">" };
|
|
||||||
let (first_argument, second_argument) = format_arguments();
|
|
||||||
|
|
||||||
format!("if {first_argument} {comparison_symbol} {second_argument} {{ SKIP }}")
|
|
||||||
}
|
|
||||||
Operation::Negate => {
|
|
||||||
let to_register = self.a();
|
|
||||||
let argument = if self.b_is_constant() {
|
|
||||||
format!("C{}", self.b())
|
|
||||||
} else {
|
|
||||||
format!("R{}", self.b())
|
|
||||||
};
|
|
||||||
|
|
||||||
format!("R{to_register} = -{argument}")
|
|
||||||
}
|
|
||||||
Operation::Not => {
|
|
||||||
let to_register = self.a();
|
|
||||||
let argument = if self.b_is_constant() {
|
|
||||||
format!("C{}", self.b())
|
|
||||||
} else {
|
|
||||||
format!("R{}", self.b())
|
|
||||||
};
|
|
||||||
|
|
||||||
format!("R{to_register} = !{argument}")
|
|
||||||
}
|
|
||||||
Operation::Jump => {
|
|
||||||
let jump_distance = self.b();
|
|
||||||
let is_positive = self.c_as_boolean();
|
|
||||||
|
|
||||||
if is_positive {
|
|
||||||
format!("JUMP +{jump_distance}")
|
|
||||||
} else {
|
|
||||||
format!("JUMP -{jump_distance}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Operation::Call => {
|
|
||||||
let to_register = self.a();
|
|
||||||
let function_register = self.b();
|
|
||||||
let argument_count = self.c();
|
|
||||||
|
|
||||||
let mut output = format!("R{to_register} = R{function_register}(");
|
|
||||||
|
|
||||||
if argument_count != 0 {
|
|
||||||
let first_argument = function_register + 1;
|
|
||||||
|
|
||||||
for (index, register) in
|
|
||||||
(first_argument..first_argument + argument_count).enumerate()
|
|
||||||
{
|
|
||||||
if index > 0 {
|
|
||||||
output.push_str(", ");
|
|
||||||
}
|
|
||||||
|
|
||||||
output.push_str(&format!("R{}", register));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output.push(')');
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
Operation::CallNative => {
|
|
||||||
let to_register = self.a();
|
|
||||||
let native_function = NativeFunction::from(self.b());
|
|
||||||
let argument_count = self.c();
|
|
||||||
let mut output = String::new();
|
|
||||||
let native_function_name = native_function.as_str();
|
|
||||||
|
|
||||||
if native_function.r#type().return_type.is_some() {
|
|
||||||
output.push_str(&format!("R{} = {}(", to_register, native_function_name));
|
|
||||||
} else {
|
|
||||||
output.push_str(&format!("{}(", native_function_name));
|
|
||||||
}
|
|
||||||
|
|
||||||
if argument_count != 0 {
|
|
||||||
let first_argument = to_register.saturating_sub(argument_count);
|
|
||||||
|
|
||||||
for (index, register) in (first_argument..to_register).enumerate() {
|
|
||||||
if index > 0 {
|
|
||||||
output.push_str(", ");
|
|
||||||
}
|
|
||||||
|
|
||||||
output.push_str(&format!("R{}", register));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output.push(')');
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
Operation::Return => {
|
|
||||||
let should_return_value = self.b_as_boolean();
|
|
||||||
|
|
||||||
if should_return_value {
|
|
||||||
"->".to_string()
|
|
||||||
} else {
|
|
||||||
"".to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&Instruction> for u32 {
|
|
||||||
fn from(instruction: &Instruction) -> Self {
|
|
||||||
instruction.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn r#move() {
|
|
||||||
let mut instruction = Instruction::r#move(0, 1);
|
|
||||||
|
|
||||||
instruction.set_b_is_constant();
|
|
||||||
instruction.set_c_is_constant();
|
|
||||||
|
|
||||||
assert_eq!(instruction.operation(), Operation::Move);
|
|
||||||
assert_eq!(instruction.a(), 0);
|
|
||||||
assert_eq!(instruction.b(), 1);
|
|
||||||
assert!(instruction.b_is_constant());
|
|
||||||
assert!(instruction.b_is_constant());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn close() {
|
|
||||||
let instruction = Instruction::close(1, 2);
|
|
||||||
|
|
||||||
assert_eq!(instruction.operation(), Operation::Close);
|
|
||||||
assert_eq!(instruction.b(), 1);
|
|
||||||
assert_eq!(instruction.c(), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn load_boolean() {
|
|
||||||
let instruction = Instruction::load_boolean(4, true, true);
|
|
||||||
|
|
||||||
assert_eq!(instruction.operation(), Operation::LoadBoolean);
|
|
||||||
assert_eq!(instruction.a(), 4);
|
|
||||||
assert!(instruction.a_as_boolean());
|
|
||||||
assert!(instruction.c_as_boolean());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn load_constant() {
|
|
||||||
let mut instruction = Instruction::load_constant(0, 1, true);
|
|
||||||
|
|
||||||
instruction.set_b_is_constant();
|
|
||||||
instruction.set_c_is_constant();
|
|
||||||
|
|
||||||
assert_eq!(instruction.operation(), Operation::LoadConstant);
|
|
||||||
assert_eq!(instruction.a(), 0);
|
|
||||||
assert_eq!(instruction.b(), 1);
|
|
||||||
assert!(instruction.b_is_constant());
|
|
||||||
assert!(instruction.b_is_constant());
|
|
||||||
assert!(instruction.c_as_boolean());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn load_list() {
|
|
||||||
let instruction = Instruction::load_list(0, 1);
|
|
||||||
|
|
||||||
assert_eq!(instruction.operation(), Operation::LoadList);
|
|
||||||
assert_eq!(instruction.a(), 0);
|
|
||||||
assert_eq!(instruction.b(), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn load_self() {
|
|
||||||
let instruction = Instruction::load_self(10);
|
|
||||||
|
|
||||||
assert_eq!(instruction.operation(), Operation::LoadSelf);
|
|
||||||
assert_eq!(instruction.a(), 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn declare_local() {
|
|
||||||
let mut instruction = Instruction::define_local(0, 1, true);
|
|
||||||
|
|
||||||
instruction.set_b_is_constant();
|
|
||||||
|
|
||||||
assert_eq!(instruction.operation(), Operation::DefineLocal);
|
|
||||||
assert_eq!(instruction.a(), 0);
|
|
||||||
assert_eq!(instruction.b(), 1);
|
|
||||||
assert_eq!(instruction.c(), true as u8);
|
|
||||||
assert!(instruction.b_is_constant());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add() {
|
|
||||||
let mut instruction = Instruction::add(1, 1, 0);
|
|
||||||
|
|
||||||
instruction.set_b_is_constant();
|
|
||||||
|
|
||||||
assert_eq!(instruction.operation(), Operation::Add);
|
|
||||||
assert_eq!(instruction.a(), 1);
|
|
||||||
assert_eq!(instruction.b(), 1);
|
|
||||||
assert_eq!(instruction.c(), 0);
|
|
||||||
assert!(instruction.b_is_constant());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn subtract() {
|
|
||||||
let mut instruction = Instruction::subtract(0, 1, 2);
|
|
||||||
|
|
||||||
instruction.set_b_is_constant();
|
|
||||||
instruction.set_c_is_constant();
|
|
||||||
|
|
||||||
assert_eq!(instruction.operation(), Operation::Subtract);
|
|
||||||
assert_eq!(instruction.a(), 0);
|
|
||||||
assert_eq!(instruction.b(), 1);
|
|
||||||
assert_eq!(instruction.c(), 2);
|
|
||||||
assert!(instruction.b_is_constant());
|
|
||||||
assert!(instruction.b_is_constant());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn multiply() {
|
|
||||||
let mut instruction = Instruction::multiply(0, 1, 2);
|
|
||||||
|
|
||||||
instruction.set_b_is_constant();
|
|
||||||
instruction.set_c_is_constant();
|
|
||||||
|
|
||||||
assert_eq!(instruction.operation(), Operation::Multiply);
|
|
||||||
assert_eq!(instruction.a(), 0);
|
|
||||||
assert_eq!(instruction.b(), 1);
|
|
||||||
assert_eq!(instruction.c(), 2);
|
|
||||||
assert!(instruction.b_is_constant());
|
|
||||||
assert!(instruction.b_is_constant());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn divide() {
|
|
||||||
let mut instruction = Instruction::divide(0, 1, 2);
|
|
||||||
|
|
||||||
instruction.set_b_is_constant();
|
|
||||||
instruction.set_c_is_constant();
|
|
||||||
|
|
||||||
assert_eq!(instruction.operation(), Operation::Divide);
|
|
||||||
assert_eq!(instruction.a(), 0);
|
|
||||||
assert_eq!(instruction.b(), 1);
|
|
||||||
assert_eq!(instruction.c(), 2);
|
|
||||||
assert!(instruction.b_is_constant());
|
|
||||||
assert!(instruction.b_is_constant());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn and() {
|
|
||||||
let instruction = Instruction::test(4, true);
|
|
||||||
|
|
||||||
assert_eq!(instruction.operation(), Operation::Test);
|
|
||||||
assert_eq!(instruction.a(), 4);
|
|
||||||
assert!(instruction.c_as_boolean());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn or() {
|
|
||||||
let instruction = Instruction::test_set(4, 1, true);
|
|
||||||
|
|
||||||
assert_eq!(instruction.operation(), Operation::TestSet);
|
|
||||||
assert_eq!(instruction.a(), 4);
|
|
||||||
assert_eq!(instruction.b(), 1);
|
|
||||||
assert!(instruction.c_as_boolean());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn equal() {
|
|
||||||
let mut instruction = Instruction::equal(true, 1, 2);
|
|
||||||
|
|
||||||
instruction.set_b_is_constant();
|
|
||||||
instruction.set_c_is_constant();
|
|
||||||
|
|
||||||
assert_eq!(instruction.operation(), Operation::Equal);
|
|
||||||
assert!(instruction.a_as_boolean());
|
|
||||||
assert_eq!(instruction.b(), 1);
|
|
||||||
assert_eq!(instruction.c(), 2);
|
|
||||||
assert!(instruction.b_is_constant());
|
|
||||||
assert!(instruction.b_is_constant());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn negate() {
|
|
||||||
let mut instruction = Instruction::negate(0, 1);
|
|
||||||
|
|
||||||
instruction.set_b_is_constant();
|
|
||||||
instruction.set_c_is_constant();
|
|
||||||
|
|
||||||
assert_eq!(instruction.operation(), Operation::Negate);
|
|
||||||
assert_eq!(instruction.a(), 0);
|
|
||||||
assert_eq!(instruction.b(), 1);
|
|
||||||
assert!(instruction.b_is_constant());
|
|
||||||
assert!(instruction.b_is_constant());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn not() {
|
|
||||||
let mut instruction = Instruction::not(0, 1);
|
|
||||||
|
|
||||||
instruction.set_b_is_constant();
|
|
||||||
instruction.set_c_is_constant();
|
|
||||||
|
|
||||||
assert_eq!(instruction.operation(), Operation::Not);
|
|
||||||
assert_eq!(instruction.a(), 0);
|
|
||||||
assert_eq!(instruction.b(), 1);
|
|
||||||
assert!(instruction.b_is_constant());
|
|
||||||
assert!(instruction.b_is_constant());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn jump() {
|
|
||||||
let instruction = Instruction::jump(4, true);
|
|
||||||
|
|
||||||
assert_eq!(instruction.operation(), Operation::Jump);
|
|
||||||
|
|
||||||
assert_eq!(instruction.b(), 4);
|
|
||||||
assert!(instruction.c_as_boolean());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn call() {
|
|
||||||
let instruction = Instruction::call(1, 3, 4);
|
|
||||||
|
|
||||||
assert_eq!(instruction.operation(), Operation::Call);
|
|
||||||
assert_eq!(instruction.a(), 1);
|
|
||||||
assert_eq!(instruction.b(), 3);
|
|
||||||
assert_eq!(instruction.c(), 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn r#return() {
|
|
||||||
let instruction = Instruction::r#return(true);
|
|
||||||
|
|
||||||
assert_eq!(instruction.operation(), Operation::Return);
|
|
||||||
assert!(instruction.b_as_boolean());
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,44 +0,0 @@
|
|||||||
//! The Dust programming language library.
|
|
||||||
|
|
||||||
pub mod chunk;
|
|
||||||
pub mod compiler;
|
|
||||||
pub mod disassembler;
|
|
||||||
pub mod dust_error;
|
|
||||||
pub mod formatter;
|
|
||||||
pub mod instruction;
|
|
||||||
pub mod lexer;
|
|
||||||
pub mod native_function;
|
|
||||||
pub mod operation;
|
|
||||||
pub mod optimizer;
|
|
||||||
pub mod token;
|
|
||||||
pub mod r#type;
|
|
||||||
pub mod value;
|
|
||||||
pub mod vm;
|
|
||||||
|
|
||||||
pub use crate::chunk::{Chunk, ChunkError, Local, Scope};
|
|
||||||
pub use crate::compiler::{compile, CompileError, Compiler};
|
|
||||||
pub use crate::disassembler::Disassembler;
|
|
||||||
pub use crate::dust_error::{AnnotatedError, DustError};
|
|
||||||
pub use crate::formatter::{format, Formatter};
|
|
||||||
pub use crate::instruction::Instruction;
|
|
||||||
pub use crate::lexer::{lex, LexError, Lexer};
|
|
||||||
pub use crate::native_function::{NativeFunction, NativeFunctionError};
|
|
||||||
pub use crate::operation::Operation;
|
|
||||||
pub use crate::optimizer::{optimize, Optimizer};
|
|
||||||
pub use crate::r#type::{EnumType, FunctionType, RangeableType, StructType, Type, TypeConflict};
|
|
||||||
pub use crate::token::{Token, TokenKind, TokenOwned};
|
|
||||||
pub use crate::value::{ConcreteValue, Function, Value, ValueError};
|
|
||||||
pub use crate::vm::{run, Vm, VmError};
|
|
||||||
|
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
|
||||||
pub struct Span(pub usize, pub usize);
|
|
||||||
|
|
||||||
impl Display for Span {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "({}, {})", self.0, self.1)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,418 +0,0 @@
|
|||||||
//! Built-in functions that implement extended functionality.
|
|
||||||
//!
|
|
||||||
//! Native functions are used either to implement features that are not possible to implement in
|
|
||||||
//! Dust itself or that are more efficient to implement in Rust.
|
|
||||||
use std::{
|
|
||||||
fmt::{self, Display, Formatter},
|
|
||||||
io::{self, stdin, stdout, Write},
|
|
||||||
string::{self},
|
|
||||||
};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
AnnotatedError, ConcreteValue, FunctionType, Instruction, Span, Type, Value, Vm, VmError,
|
|
||||||
};
|
|
||||||
|
|
||||||
macro_rules! define_native_function {
|
|
||||||
($(($name:ident, $byte:literal, $str:expr, $type:expr)),*) => {
|
|
||||||
/// A dust-native function.
|
|
||||||
///
|
|
||||||
/// See the [module-level documentation](index.html) for more information.
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
|
||||||
pub enum NativeFunction {
|
|
||||||
$(
|
|
||||||
$name = $byte as isize,
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NativeFunction {
|
|
||||||
pub fn as_str(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
$(
|
|
||||||
NativeFunction::$name => $str,
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::should_implement_trait)]
|
|
||||||
pub fn from_str(string: &str) -> Option<Self> {
|
|
||||||
match string {
|
|
||||||
$(
|
|
||||||
$str => Some(NativeFunction::$name),
|
|
||||||
)*
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn r#type(&self) -> FunctionType {
|
|
||||||
match self {
|
|
||||||
$(
|
|
||||||
NativeFunction::$name => $type,
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn returns_value(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
$(
|
|
||||||
NativeFunction::$name => $type.return_type.is_some(),
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u8> for NativeFunction {
|
|
||||||
fn from(byte: u8) -> Self {
|
|
||||||
match byte {
|
|
||||||
$(
|
|
||||||
$byte => NativeFunction::$name,
|
|
||||||
)*
|
|
||||||
_ => {
|
|
||||||
if cfg!(test) {
|
|
||||||
panic!("Invalid native function byte: {}", byte)
|
|
||||||
} else {
|
|
||||||
NativeFunction::Panic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<NativeFunction> for u8 {
|
|
||||||
fn from(native_function: NativeFunction) -> Self {
|
|
||||||
match native_function {
|
|
||||||
$(
|
|
||||||
NativeFunction::$name => $byte,
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
define_native_function! {
|
|
||||||
// Assertion
|
|
||||||
(
|
|
||||||
Assert,
|
|
||||||
0_u8,
|
|
||||||
"assert",
|
|
||||||
FunctionType {
|
|
||||||
type_parameters: None,
|
|
||||||
value_parameters: None,
|
|
||||||
return_type: None
|
|
||||||
}
|
|
||||||
),
|
|
||||||
// (AssertEqual, 1_u8, "assert_equal", false),
|
|
||||||
// (AssertNotEqual, 2_u8, "assert_not_equal", false),
|
|
||||||
(
|
|
||||||
Panic,
|
|
||||||
3_u8,
|
|
||||||
"panic",
|
|
||||||
FunctionType {
|
|
||||||
type_parameters: None,
|
|
||||||
value_parameters: None,
|
|
||||||
return_type: Some(Box::new(Type::Any))
|
|
||||||
}
|
|
||||||
),
|
|
||||||
|
|
||||||
// // Type conversion
|
|
||||||
// (Parse, 4_u8, "parse", true),
|
|
||||||
// (ToByte, 5_u8, "to_byte", true),
|
|
||||||
// (ToFloat, 6_u8, "to_float", true),
|
|
||||||
// (ToInteger, 7_u8, "to_integer", true),
|
|
||||||
(
|
|
||||||
ToString,
|
|
||||||
8_u8,
|
|
||||||
"to_string",
|
|
||||||
FunctionType {
|
|
||||||
type_parameters: None,
|
|
||||||
value_parameters: Some(vec![(0, Type::Any)]),
|
|
||||||
return_type: Some(Box::new(Type::String { length: None }))
|
|
||||||
}
|
|
||||||
),
|
|
||||||
|
|
||||||
// // List and string
|
|
||||||
// (All, 9_u8, "all", true),
|
|
||||||
// (Any, 10_u8, "any", true),
|
|
||||||
// (Append, 11_u8, "append", false),
|
|
||||||
// (Contains, 12_u8, "contains", true),
|
|
||||||
// (Dedup, 13_u8, "dedup", false),
|
|
||||||
// (EndsWith, 14_u8, "ends_with", true),
|
|
||||||
// (Find, 15_u8, "find", true),
|
|
||||||
// (Get, 16_u8, "get", true),
|
|
||||||
// (IndexOf, 17_u8, "index_of", true),
|
|
||||||
// (Length, 18_u8, "length", true),
|
|
||||||
// (Prepend, 19_u8, "prepend", false),
|
|
||||||
// (Replace, 20_u8, "replace", false),
|
|
||||||
// (Set, 21_u8, "set", false),
|
|
||||||
// (StartsWith, 22_u8, "starts_with", true),
|
|
||||||
// (Slice, 23_u8, "slice", true),
|
|
||||||
// (Sort, 24_u8, "sort", false),
|
|
||||||
// (Split, 25_u8, "split", true),
|
|
||||||
|
|
||||||
// // List
|
|
||||||
// (Flatten, 26_u8, "flatten", false),
|
|
||||||
// (Join, 27_u8, "join", true),
|
|
||||||
// (Map, 28_u8, "map", true),
|
|
||||||
// (Reduce, 29_u8, "reduce", true),
|
|
||||||
// (Remove, 30_u8, "remove", false),
|
|
||||||
// (Reverse, 31_u8, "reverse", false),
|
|
||||||
// (Unzip, 32_u8, "unzip", true),
|
|
||||||
// (Zip, 33_u8, "zip", true),
|
|
||||||
|
|
||||||
// // String
|
|
||||||
// (Bytes, 34_u8, "bytes", true),
|
|
||||||
// (CharAt, 35_u8, "char_at", true),
|
|
||||||
// (CharCodeAt, 36_u8, "char_code_at", true),
|
|
||||||
// (Chars, 37_u8, "chars", true),
|
|
||||||
// (Format, 38_u8, "format", true),
|
|
||||||
// (Repeat, 39_u8, "repeat", true),
|
|
||||||
// (SplitAt, 40_u8, "split_at", true),
|
|
||||||
// (SplitLines, 41_u8, "split_lines", true),
|
|
||||||
// (SplitWhitespace, 42_u8, "split_whitespace", true),
|
|
||||||
// (ToLowerCase, 43_u8, "to_lower_case", true),
|
|
||||||
// (ToUpperCase, 44_u8, "to_upper_case", true),
|
|
||||||
// (Trim, 45_u8, "trim", true),
|
|
||||||
// (TrimEnd, 46_u8, "trim_end", true),
|
|
||||||
// (TrimStart, 47_u8, "trim_start", true),
|
|
||||||
|
|
||||||
// // I/O
|
|
||||||
// // Read
|
|
||||||
// (Read, 48_u8, "read", true),
|
|
||||||
// (ReadFile, 49_u8, "read_file", true),
|
|
||||||
(
|
|
||||||
ReadLine,
|
|
||||||
50_u8,
|
|
||||||
"read_line",
|
|
||||||
FunctionType {
|
|
||||||
type_parameters: None,
|
|
||||||
value_parameters: None,
|
|
||||||
return_type: Some(Box::new(Type::String { length: None }))
|
|
||||||
}
|
|
||||||
),
|
|
||||||
// (ReadTo, 51_u8, "read_to", false),
|
|
||||||
// (ReadUntil, 52_u8, "read_until", true),
|
|
||||||
// // Write
|
|
||||||
// (AppendFile, 53_u8, "append_file", false),
|
|
||||||
// (PrependFile, 54_u8, "prepend_file", false),
|
|
||||||
(
|
|
||||||
Write,
|
|
||||||
55_u8,
|
|
||||||
"write",
|
|
||||||
FunctionType {
|
|
||||||
type_parameters: None,
|
|
||||||
value_parameters: Some(vec![(0, Type::String { length: None })]),
|
|
||||||
return_type: None
|
|
||||||
}
|
|
||||||
),
|
|
||||||
// (WriteFile, 56_u8, "write_file", false),
|
|
||||||
(
|
|
||||||
WriteLine,
|
|
||||||
57_u8,
|
|
||||||
"write_line",
|
|
||||||
FunctionType {
|
|
||||||
type_parameters: None,
|
|
||||||
value_parameters: Some(vec![(0, Type::String { length: None })]),
|
|
||||||
return_type: None
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// // Random
|
|
||||||
// (Random, 58_u8, "random", true),
|
|
||||||
// (RandomInRange, 59_u8, "random_in_range", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NativeFunction {
|
|
||||||
pub fn call(
|
|
||||||
&self,
|
|
||||||
instruction: Instruction,
|
|
||||||
vm: &Vm,
|
|
||||||
position: Span,
|
|
||||||
) -> Result<Option<Value>, VmError> {
|
|
||||||
let to_register = instruction.a();
|
|
||||||
let argument_count = instruction.c();
|
|
||||||
|
|
||||||
let return_value = match self {
|
|
||||||
NativeFunction::Panic => {
|
|
||||||
let message = if argument_count == 0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let mut message = String::new();
|
|
||||||
|
|
||||||
for argument_index in 0..argument_count {
|
|
||||||
if argument_index != 0 {
|
|
||||||
message.push(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
let argument = vm.open_register(argument_index, position)?;
|
|
||||||
|
|
||||||
message.push_str(&argument.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(message)
|
|
||||||
};
|
|
||||||
|
|
||||||
return Err(VmError::NativeFunction(NativeFunctionError::Panic {
|
|
||||||
message,
|
|
||||||
position,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type conversion
|
|
||||||
NativeFunction::ToString => {
|
|
||||||
let mut string = String::new();
|
|
||||||
|
|
||||||
for argument_index in 0..argument_count {
|
|
||||||
let argument = vm.open_register(argument_index, position)?;
|
|
||||||
|
|
||||||
string.push_str(&argument.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Value::Concrete(ConcreteValue::String(string)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// I/O
|
|
||||||
NativeFunction::ReadLine => {
|
|
||||||
let mut buffer = String::new();
|
|
||||||
|
|
||||||
stdin().read_line(&mut buffer).map_err(|io_error| {
|
|
||||||
VmError::NativeFunction(NativeFunctionError::Io {
|
|
||||||
error: io_error.kind(),
|
|
||||||
position,
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
buffer = buffer.trim_end_matches('\n').to_string();
|
|
||||||
|
|
||||||
Some(Value::Concrete(ConcreteValue::String(buffer)))
|
|
||||||
}
|
|
||||||
NativeFunction::Write => {
|
|
||||||
let to_register = instruction.a();
|
|
||||||
let mut stdout = stdout();
|
|
||||||
let map_err = |io_error: io::Error| {
|
|
||||||
VmError::NativeFunction(NativeFunctionError::Io {
|
|
||||||
error: io_error.kind(),
|
|
||||||
position,
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let first_argument = to_register.saturating_sub(argument_count);
|
|
||||||
let last_argument = to_register.saturating_sub(1);
|
|
||||||
|
|
||||||
for argument_index in first_argument..=last_argument {
|
|
||||||
if argument_index != first_argument {
|
|
||||||
stdout.write(b" ").map_err(map_err)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let argument_string = vm.open_register(argument_index, position)?.to_string();
|
|
||||||
|
|
||||||
stdout
|
|
||||||
.write_all(argument_string.as_bytes())
|
|
||||||
.map_err(map_err)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
NativeFunction::WriteLine => {
|
|
||||||
let mut stdout = stdout();
|
|
||||||
let map_err = |io_error: io::Error| {
|
|
||||||
VmError::NativeFunction(NativeFunctionError::Io {
|
|
||||||
error: io_error.kind(),
|
|
||||||
position,
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let first_index = to_register.saturating_sub(argument_count);
|
|
||||||
let arguments = vm.open_nonempty_registers(first_index..to_register, position)?;
|
|
||||||
|
|
||||||
for (index, argument) in arguments.into_iter().enumerate() {
|
|
||||||
if index != 0 {
|
|
||||||
stdout.write(b" ").map_err(map_err)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Value::Concrete(ConcreteValue::String(string)) = argument {
|
|
||||||
let bytes = string.as_bytes();
|
|
||||||
|
|
||||||
stdout.write_all(bytes).map_err(map_err)?;
|
|
||||||
} else {
|
|
||||||
let bytes = argument.to_string().into_bytes();
|
|
||||||
|
|
||||||
stdout.write_all(&bytes).map_err(map_err)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stdout.write(b"\n").map_err(map_err)?;
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
_ => todo!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(return_value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for NativeFunction {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub enum NativeFunctionError {
|
|
||||||
ExpectedArgumentCount {
|
|
||||||
expected: usize,
|
|
||||||
found: usize,
|
|
||||||
position: Span,
|
|
||||||
},
|
|
||||||
Panic {
|
|
||||||
message: Option<String>,
|
|
||||||
position: Span,
|
|
||||||
},
|
|
||||||
Parse {
|
|
||||||
error: string::ParseError,
|
|
||||||
position: Span,
|
|
||||||
},
|
|
||||||
Io {
|
|
||||||
error: io::ErrorKind,
|
|
||||||
position: Span,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AnnotatedError for NativeFunctionError {
|
|
||||||
fn title() -> &'static str {
|
|
||||||
"Native Function Error"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn description(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
NativeFunctionError::ExpectedArgumentCount { .. } => {
|
|
||||||
"Expected a different number of arguments"
|
|
||||||
}
|
|
||||||
NativeFunctionError::Panic { .. } => "Explicit panic",
|
|
||||||
NativeFunctionError::Parse { .. } => "Failed to parse value",
|
|
||||||
NativeFunctionError::Io { .. } => "I/O error",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn details(&self) -> Option<String> {
|
|
||||||
match self {
|
|
||||||
NativeFunctionError::ExpectedArgumentCount {
|
|
||||||
expected, found, ..
|
|
||||||
} => Some(format!("Expected {} arguments, found {}", expected, found)),
|
|
||||||
NativeFunctionError::Panic { message, .. } => message.clone(),
|
|
||||||
NativeFunctionError::Parse { error, .. } => Some(format!("{}", error)),
|
|
||||||
NativeFunctionError::Io { error, .. } => Some(format!("{}", error)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn position(&self) -> Span {
|
|
||||||
match self {
|
|
||||||
NativeFunctionError::ExpectedArgumentCount { position, .. } => *position,
|
|
||||||
NativeFunctionError::Panic { position, .. } => *position,
|
|
||||||
NativeFunctionError::Parse { position, .. } => *position,
|
|
||||||
NativeFunctionError::Io { position, .. } => *position,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,110 +0,0 @@
|
|||||||
//! Part of an [Instruction][crate::Instruction], which can be executed by the Dust virtual machine.
|
|
||||||
//!
|
|
||||||
//! !!! Warning !!!
|
|
||||||
//! The byte values of the operations matter. The seventh and eighth bits must be zero so that the
|
|
||||||
//! [Instruction][crate::Instruction] type can use them as flags.
|
|
||||||
|
|
||||||
use std::fmt::{self, Display, Formatter};
|
|
||||||
|
|
||||||
macro_rules! define_operation {
|
|
||||||
($(($name:ident, $byte:literal, $str:expr, $type:expr)),*) => {
|
|
||||||
/// Part of an [Instruction][crate::Instruction], which can be executed by the Dust virtual machine.)
|
|
||||||
///
|
|
||||||
/// See the [module-level documentation](index.html) for more information.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
||||||
pub enum Operation {
|
|
||||||
$(
|
|
||||||
$name = $byte as isize,
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u8> for Operation {
|
|
||||||
fn from(byte: u8) -> Self {
|
|
||||||
match byte {
|
|
||||||
$(
|
|
||||||
$byte => Operation::$name,
|
|
||||||
)*
|
|
||||||
_ => {
|
|
||||||
if cfg!(test) {
|
|
||||||
panic!("Invalid operation byte: {}", byte)
|
|
||||||
} else {
|
|
||||||
Operation::Return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Operation> for u8 {
|
|
||||||
fn from(operation: Operation) -> Self {
|
|
||||||
match operation {
|
|
||||||
$(
|
|
||||||
Operation::$name => $byte,
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Operation {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
$(
|
|
||||||
Operation::$name => write!(f, "{}", $str),
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
define_operation! {
|
|
||||||
(Move, 0b0000_0000, "MOVE", None),
|
|
||||||
(Close, 0b000_0001, "CLOSE", None),
|
|
||||||
(LoadBoolean, 0b0000_0010, "LOAD_BOOLEAN", None),
|
|
||||||
(LoadConstant, 0b0000_0011, "LOAD_CONSTANT", None),
|
|
||||||
(LoadList, 0b0000_0100, "LOAD_LIST", None),
|
|
||||||
(LoadSelf, 0b0000_0101, "LOAD_SELF", None),
|
|
||||||
(DefineLocal, 0b0000_0110, "DEFINE_LOCAL", None),
|
|
||||||
(GetLocal, 0b0000_0111, "GET_LOCAL", None),
|
|
||||||
(SetLocal, 0b0000_1000, "SET_LOCAL", None),
|
|
||||||
(Add, 0b0000_1001, "ADD", None),
|
|
||||||
(Subtract, 0b0000_1010, "SUBTRACT", None),
|
|
||||||
(Multiply, 0b0000_1011, "MULTIPLY", None),
|
|
||||||
(Divide, 0b0000_1100, "DIVIDE", None),
|
|
||||||
(Modulo, 0b0000_1101, "MODULO", None),
|
|
||||||
(Test, 0b0000_1110, "TEST", None),
|
|
||||||
(TestSet, 0b0000_1111, "TEST_SET", None),
|
|
||||||
(Equal, 0b0001_0000, "EQUAL", None),
|
|
||||||
(Less, 0b0001_0001, "LESS", None),
|
|
||||||
(LessEqual, 0b0001_0010, "LESS_EQUAL", None),
|
|
||||||
(Negate, 0b0001_0011, "NEGATE", None),
|
|
||||||
(Not, 0b0001_0100, "NOT", None),
|
|
||||||
(Jump, 0b0001_0101, "JUMP", None),
|
|
||||||
(Call, 0b0001_0110, "CALL", None),
|
|
||||||
(CallNative, 0b0001_0111, "CALL_NATIVE", None),
|
|
||||||
(Return, 0b0001_1000, "RETURN", None)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Operation {
|
|
||||||
pub fn is_math(&self) -> bool {
|
|
||||||
matches!(
|
|
||||||
self,
|
|
||||||
Operation::Add
|
|
||||||
| Operation::Subtract
|
|
||||||
| Operation::Multiply
|
|
||||||
| Operation::Divide
|
|
||||||
| Operation::Modulo
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_comparison(&self) -> bool {
|
|
||||||
matches!(
|
|
||||||
self,
|
|
||||||
Operation::Equal | Operation::Less | Operation::LessEqual
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_test(&self) -> bool {
|
|
||||||
matches!(self, Operation::Test | Operation::TestSet)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
//! Tools used by the compiler to optimize a chunk's bytecode.
|
|
||||||
use std::{iter::Map, slice::Iter};
|
|
||||||
|
|
||||||
use crate::{Instruction, Operation, Span};
|
|
||||||
|
|
||||||
type MapToOperation = fn(&(Instruction, Span)) -> Operation;
|
|
||||||
|
|
||||||
type OperationIter<'iter> = Map<Iter<'iter, (Instruction, Span)>, MapToOperation>;
|
|
||||||
|
|
||||||
/// Performs optimizations on a subset of instructions.
|
|
||||||
pub fn optimize(instructions: &mut [(Instruction, Span)]) -> usize {
|
|
||||||
Optimizer::new(instructions).optimize()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An instruction optimizer that mutably borrows instructions from a chunk.
|
|
||||||
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
|
|
||||||
pub struct Optimizer<'chunk> {
|
|
||||||
instructions: &'chunk mut [(Instruction, Span)],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'chunk> Optimizer<'chunk> {
|
|
||||||
/// Creates a new optimizer with a mutable reference to some of a chunk's instructions.
|
|
||||||
pub fn new(instructions: &'chunk mut [(Instruction, Span)]) -> Self {
|
|
||||||
Self { instructions }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Potentially mutates the instructions to optimize them.
|
|
||||||
pub fn optimize(&mut self) -> usize {
|
|
||||||
let mut optimizations = 0;
|
|
||||||
|
|
||||||
if matches!(
|
|
||||||
self.get_operations(),
|
|
||||||
Some([
|
|
||||||
Operation::Equal | Operation::Less | Operation::LessEqual,
|
|
||||||
Operation::Jump,
|
|
||||||
Operation::LoadBoolean | Operation::LoadConstant,
|
|
||||||
Operation::LoadBoolean | Operation::LoadConstant,
|
|
||||||
])
|
|
||||||
) {
|
|
||||||
self.optimize_comparison();
|
|
||||||
|
|
||||||
optimizations += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
optimizations
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Optimizes a comparison operation.
|
|
||||||
///
|
|
||||||
/// The instructions must be in the following order:
|
|
||||||
/// - `Operation::Equal | Operation::Less | Operation::LessEqual`
|
|
||||||
/// - `Operation::Jump`
|
|
||||||
/// - `Operation::LoadBoolean | Operation::LoadConstant`
|
|
||||||
/// - `Operation::LoadBoolean | Operation::LoadConstant`
|
|
||||||
fn optimize_comparison(&mut self) {
|
|
||||||
log::debug!("Optimizing comparison");
|
|
||||||
|
|
||||||
let first_loader_register = {
|
|
||||||
let first_loader = &mut self.instructions[2].0;
|
|
||||||
|
|
||||||
first_loader.set_c_to_boolean(true);
|
|
||||||
first_loader.a()
|
|
||||||
};
|
|
||||||
|
|
||||||
let second_loader = &mut self.instructions[3].0;
|
|
||||||
let mut second_loader_new = Instruction::with_operation(second_loader.operation());
|
|
||||||
|
|
||||||
second_loader_new.set_a(first_loader_register);
|
|
||||||
second_loader_new.set_b(second_loader.b());
|
|
||||||
second_loader_new.set_c(second_loader.c());
|
|
||||||
second_loader_new.set_b_to_boolean(second_loader.b_is_constant());
|
|
||||||
second_loader_new.set_c_to_boolean(second_loader.c_is_constant());
|
|
||||||
|
|
||||||
*second_loader = second_loader_new;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn operations_iter(&self) -> OperationIter {
|
|
||||||
self.instructions
|
|
||||||
.iter()
|
|
||||||
.map(|(instruction, _)| instruction.operation())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_operations<const COUNT: usize>(&self) -> Option<[Operation; COUNT]> {
|
|
||||||
if self.instructions.len() < COUNT {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut n_operations = [Operation::Return; COUNT];
|
|
||||||
|
|
||||||
for (nth, operation) in n_operations.iter_mut().zip(self.operations_iter()) {
|
|
||||||
*nth = operation;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(n_operations)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,642 +0,0 @@
|
|||||||
//! Token, TokenOwned and TokenKind types.
|
|
||||||
use std::fmt::{self, Display, Formatter};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
macro_rules! define_tokens {
|
|
||||||
($($variant:ident $(($data_type:ty))?),+ $(,)?) => {
|
|
||||||
/// Source token.
|
|
||||||
///
|
|
||||||
/// This is a borrowed type, i.e. some variants contain references to the source text.
|
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Default, Serialize, Deserialize)]
|
|
||||||
pub enum Token<'src> {
|
|
||||||
#[default]
|
|
||||||
Eof,
|
|
||||||
$(
|
|
||||||
$variant $(($data_type))?,
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
|
||||||
/// Data-less representation of a source token.
|
|
||||||
///
|
|
||||||
/// If a [Token] borrows from the source text, its TokenKind omits the data.
|
|
||||||
pub enum TokenKind {
|
|
||||||
Eof,
|
|
||||||
$(
|
|
||||||
$variant,
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
define_tokens! {
|
|
||||||
// Hard-coded values
|
|
||||||
Boolean(&'src str),
|
|
||||||
Byte(&'src str),
|
|
||||||
Character(char),
|
|
||||||
Float(&'src str),
|
|
||||||
Identifier(&'src str),
|
|
||||||
Integer(&'src str),
|
|
||||||
String(&'src str),
|
|
||||||
|
|
||||||
// Keywords
|
|
||||||
Async,
|
|
||||||
Bool,
|
|
||||||
Break,
|
|
||||||
Else,
|
|
||||||
FloatKeyword,
|
|
||||||
Fn,
|
|
||||||
If,
|
|
||||||
Int,
|
|
||||||
Let,
|
|
||||||
Loop,
|
|
||||||
Map,
|
|
||||||
Mut,
|
|
||||||
Return,
|
|
||||||
Str,
|
|
||||||
Struct,
|
|
||||||
While,
|
|
||||||
|
|
||||||
// Symbols
|
|
||||||
ArrowThin,
|
|
||||||
BangEqual,
|
|
||||||
Bang,
|
|
||||||
Colon,
|
|
||||||
Comma,
|
|
||||||
Dot,
|
|
||||||
DoubleAmpersand,
|
|
||||||
DoubleDot,
|
|
||||||
DoubleEqual,
|
|
||||||
DoublePipe,
|
|
||||||
Equal,
|
|
||||||
Greater,
|
|
||||||
GreaterEqual,
|
|
||||||
LeftCurlyBrace,
|
|
||||||
LeftParenthesis,
|
|
||||||
LeftSquareBrace,
|
|
||||||
Less,
|
|
||||||
LessEqual,
|
|
||||||
Minus,
|
|
||||||
MinusEqual,
|
|
||||||
Percent,
|
|
||||||
PercentEqual,
|
|
||||||
Plus,
|
|
||||||
PlusEqual,
|
|
||||||
RightCurlyBrace,
|
|
||||||
RightParenthesis,
|
|
||||||
RightSquareBrace,
|
|
||||||
Semicolon,
|
|
||||||
Slash,
|
|
||||||
SlashEqual,
|
|
||||||
Star,
|
|
||||||
StarEqual,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'src> Token<'src> {
|
|
||||||
#[allow(clippy::len_without_is_empty)]
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
match self {
|
|
||||||
Token::Eof => 0,
|
|
||||||
Token::Boolean(text) => text.len(),
|
|
||||||
Token::Byte(_) => 3,
|
|
||||||
Token::Character(_) => 3,
|
|
||||||
Token::Float(text) => text.len(),
|
|
||||||
Token::Identifier(text) => text.len(),
|
|
||||||
Token::Integer(text) => text.len(),
|
|
||||||
Token::String(text) => text.len() + 2,
|
|
||||||
Token::Async => 5,
|
|
||||||
Token::ArrowThin => 2,
|
|
||||||
Token::Bool => 4,
|
|
||||||
Token::Break => 5,
|
|
||||||
Token::Else => 4,
|
|
||||||
Token::FloatKeyword => 5,
|
|
||||||
Token::Fn => 2,
|
|
||||||
Token::If => 2,
|
|
||||||
Token::Int => 3,
|
|
||||||
Token::Let => 3,
|
|
||||||
Token::Loop => 4,
|
|
||||||
Token::Map => 3,
|
|
||||||
Token::Mut => 3,
|
|
||||||
Token::Str => 3,
|
|
||||||
Token::Struct => 6,
|
|
||||||
Token::While => 5,
|
|
||||||
Token::BangEqual => 2,
|
|
||||||
Token::Bang => 1,
|
|
||||||
Token::Colon => 1,
|
|
||||||
Token::Comma => 1,
|
|
||||||
Token::Dot => 1,
|
|
||||||
Token::DoubleAmpersand => 2,
|
|
||||||
Token::DoubleDot => 2,
|
|
||||||
Token::DoubleEqual => 2,
|
|
||||||
Token::DoublePipe => 2,
|
|
||||||
Token::Equal => 1,
|
|
||||||
Token::Greater => 1,
|
|
||||||
Token::GreaterEqual => 2,
|
|
||||||
Token::LeftCurlyBrace => 1,
|
|
||||||
Token::LeftParenthesis => 1,
|
|
||||||
Token::LeftSquareBrace => 1,
|
|
||||||
Token::Less => 1,
|
|
||||||
Token::LessEqual => 2,
|
|
||||||
Token::Minus => 1,
|
|
||||||
Token::MinusEqual => 2,
|
|
||||||
Token::Percent => 1,
|
|
||||||
Token::PercentEqual => 2,
|
|
||||||
Token::Plus => 1,
|
|
||||||
Token::PlusEqual => 2,
|
|
||||||
Token::Return => 6,
|
|
||||||
Token::RightCurlyBrace => 1,
|
|
||||||
Token::RightParenthesis => 1,
|
|
||||||
Token::RightSquareBrace => 1,
|
|
||||||
Token::Semicolon => 1,
|
|
||||||
Token::Slash => 1,
|
|
||||||
Token::SlashEqual => 2,
|
|
||||||
Token::Star => 1,
|
|
||||||
Token::StarEqual => 2,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_str(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
Token::Eof => "",
|
|
||||||
Token::Boolean(text) => text,
|
|
||||||
Token::Byte(text) => text,
|
|
||||||
Token::Character(_) => "character token",
|
|
||||||
Token::Float(text) => text,
|
|
||||||
Token::Identifier(text) => text,
|
|
||||||
Token::Integer(text) => text,
|
|
||||||
Token::String(text) => text,
|
|
||||||
Token::Async => "async",
|
|
||||||
Token::ArrowThin => "->",
|
|
||||||
Token::Bool => "bool",
|
|
||||||
Token::Break => "break",
|
|
||||||
Token::Else => "else",
|
|
||||||
Token::FloatKeyword => "float",
|
|
||||||
Token::Fn => "fn",
|
|
||||||
Token::If => "if",
|
|
||||||
Token::Int => "int",
|
|
||||||
Token::Let => "let",
|
|
||||||
Token::Loop => "loop",
|
|
||||||
Token::Map => "map",
|
|
||||||
Token::Mut => "mut",
|
|
||||||
Token::Str => "str",
|
|
||||||
Token::Struct => "struct",
|
|
||||||
Token::While => "while",
|
|
||||||
Token::BangEqual => "!=",
|
|
||||||
Token::Bang => "!",
|
|
||||||
Token::Colon => ":",
|
|
||||||
Token::Comma => ",",
|
|
||||||
Token::Dot => ".",
|
|
||||||
Token::DoubleAmpersand => "&&",
|
|
||||||
Token::DoubleDot => "..",
|
|
||||||
Token::DoubleEqual => "==",
|
|
||||||
Token::DoublePipe => "||",
|
|
||||||
Token::Equal => "=",
|
|
||||||
Token::Greater => ">",
|
|
||||||
Token::GreaterEqual => ">=",
|
|
||||||
Token::LeftCurlyBrace => "{",
|
|
||||||
Token::LeftParenthesis => "(",
|
|
||||||
Token::LeftSquareBrace => "[",
|
|
||||||
Token::Less => "<",
|
|
||||||
Token::LessEqual => "<=",
|
|
||||||
Token::Minus => "-",
|
|
||||||
Token::MinusEqual => "-=",
|
|
||||||
Token::Percent => "%",
|
|
||||||
Token::PercentEqual => "%=",
|
|
||||||
Token::Plus => "+",
|
|
||||||
Token::PlusEqual => "+=",
|
|
||||||
Token::Return => "return",
|
|
||||||
Token::RightCurlyBrace => "}",
|
|
||||||
Token::RightParenthesis => ")",
|
|
||||||
Token::RightSquareBrace => "]",
|
|
||||||
Token::Semicolon => ";",
|
|
||||||
Token::Slash => "/",
|
|
||||||
Token::SlashEqual => "/=",
|
|
||||||
Token::Star => "*",
|
|
||||||
Token::StarEqual => "*=",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_owned(&self) -> TokenOwned {
|
|
||||||
match self {
|
|
||||||
Token::ArrowThin => TokenOwned::ArrowThin,
|
|
||||||
Token::Async => TokenOwned::Async,
|
|
||||||
Token::BangEqual => TokenOwned::BangEqual,
|
|
||||||
Token::Bang => TokenOwned::Bang,
|
|
||||||
Token::Bool => TokenOwned::Bool,
|
|
||||||
Token::Boolean(boolean) => TokenOwned::Boolean(boolean.to_string()),
|
|
||||||
Token::Break => TokenOwned::Break,
|
|
||||||
Token::Byte(byte) => TokenOwned::Byte(byte.to_string()),
|
|
||||||
Token::Character(character) => TokenOwned::Character(*character),
|
|
||||||
Token::Colon => TokenOwned::Colon,
|
|
||||||
Token::Comma => TokenOwned::Comma,
|
|
||||||
Token::Dot => TokenOwned::Dot,
|
|
||||||
Token::DoubleAmpersand => TokenOwned::DoubleAmpersand,
|
|
||||||
Token::DoubleDot => TokenOwned::DoubleDot,
|
|
||||||
Token::DoubleEqual => TokenOwned::DoubleEqual,
|
|
||||||
Token::DoublePipe => TokenOwned::DoublePipe,
|
|
||||||
Token::Else => TokenOwned::Else,
|
|
||||||
Token::Eof => TokenOwned::Eof,
|
|
||||||
Token::Equal => TokenOwned::Equal,
|
|
||||||
Token::Float(float) => TokenOwned::Float(float.to_string()),
|
|
||||||
Token::FloatKeyword => TokenOwned::FloatKeyword,
|
|
||||||
Token::Fn => TokenOwned::Fn,
|
|
||||||
Token::Greater => TokenOwned::Greater,
|
|
||||||
Token::GreaterEqual => TokenOwned::GreaterOrEqual,
|
|
||||||
Token::Identifier(text) => TokenOwned::Identifier(text.to_string()),
|
|
||||||
Token::If => TokenOwned::If,
|
|
||||||
Token::Int => TokenOwned::Int,
|
|
||||||
Token::Integer(integer) => TokenOwned::Integer(integer.to_string()),
|
|
||||||
Token::LeftCurlyBrace => TokenOwned::LeftCurlyBrace,
|
|
||||||
Token::LeftParenthesis => TokenOwned::LeftParenthesis,
|
|
||||||
Token::LeftSquareBrace => TokenOwned::LeftSquareBrace,
|
|
||||||
Token::Let => TokenOwned::Let,
|
|
||||||
Token::Less => TokenOwned::Less,
|
|
||||||
Token::LessEqual => TokenOwned::LessOrEqual,
|
|
||||||
Token::Loop => TokenOwned::Loop,
|
|
||||||
Token::Map => TokenOwned::Map,
|
|
||||||
Token::Minus => TokenOwned::Minus,
|
|
||||||
Token::MinusEqual => TokenOwned::MinusEqual,
|
|
||||||
Token::Mut => TokenOwned::Mut,
|
|
||||||
Token::Percent => TokenOwned::Percent,
|
|
||||||
Token::PercentEqual => TokenOwned::PercentEqual,
|
|
||||||
Token::Plus => TokenOwned::Plus,
|
|
||||||
Token::PlusEqual => TokenOwned::PlusEqual,
|
|
||||||
Token::Return => TokenOwned::Return,
|
|
||||||
Token::RightCurlyBrace => TokenOwned::RightCurlyBrace,
|
|
||||||
Token::RightParenthesis => TokenOwned::RightParenthesis,
|
|
||||||
Token::RightSquareBrace => TokenOwned::RightSquareBrace,
|
|
||||||
Token::Semicolon => TokenOwned::Semicolon,
|
|
||||||
Token::Star => TokenOwned::Star,
|
|
||||||
Token::StarEqual => TokenOwned::StarEqual,
|
|
||||||
Token::Slash => TokenOwned::Slash,
|
|
||||||
Token::SlashEqual => TokenOwned::SlashEqual,
|
|
||||||
Token::String(text) => TokenOwned::String(text.to_string()),
|
|
||||||
Token::Str => TokenOwned::Str,
|
|
||||||
Token::Struct => TokenOwned::Struct,
|
|
||||||
Token::While => TokenOwned::While,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn kind(&self) -> TokenKind {
|
|
||||||
match self {
|
|
||||||
Token::ArrowThin => TokenKind::ArrowThin,
|
|
||||||
Token::Async => TokenKind::Async,
|
|
||||||
Token::BangEqual => TokenKind::BangEqual,
|
|
||||||
Token::Bang => TokenKind::Bang,
|
|
||||||
Token::Bool => TokenKind::Bool,
|
|
||||||
Token::Boolean(_) => TokenKind::Boolean,
|
|
||||||
Token::Break => TokenKind::Break,
|
|
||||||
Token::Byte(_) => TokenKind::Byte,
|
|
||||||
Token::Character(_) => TokenKind::Character,
|
|
||||||
Token::Colon => TokenKind::Colon,
|
|
||||||
Token::Comma => TokenKind::Comma,
|
|
||||||
Token::Dot => TokenKind::Dot,
|
|
||||||
Token::DoubleAmpersand => TokenKind::DoubleAmpersand,
|
|
||||||
Token::DoubleDot => TokenKind::DoubleDot,
|
|
||||||
Token::DoubleEqual => TokenKind::DoubleEqual,
|
|
||||||
Token::DoublePipe => TokenKind::DoublePipe,
|
|
||||||
Token::Else => TokenKind::Else,
|
|
||||||
Token::Eof => TokenKind::Eof,
|
|
||||||
Token::Equal => TokenKind::Equal,
|
|
||||||
Token::Float(_) => TokenKind::Float,
|
|
||||||
Token::FloatKeyword => TokenKind::FloatKeyword,
|
|
||||||
Token::Fn => TokenKind::Fn,
|
|
||||||
Token::Greater => TokenKind::Greater,
|
|
||||||
Token::GreaterEqual => TokenKind::GreaterEqual,
|
|
||||||
Token::Identifier(_) => TokenKind::Identifier,
|
|
||||||
Token::If => TokenKind::If,
|
|
||||||
Token::Int => TokenKind::Int,
|
|
||||||
Token::Integer(_) => TokenKind::Integer,
|
|
||||||
Token::LeftCurlyBrace => TokenKind::LeftCurlyBrace,
|
|
||||||
Token::LeftParenthesis => TokenKind::LeftParenthesis,
|
|
||||||
Token::LeftSquareBrace => TokenKind::LeftSquareBrace,
|
|
||||||
Token::Let => TokenKind::Let,
|
|
||||||
Token::Less => TokenKind::Less,
|
|
||||||
Token::LessEqual => TokenKind::LessEqual,
|
|
||||||
Token::Loop => TokenKind::Loop,
|
|
||||||
Token::Map => TokenKind::Map,
|
|
||||||
Token::Minus => TokenKind::Minus,
|
|
||||||
Token::MinusEqual => TokenKind::MinusEqual,
|
|
||||||
Token::Mut => TokenKind::Mut,
|
|
||||||
Token::Percent => TokenKind::Percent,
|
|
||||||
Token::PercentEqual => TokenKind::PercentEqual,
|
|
||||||
Token::Plus => TokenKind::Plus,
|
|
||||||
Token::PlusEqual => TokenKind::PlusEqual,
|
|
||||||
Token::Return => TokenKind::Return,
|
|
||||||
Token::RightCurlyBrace => TokenKind::RightCurlyBrace,
|
|
||||||
Token::RightParenthesis => TokenKind::RightParenthesis,
|
|
||||||
Token::RightSquareBrace => TokenKind::RightSquareBrace,
|
|
||||||
Token::Semicolon => TokenKind::Semicolon,
|
|
||||||
Token::Star => TokenKind::Star,
|
|
||||||
Token::StarEqual => TokenKind::StarEqual,
|
|
||||||
Token::Slash => TokenKind::Slash,
|
|
||||||
Token::SlashEqual => TokenKind::SlashEqual,
|
|
||||||
Token::Str => TokenKind::Str,
|
|
||||||
Token::String(_) => TokenKind::String,
|
|
||||||
Token::Struct => TokenKind::Struct,
|
|
||||||
Token::While => TokenKind::While,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if the token yields a value, begins an expression or is an expression operator.
|
|
||||||
pub fn is_expression(&self) -> bool {
|
|
||||||
matches!(
|
|
||||||
self,
|
|
||||||
Token::Boolean(_)
|
|
||||||
| Token::Byte(_)
|
|
||||||
| Token::Character(_)
|
|
||||||
| Token::Float(_)
|
|
||||||
| Token::Identifier(_)
|
|
||||||
| Token::Integer(_)
|
|
||||||
| Token::String(_)
|
|
||||||
| Token::Break
|
|
||||||
| Token::If
|
|
||||||
| Token::Return
|
|
||||||
| Token::Map
|
|
||||||
| Token::Loop
|
|
||||||
| Token::Struct
|
|
||||||
| Token::BangEqual
|
|
||||||
| Token::DoubleAmpersand
|
|
||||||
| Token::DoubleEqual
|
|
||||||
| Token::DoublePipe
|
|
||||||
| Token::Equal
|
|
||||||
| Token::Greater
|
|
||||||
| Token::GreaterEqual
|
|
||||||
| Token::LeftCurlyBrace
|
|
||||||
| Token::LeftParenthesis
|
|
||||||
| Token::LeftSquareBrace
|
|
||||||
| Token::Less
|
|
||||||
| Token::LessEqual
|
|
||||||
| Token::Minus
|
|
||||||
| Token::MinusEqual
|
|
||||||
| Token::Percent
|
|
||||||
| Token::PercentEqual
|
|
||||||
| Token::Plus
|
|
||||||
| Token::PlusEqual
|
|
||||||
| Token::Slash
|
|
||||||
| Token::SlashEqual
|
|
||||||
| Token::Star
|
|
||||||
| Token::StarEqual
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'src> Display for Token<'src> {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Token::ArrowThin => write!(f, "->"),
|
|
||||||
Token::Async => write!(f, "async"),
|
|
||||||
Token::BangEqual => write!(f, "!="),
|
|
||||||
Token::Bang => write!(f, "!"),
|
|
||||||
Token::Bool => write!(f, "bool"),
|
|
||||||
Token::Boolean(value) => write!(f, "{value}"),
|
|
||||||
Token::Break => write!(f, "break"),
|
|
||||||
Token::Byte(value) => write!(f, "{value}"),
|
|
||||||
Token::Character(value) => write!(f, "{value}"),
|
|
||||||
Token::Colon => write!(f, ":"),
|
|
||||||
Token::Comma => write!(f, ","),
|
|
||||||
Token::Dot => write!(f, "."),
|
|
||||||
Token::DoubleAmpersand => write!(f, "&&"),
|
|
||||||
Token::DoubleDot => write!(f, ".."),
|
|
||||||
Token::DoubleEqual => write!(f, "=="),
|
|
||||||
Token::DoublePipe => write!(f, "||"),
|
|
||||||
Token::Else => write!(f, "else"),
|
|
||||||
Token::Eof => write!(f, "EOF"),
|
|
||||||
Token::Equal => write!(f, "="),
|
|
||||||
Token::Float(value) => write!(f, "{value}"),
|
|
||||||
Token::FloatKeyword => write!(f, "float"),
|
|
||||||
Token::Fn => write!(f, "fn"),
|
|
||||||
Token::Greater => write!(f, ">"),
|
|
||||||
Token::GreaterEqual => write!(f, ">="),
|
|
||||||
Token::Identifier(value) => write!(f, "{value}"),
|
|
||||||
Token::If => write!(f, "if"),
|
|
||||||
Token::Int => write!(f, "int"),
|
|
||||||
Token::Integer(value) => write!(f, "{value}"),
|
|
||||||
Token::LeftCurlyBrace => write!(f, "{{"),
|
|
||||||
Token::LeftParenthesis => write!(f, "("),
|
|
||||||
Token::LeftSquareBrace => write!(f, "["),
|
|
||||||
Token::Let => write!(f, "let"),
|
|
||||||
Token::Less => write!(f, "<"),
|
|
||||||
Token::LessEqual => write!(f, "<="),
|
|
||||||
Token::Loop => write!(f, "loop"),
|
|
||||||
Token::Map => write!(f, "map"),
|
|
||||||
Token::Minus => write!(f, "-"),
|
|
||||||
Token::MinusEqual => write!(f, "-="),
|
|
||||||
Token::Mut => write!(f, "mut"),
|
|
||||||
Token::Percent => write!(f, "%"),
|
|
||||||
Token::PercentEqual => write!(f, "%="),
|
|
||||||
Token::Plus => write!(f, "+"),
|
|
||||||
Token::PlusEqual => write!(f, "+="),
|
|
||||||
Token::Return => write!(f, "return"),
|
|
||||||
Token::RightCurlyBrace => write!(f, "}}"),
|
|
||||||
Token::RightParenthesis => write!(f, ")"),
|
|
||||||
Token::RightSquareBrace => write!(f, "]"),
|
|
||||||
Token::Semicolon => write!(f, ";"),
|
|
||||||
Token::Slash => write!(f, "/"),
|
|
||||||
Token::SlashEqual => write!(f, "/="),
|
|
||||||
Token::Star => write!(f, "*"),
|
|
||||||
Token::StarEqual => write!(f, "*="),
|
|
||||||
Token::Str => write!(f, "str"),
|
|
||||||
Token::String(value) => write!(f, "{value}"),
|
|
||||||
Token::Struct => write!(f, "struct"),
|
|
||||||
Token::While => write!(f, "while"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Owned representation of a source token.
|
|
||||||
///
|
|
||||||
/// If a [Token] borrows from the source text, its TokenOwned omits the data.
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
|
||||||
pub enum TokenOwned {
|
|
||||||
Eof,
|
|
||||||
|
|
||||||
Identifier(String),
|
|
||||||
|
|
||||||
// Hard-coded values
|
|
||||||
Boolean(String),
|
|
||||||
Byte(String),
|
|
||||||
Character(char),
|
|
||||||
Float(String),
|
|
||||||
Integer(String),
|
|
||||||
String(String),
|
|
||||||
|
|
||||||
// Keywords
|
|
||||||
Async,
|
|
||||||
Bool,
|
|
||||||
Break,
|
|
||||||
Else,
|
|
||||||
FloatKeyword,
|
|
||||||
Fn,
|
|
||||||
If,
|
|
||||||
Int,
|
|
||||||
Let,
|
|
||||||
Loop,
|
|
||||||
Map,
|
|
||||||
Mut,
|
|
||||||
Return,
|
|
||||||
Str,
|
|
||||||
While,
|
|
||||||
|
|
||||||
// Symbols
|
|
||||||
ArrowThin,
|
|
||||||
Bang,
|
|
||||||
BangEqual,
|
|
||||||
Colon,
|
|
||||||
Comma,
|
|
||||||
Dot,
|
|
||||||
DoubleAmpersand,
|
|
||||||
DoubleDot,
|
|
||||||
DoubleEqual,
|
|
||||||
DoublePipe,
|
|
||||||
Equal,
|
|
||||||
Greater,
|
|
||||||
GreaterOrEqual,
|
|
||||||
LeftCurlyBrace,
|
|
||||||
LeftParenthesis,
|
|
||||||
LeftSquareBrace,
|
|
||||||
Less,
|
|
||||||
LessOrEqual,
|
|
||||||
Minus,
|
|
||||||
MinusEqual,
|
|
||||||
Percent,
|
|
||||||
PercentEqual,
|
|
||||||
Plus,
|
|
||||||
PlusEqual,
|
|
||||||
RightCurlyBrace,
|
|
||||||
RightParenthesis,
|
|
||||||
RightSquareBrace,
|
|
||||||
Semicolon,
|
|
||||||
Star,
|
|
||||||
StarEqual,
|
|
||||||
Struct,
|
|
||||||
Slash,
|
|
||||||
SlashEqual,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for TokenOwned {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
TokenOwned::ArrowThin => Token::ArrowThin.fmt(f),
|
|
||||||
TokenOwned::Async => Token::Async.fmt(f),
|
|
||||||
TokenOwned::Bang => Token::Bang.fmt(f),
|
|
||||||
TokenOwned::BangEqual => Token::BangEqual.fmt(f),
|
|
||||||
TokenOwned::Bool => Token::Bool.fmt(f),
|
|
||||||
TokenOwned::Boolean(boolean) => Token::Boolean(boolean).fmt(f),
|
|
||||||
TokenOwned::Break => Token::Break.fmt(f),
|
|
||||||
TokenOwned::Byte(byte) => Token::Byte(byte).fmt(f),
|
|
||||||
TokenOwned::Character(character) => Token::Character(*character).fmt(f),
|
|
||||||
TokenOwned::Colon => Token::Colon.fmt(f),
|
|
||||||
TokenOwned::Comma => Token::Comma.fmt(f),
|
|
||||||
TokenOwned::Dot => Token::Dot.fmt(f),
|
|
||||||
TokenOwned::DoubleAmpersand => Token::DoubleAmpersand.fmt(f),
|
|
||||||
TokenOwned::DoubleDot => Token::DoubleDot.fmt(f),
|
|
||||||
TokenOwned::DoubleEqual => Token::DoubleEqual.fmt(f),
|
|
||||||
TokenOwned::DoublePipe => Token::DoublePipe.fmt(f),
|
|
||||||
TokenOwned::Else => Token::Else.fmt(f),
|
|
||||||
TokenOwned::Eof => Token::Eof.fmt(f),
|
|
||||||
TokenOwned::Equal => Token::Equal.fmt(f),
|
|
||||||
TokenOwned::Float(float) => Token::Float(float).fmt(f),
|
|
||||||
TokenOwned::FloatKeyword => Token::FloatKeyword.fmt(f),
|
|
||||||
TokenOwned::Fn => Token::Fn.fmt(f),
|
|
||||||
TokenOwned::Greater => Token::Greater.fmt(f),
|
|
||||||
TokenOwned::GreaterOrEqual => Token::GreaterEqual.fmt(f),
|
|
||||||
TokenOwned::Identifier(text) => Token::Identifier(text).fmt(f),
|
|
||||||
TokenOwned::If => Token::If.fmt(f),
|
|
||||||
TokenOwned::Int => Token::Int.fmt(f),
|
|
||||||
TokenOwned::Integer(integer) => Token::Integer(integer).fmt(f),
|
|
||||||
TokenOwned::LeftCurlyBrace => Token::LeftCurlyBrace.fmt(f),
|
|
||||||
TokenOwned::LeftParenthesis => Token::LeftParenthesis.fmt(f),
|
|
||||||
TokenOwned::LeftSquareBrace => Token::LeftSquareBrace.fmt(f),
|
|
||||||
TokenOwned::Let => Token::Let.fmt(f),
|
|
||||||
TokenOwned::Less => Token::Less.fmt(f),
|
|
||||||
TokenOwned::LessOrEqual => Token::LessEqual.fmt(f),
|
|
||||||
TokenOwned::Loop => Token::Loop.fmt(f),
|
|
||||||
TokenOwned::Map => Token::Map.fmt(f),
|
|
||||||
TokenOwned::Minus => Token::Minus.fmt(f),
|
|
||||||
TokenOwned::MinusEqual => Token::MinusEqual.fmt(f),
|
|
||||||
TokenOwned::Mut => Token::Mut.fmt(f),
|
|
||||||
TokenOwned::Percent => Token::Percent.fmt(f),
|
|
||||||
TokenOwned::PercentEqual => Token::PercentEqual.fmt(f),
|
|
||||||
TokenOwned::Plus => Token::Plus.fmt(f),
|
|
||||||
TokenOwned::PlusEqual => Token::PlusEqual.fmt(f),
|
|
||||||
TokenOwned::Return => Token::Return.fmt(f),
|
|
||||||
TokenOwned::RightCurlyBrace => Token::RightCurlyBrace.fmt(f),
|
|
||||||
TokenOwned::RightParenthesis => Token::RightParenthesis.fmt(f),
|
|
||||||
TokenOwned::RightSquareBrace => Token::RightSquareBrace.fmt(f),
|
|
||||||
TokenOwned::Semicolon => Token::Semicolon.fmt(f),
|
|
||||||
TokenOwned::Star => Token::Star.fmt(f),
|
|
||||||
TokenOwned::StarEqual => Token::StarEqual.fmt(f),
|
|
||||||
TokenOwned::Slash => Token::Slash.fmt(f),
|
|
||||||
TokenOwned::SlashEqual => Token::SlashEqual.fmt(f),
|
|
||||||
TokenOwned::Str => Token::Str.fmt(f),
|
|
||||||
TokenOwned::String(string) => Token::String(string).fmt(f),
|
|
||||||
TokenOwned::Struct => Token::Struct.fmt(f),
|
|
||||||
TokenOwned::While => Token::While.fmt(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for TokenKind {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
TokenKind::ArrowThin => Token::ArrowThin.fmt(f),
|
|
||||||
TokenKind::Async => Token::Async.fmt(f),
|
|
||||||
TokenKind::Bang => Token::Bang.fmt(f),
|
|
||||||
TokenKind::BangEqual => Token::BangEqual.fmt(f),
|
|
||||||
TokenKind::Bool => Token::Bool.fmt(f),
|
|
||||||
TokenKind::Boolean => write!(f, "boolean value"),
|
|
||||||
TokenKind::Break => Token::Break.fmt(f),
|
|
||||||
TokenKind::Byte => write!(f, "byte value"),
|
|
||||||
TokenKind::Character => write!(f, "character value"),
|
|
||||||
TokenKind::Colon => Token::Colon.fmt(f),
|
|
||||||
TokenKind::Comma => Token::Comma.fmt(f),
|
|
||||||
TokenKind::Dot => Token::Dot.fmt(f),
|
|
||||||
TokenKind::DoubleAmpersand => Token::DoubleAmpersand.fmt(f),
|
|
||||||
TokenKind::DoubleDot => Token::DoubleDot.fmt(f),
|
|
||||||
TokenKind::DoubleEqual => Token::DoubleEqual.fmt(f),
|
|
||||||
TokenKind::DoublePipe => Token::DoublePipe.fmt(f),
|
|
||||||
TokenKind::Else => Token::Else.fmt(f),
|
|
||||||
TokenKind::Eof => Token::Eof.fmt(f),
|
|
||||||
TokenKind::Equal => Token::Equal.fmt(f),
|
|
||||||
TokenKind::Float => write!(f, "float value"),
|
|
||||||
TokenKind::FloatKeyword => Token::FloatKeyword.fmt(f),
|
|
||||||
TokenKind::Fn => Token::Fn.fmt(f),
|
|
||||||
TokenKind::Greater => Token::Greater.fmt(f),
|
|
||||||
TokenKind::GreaterEqual => Token::GreaterEqual.fmt(f),
|
|
||||||
TokenKind::Identifier => write!(f, "identifier"),
|
|
||||||
TokenKind::If => Token::If.fmt(f),
|
|
||||||
TokenKind::Int => Token::Int.fmt(f),
|
|
||||||
TokenKind::Integer => write!(f, "integer value"),
|
|
||||||
TokenKind::LeftCurlyBrace => Token::LeftCurlyBrace.fmt(f),
|
|
||||||
TokenKind::LeftParenthesis => Token::LeftParenthesis.fmt(f),
|
|
||||||
TokenKind::LeftSquareBrace => Token::LeftSquareBrace.fmt(f),
|
|
||||||
TokenKind::Let => Token::Let.fmt(f),
|
|
||||||
TokenKind::Less => Token::Less.fmt(f),
|
|
||||||
TokenKind::LessEqual => Token::LessEqual.fmt(f),
|
|
||||||
TokenKind::Loop => Token::Loop.fmt(f),
|
|
||||||
TokenKind::Map => Token::Map.fmt(f),
|
|
||||||
TokenKind::Minus => Token::Minus.fmt(f),
|
|
||||||
TokenKind::MinusEqual => Token::MinusEqual.fmt(f),
|
|
||||||
TokenKind::Mut => Token::Mut.fmt(f),
|
|
||||||
TokenKind::Percent => Token::Percent.fmt(f),
|
|
||||||
TokenKind::PercentEqual => Token::PercentEqual.fmt(f),
|
|
||||||
TokenKind::Plus => Token::Plus.fmt(f),
|
|
||||||
TokenKind::PlusEqual => Token::PlusEqual.fmt(f),
|
|
||||||
TokenKind::Return => Token::Return.fmt(f),
|
|
||||||
TokenKind::RightCurlyBrace => Token::RightCurlyBrace.fmt(f),
|
|
||||||
TokenKind::RightParenthesis => Token::RightParenthesis.fmt(f),
|
|
||||||
TokenKind::RightSquareBrace => Token::RightSquareBrace.fmt(f),
|
|
||||||
TokenKind::Semicolon => Token::Semicolon.fmt(f),
|
|
||||||
TokenKind::Star => Token::Star.fmt(f),
|
|
||||||
TokenKind::StarEqual => Token::StarEqual.fmt(f),
|
|
||||||
TokenKind::Str => Token::Str.fmt(f),
|
|
||||||
TokenKind::Slash => Token::Slash.fmt(f),
|
|
||||||
TokenKind::SlashEqual => Token::SlashEqual.fmt(f),
|
|
||||||
TokenKind::String => write!(f, "string value"),
|
|
||||||
TokenKind::Struct => Token::Struct.fmt(f),
|
|
||||||
TokenKind::While => Token::While.fmt(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,659 +0,0 @@
|
|||||||
//! Value types and conflict handling.
|
|
||||||
use std::{
|
|
||||||
cmp::Ordering,
|
|
||||||
collections::HashMap,
|
|
||||||
fmt::{self, Display, Formatter},
|
|
||||||
};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
/// Description of a kind of value.
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub enum Type {
|
|
||||||
Any,
|
|
||||||
Boolean,
|
|
||||||
Byte,
|
|
||||||
Character,
|
|
||||||
Enum(EnumType),
|
|
||||||
Float,
|
|
||||||
Function(FunctionType),
|
|
||||||
Generic {
|
|
||||||
identifier_index: u8,
|
|
||||||
concrete_type: Option<Box<Type>>,
|
|
||||||
},
|
|
||||||
Integer,
|
|
||||||
List {
|
|
||||||
item_type: Box<Type>,
|
|
||||||
length: usize,
|
|
||||||
},
|
|
||||||
ListEmpty,
|
|
||||||
ListOf {
|
|
||||||
item_type: Box<Type>,
|
|
||||||
},
|
|
||||||
Map {
|
|
||||||
pairs: HashMap<u8, Type>,
|
|
||||||
},
|
|
||||||
Number,
|
|
||||||
Range {
|
|
||||||
r#type: RangeableType,
|
|
||||||
},
|
|
||||||
String {
|
|
||||||
length: Option<usize>,
|
|
||||||
},
|
|
||||||
Struct(StructType),
|
|
||||||
Tuple {
|
|
||||||
fields: Option<Vec<Type>>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Type {
|
|
||||||
/// Returns a concrete type, either the type itself or the concrete type of a generic type.
|
|
||||||
pub fn concrete_type(&self) -> &Type {
|
|
||||||
if let Type::Generic {
|
|
||||||
concrete_type: Some(concrete_type),
|
|
||||||
..
|
|
||||||
} = self
|
|
||||||
{
|
|
||||||
concrete_type.concrete_type()
|
|
||||||
} else {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks that the type is compatible with another type.
|
|
||||||
pub fn check(&self, other: &Type) -> Result<(), TypeConflict> {
|
|
||||||
match (self.concrete_type(), other.concrete_type()) {
|
|
||||||
(Type::Any, _)
|
|
||||||
| (_, Type::Any)
|
|
||||||
| (Type::Boolean, Type::Boolean)
|
|
||||||
| (Type::Byte, Type::Byte)
|
|
||||||
| (Type::Character, Type::Character)
|
|
||||||
| (Type::Float, Type::Float)
|
|
||||||
| (Type::Integer, Type::Integer)
|
|
||||||
| (Type::String { .. }, Type::String { .. }) => return Ok(()),
|
|
||||||
(
|
|
||||||
Type::Generic {
|
|
||||||
concrete_type: left,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
Type::Generic {
|
|
||||||
concrete_type: right,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
) => match (left, right) {
|
|
||||||
(Some(left), Some(right)) => {
|
|
||||||
if left.check(right).is_ok() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(None, None) => {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
(Type::Generic { concrete_type, .. }, other)
|
|
||||||
| (other, Type::Generic { concrete_type, .. }) => {
|
|
||||||
if let Some(concrete_type) = concrete_type {
|
|
||||||
if other == concrete_type.as_ref() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Type::Struct(left_struct_type), Type::Struct(right_struct_type)) => {
|
|
||||||
if left_struct_type == right_struct_type {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(
|
|
||||||
Type::List {
|
|
||||||
item_type: left_type,
|
|
||||||
length: left_length,
|
|
||||||
},
|
|
||||||
Type::List {
|
|
||||||
item_type: right_type,
|
|
||||||
length: right_length,
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
if left_length != right_length {
|
|
||||||
return Err(TypeConflict {
|
|
||||||
actual: other.clone(),
|
|
||||||
expected: self.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if left_type.check(right_type).is_err() {
|
|
||||||
return Err(TypeConflict {
|
|
||||||
actual: other.clone(),
|
|
||||||
expected: self.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
(
|
|
||||||
Type::ListOf {
|
|
||||||
item_type: left_type,
|
|
||||||
},
|
|
||||||
Type::ListOf {
|
|
||||||
item_type: right_type,
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
if left_type.check(right_type).is_err() {
|
|
||||||
return Err(TypeConflict {
|
|
||||||
actual: other.clone(),
|
|
||||||
expected: self.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(
|
|
||||||
Type::List {
|
|
||||||
item_type: list_item_type,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
Type::ListOf {
|
|
||||||
item_type: list_of_item_type,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
| (
|
|
||||||
Type::ListOf {
|
|
||||||
item_type: list_of_item_type,
|
|
||||||
},
|
|
||||||
Type::List {
|
|
||||||
item_type: list_item_type,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
// TODO: This is a hack, remove it.
|
|
||||||
if let Type::Any = **list_of_item_type {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
if list_item_type.check(list_of_item_type).is_err() {
|
|
||||||
return Err(TypeConflict {
|
|
||||||
actual: other.clone(),
|
|
||||||
expected: self.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(
|
|
||||||
Type::Function(FunctionType {
|
|
||||||
type_parameters: left_type_parameters,
|
|
||||||
value_parameters: left_value_parameters,
|
|
||||||
return_type: left_return,
|
|
||||||
}),
|
|
||||||
Type::Function(FunctionType {
|
|
||||||
type_parameters: right_type_parameters,
|
|
||||||
value_parameters: right_value_parameters,
|
|
||||||
return_type: right_return,
|
|
||||||
}),
|
|
||||||
) => {
|
|
||||||
if left_return != right_return
|
|
||||||
|| left_type_parameters != right_type_parameters
|
|
||||||
|| left_value_parameters != right_value_parameters
|
|
||||||
{
|
|
||||||
return Err(TypeConflict {
|
|
||||||
actual: other.clone(),
|
|
||||||
expected: self.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
(Type::Range { r#type: left_type }, Type::Range { r#type: right_type }) => {
|
|
||||||
if left_type == right_type {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Type::Number, Type::Number | Type::Integer | Type::Float)
|
|
||||||
| (Type::Integer | Type::Float, Type::Number) => {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(TypeConflict {
|
|
||||||
actual: other.clone(),
|
|
||||||
expected: self.clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Type {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Type::Any => write!(f, "any"),
|
|
||||||
Type::Boolean => write!(f, "bool"),
|
|
||||||
Type::Byte => write!(f, "byte"),
|
|
||||||
Type::Character => write!(f, "char"),
|
|
||||||
Type::Enum(EnumType { name, .. }) => write!(f, "{name}"),
|
|
||||||
Type::Float => write!(f, "float"),
|
|
||||||
Type::Function(function_type) => write!(f, "{function_type}"),
|
|
||||||
Type::Generic { concrete_type, .. } => {
|
|
||||||
match concrete_type.clone().map(|r#box| *r#box) {
|
|
||||||
Some(Type::Generic {
|
|
||||||
identifier_index: identifier,
|
|
||||||
..
|
|
||||||
}) => write!(f, "{identifier}"),
|
|
||||||
Some(concrete_type) => write!(f, "implied to be {concrete_type}"),
|
|
||||||
None => write!(f, "unknown"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Type::Integer => write!(f, "int"),
|
|
||||||
Type::List { item_type, length } => write!(f, "[{item_type}; {length}]"),
|
|
||||||
Type::ListEmpty => write!(f, "[]"),
|
|
||||||
Type::ListOf { item_type } => write!(f, "[{item_type}]"),
|
|
||||||
Type::Map { pairs } => {
|
|
||||||
write!(f, "map ")?;
|
|
||||||
|
|
||||||
write!(f, "{{")?;
|
|
||||||
|
|
||||||
for (index, (key, value)) in pairs.iter().enumerate() {
|
|
||||||
write!(f, "{key}: {value}")?;
|
|
||||||
|
|
||||||
if index != pairs.len() - 1 {
|
|
||||||
write!(f, ", ")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, "}}")
|
|
||||||
}
|
|
||||||
Type::Number => write!(f, "num"),
|
|
||||||
Type::Range { r#type } => write!(f, "{type} range"),
|
|
||||||
Type::String { .. } => write!(f, "str"),
|
|
||||||
Type::Struct(struct_type) => write!(f, "{struct_type}"),
|
|
||||||
Type::Tuple { fields } => {
|
|
||||||
if let Some(fields) = fields {
|
|
||||||
write!(f, "(")?;
|
|
||||||
|
|
||||||
for (index, r#type) in fields.iter().enumerate() {
|
|
||||||
write!(f, "{type}")?;
|
|
||||||
|
|
||||||
if index != fields.len() - 1 {
|
|
||||||
write!(f, ", ")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, ")")
|
|
||||||
} else {
|
|
||||||
write!(f, "tuple")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for Type {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for Type {
|
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
|
||||||
match (self, other) {
|
|
||||||
(Type::Any, Type::Any) => Ordering::Equal,
|
|
||||||
(Type::Any, _) => Ordering::Greater,
|
|
||||||
(Type::Boolean, Type::Boolean) => Ordering::Equal,
|
|
||||||
(Type::Boolean, _) => Ordering::Greater,
|
|
||||||
(Type::Byte, Type::Byte) => Ordering::Equal,
|
|
||||||
(Type::Byte, _) => Ordering::Greater,
|
|
||||||
(Type::Character, Type::Character) => Ordering::Equal,
|
|
||||||
(Type::Character, _) => Ordering::Greater,
|
|
||||||
(Type::Enum(left_enum), Type::Enum(right_enum)) => left_enum.cmp(right_enum),
|
|
||||||
(Type::Enum(_), _) => Ordering::Greater,
|
|
||||||
(Type::Float, Type::Float) => Ordering::Equal,
|
|
||||||
(Type::Float, _) => Ordering::Greater,
|
|
||||||
(Type::Function(left_function), Type::Function(right_function)) => {
|
|
||||||
left_function.cmp(right_function)
|
|
||||||
}
|
|
||||||
(Type::Function(_), _) => Ordering::Greater,
|
|
||||||
(Type::Generic { .. }, Type::Generic { .. }) => Ordering::Equal,
|
|
||||||
(Type::Generic { .. }, _) => Ordering::Greater,
|
|
||||||
(Type::Integer, Type::Integer) => Ordering::Equal,
|
|
||||||
(Type::Integer, _) => Ordering::Greater,
|
|
||||||
(
|
|
||||||
Type::List {
|
|
||||||
item_type: left_item_type,
|
|
||||||
length: left_length,
|
|
||||||
},
|
|
||||||
Type::List {
|
|
||||||
item_type: right_item_type,
|
|
||||||
length: right_length,
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
if left_length == right_length {
|
|
||||||
left_item_type.cmp(right_item_type)
|
|
||||||
} else {
|
|
||||||
left_length.cmp(right_length)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Type::List { .. }, _) => Ordering::Greater,
|
|
||||||
(Type::ListEmpty, Type::ListEmpty) => Ordering::Equal,
|
|
||||||
(Type::ListEmpty, _) => Ordering::Greater,
|
|
||||||
(
|
|
||||||
Type::ListOf {
|
|
||||||
item_type: left_item_type,
|
|
||||||
},
|
|
||||||
Type::ListOf {
|
|
||||||
item_type: right_item_type,
|
|
||||||
},
|
|
||||||
) => left_item_type.cmp(right_item_type),
|
|
||||||
(Type::ListOf { .. }, _) => Ordering::Greater,
|
|
||||||
(Type::Map { pairs: left_pairs }, Type::Map { pairs: right_pairs }) => {
|
|
||||||
left_pairs.iter().cmp(right_pairs.iter())
|
|
||||||
}
|
|
||||||
(Type::Map { .. }, _) => Ordering::Greater,
|
|
||||||
(Type::Number, Type::Number) => Ordering::Equal,
|
|
||||||
(Type::Number, _) => Ordering::Greater,
|
|
||||||
(Type::Range { r#type: left_type }, Type::Range { r#type: right_type }) => {
|
|
||||||
left_type.cmp(right_type)
|
|
||||||
}
|
|
||||||
(Type::Range { .. }, _) => Ordering::Greater,
|
|
||||||
(Type::String { length: left }, Type::String { length: right }) => left.cmp(right),
|
|
||||||
(Type::String { .. }, _) => Ordering::Greater,
|
|
||||||
(Type::Struct(left_struct), Type::Struct(right_struct)) => {
|
|
||||||
left_struct.cmp(right_struct)
|
|
||||||
}
|
|
||||||
(Type::Struct(_), _) => Ordering::Greater,
|
|
||||||
|
|
||||||
(Type::Tuple { fields: left }, Type::Tuple { fields: right }) => left.cmp(right),
|
|
||||||
(Type::Tuple { .. }, _) => Ordering::Greater,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
|
||||||
pub struct FunctionType {
|
|
||||||
pub type_parameters: Option<Vec<u8>>,
|
|
||||||
pub value_parameters: Option<Vec<(u8, Type)>>,
|
|
||||||
pub return_type: Option<Box<Type>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for FunctionType {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "fn ")?;
|
|
||||||
|
|
||||||
if let Some(type_parameters) = &self.type_parameters {
|
|
||||||
write!(f, "<")?;
|
|
||||||
|
|
||||||
for (index, type_parameter) in type_parameters.iter().enumerate() {
|
|
||||||
if index > 0 {
|
|
||||||
write!(f, ", ")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, "{type_parameter}")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, ">")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, "(")?;
|
|
||||||
|
|
||||||
if let Some(value_parameters) = &self.value_parameters {
|
|
||||||
for (index, (identifier, r#type)) in value_parameters.iter().enumerate() {
|
|
||||||
if index > 0 {
|
|
||||||
write!(f, ", ")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, "{identifier}: {type}")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, ")")?;
|
|
||||||
|
|
||||||
if let Some(return_type) = &self.return_type {
|
|
||||||
write!(f, " -> {return_type}")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub enum StructType {
|
|
||||||
Unit { name: u8 },
|
|
||||||
Tuple { name: u8, fields: Vec<Type> },
|
|
||||||
Fields { name: u8, fields: HashMap<u8, Type> },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StructType {
|
|
||||||
pub fn name(&self) -> u8 {
|
|
||||||
match self {
|
|
||||||
StructType::Unit { name } => *name,
|
|
||||||
StructType::Tuple { name, .. } => *name,
|
|
||||||
StructType::Fields { name, .. } => *name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for StructType {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
StructType::Unit { name } => write!(f, "{name}"),
|
|
||||||
StructType::Tuple { name, fields } => {
|
|
||||||
write!(f, "{name}(")?;
|
|
||||||
|
|
||||||
for (index, field) in fields.iter().enumerate() {
|
|
||||||
write!(f, "{field}")?;
|
|
||||||
|
|
||||||
if index != fields.len() - 1 {
|
|
||||||
write!(f, ", ")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, ")")
|
|
||||||
}
|
|
||||||
StructType::Fields { name, fields } => {
|
|
||||||
write!(f, "{name} {{")?;
|
|
||||||
|
|
||||||
for (index, (identifier, r#type)) in fields.iter().enumerate() {
|
|
||||||
write!(f, "{identifier}: {type}")?;
|
|
||||||
|
|
||||||
if index != fields.len() - 1 {
|
|
||||||
write!(f, ", ")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, "}}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for StructType {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for StructType {
|
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
|
||||||
match (self, other) {
|
|
||||||
(StructType::Unit { name: left_name }, StructType::Unit { name: right_name }) => {
|
|
||||||
left_name.cmp(right_name)
|
|
||||||
}
|
|
||||||
(StructType::Unit { .. }, _) => Ordering::Greater,
|
|
||||||
(
|
|
||||||
StructType::Tuple {
|
|
||||||
name: left_name,
|
|
||||||
fields: left_fields,
|
|
||||||
},
|
|
||||||
StructType::Tuple {
|
|
||||||
name: right_name,
|
|
||||||
fields: right_fields,
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
let name_cmp = left_name.cmp(right_name);
|
|
||||||
|
|
||||||
if name_cmp == Ordering::Equal {
|
|
||||||
left_fields.cmp(right_fields)
|
|
||||||
} else {
|
|
||||||
name_cmp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(StructType::Tuple { .. }, _) => Ordering::Greater,
|
|
||||||
(
|
|
||||||
StructType::Fields {
|
|
||||||
name: left_name,
|
|
||||||
fields: left_fields,
|
|
||||||
},
|
|
||||||
StructType::Fields {
|
|
||||||
name: right_name,
|
|
||||||
fields: right_fields,
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
let name_cmp = left_name.cmp(right_name);
|
|
||||||
|
|
||||||
if name_cmp == Ordering::Equal {
|
|
||||||
let len_cmp = left_fields.len().cmp(&right_fields.len());
|
|
||||||
|
|
||||||
if len_cmp == Ordering::Equal {
|
|
||||||
left_fields.iter().cmp(right_fields.iter())
|
|
||||||
} else {
|
|
||||||
len_cmp
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
name_cmp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(StructType::Fields { .. }, _) => Ordering::Greater,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
|
||||||
pub struct EnumType {
|
|
||||||
pub name: u8,
|
|
||||||
pub variants: Vec<StructType>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for EnumType {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
let EnumType { name, variants } = self;
|
|
||||||
|
|
||||||
write!(f, "enum {name} {{ ")?;
|
|
||||||
|
|
||||||
for (index, variant) in variants.iter().enumerate() {
|
|
||||||
write!(f, "{variant}")?;
|
|
||||||
|
|
||||||
if index != self.variants.len() - 1 {
|
|
||||||
write!(f, ", ")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, " }}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
|
||||||
pub enum RangeableType {
|
|
||||||
Byte,
|
|
||||||
Character,
|
|
||||||
Float,
|
|
||||||
Integer,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for RangeableType {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
RangeableType::Byte => Type::Byte.fmt(f),
|
|
||||||
RangeableType::Character => Type::Character.fmt(f),
|
|
||||||
RangeableType::Float => Type::Float.fmt(f),
|
|
||||||
RangeableType::Integer => Type::Integer.fmt(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub struct TypeConflict {
|
|
||||||
pub expected: Type,
|
|
||||||
pub actual: Type,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn check_type_any() {
|
|
||||||
let foo = Type::Any;
|
|
||||||
let bar = Type::Any;
|
|
||||||
|
|
||||||
foo.check(&bar).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn check_type_boolean() {
|
|
||||||
let foo = Type::Boolean;
|
|
||||||
let bar = Type::Boolean;
|
|
||||||
|
|
||||||
foo.check(&bar).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn check_type_byte() {
|
|
||||||
let foo = Type::Byte;
|
|
||||||
let bar = Type::Byte;
|
|
||||||
|
|
||||||
foo.check(&bar).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn check_type_character() {
|
|
||||||
let foo = Type::Character;
|
|
||||||
let bar = Type::Character;
|
|
||||||
|
|
||||||
foo.check(&bar).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn errors() {
|
|
||||||
let foo = Type::Integer;
|
|
||||||
let bar = Type::String { length: None };
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
foo.check(&bar),
|
|
||||||
Err(TypeConflict {
|
|
||||||
actual: bar.clone(),
|
|
||||||
expected: foo.clone()
|
|
||||||
})
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
bar.check(&foo),
|
|
||||||
Err(TypeConflict {
|
|
||||||
actual: foo.clone(),
|
|
||||||
expected: bar.clone()
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
let types = [
|
|
||||||
Type::Boolean,
|
|
||||||
Type::Float,
|
|
||||||
Type::Integer,
|
|
||||||
Type::List {
|
|
||||||
item_type: Box::new(Type::Integer),
|
|
||||||
length: 42,
|
|
||||||
},
|
|
||||||
Type::Range {
|
|
||||||
r#type: RangeableType::Integer,
|
|
||||||
},
|
|
||||||
Type::String { length: None },
|
|
||||||
];
|
|
||||||
|
|
||||||
for left in types.clone() {
|
|
||||||
for right in types.clone() {
|
|
||||||
if left == right {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
left.check(&right),
|
|
||||||
Err(TypeConflict {
|
|
||||||
actual: right.clone(),
|
|
||||||
expected: left.clone()
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,767 +0,0 @@
|
|||||||
//! Dust value representation
|
|
||||||
//!
|
|
||||||
//! # Examples
|
|
||||||
//!
|
|
||||||
//! Each type of value has a corresponding method for instantiation:
|
|
||||||
//!
|
|
||||||
//! ```
|
|
||||||
//! # use dust_lang::Value;
|
|
||||||
//! let boolean = Value::boolean(true);
|
|
||||||
//! let float = Value::float(3.14);
|
|
||||||
//! let integer = Value::integer(42);
|
|
||||||
//! let string = Value::string("Hello, world!");
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Values have a type, which can be retrieved using the `r#type` method:
|
|
||||||
//!
|
|
||||||
//! ```
|
|
||||||
//! # use dust_lang::*;
|
|
||||||
//! let value = Value::integer(42);
|
|
||||||
//!
|
|
||||||
//! assert_eq!(value.r#type(), Type::Integer);
|
|
||||||
//! ```
|
|
||||||
use std::{
|
|
||||||
cmp::Ordering,
|
|
||||||
fmt::{self, Debug, Display, Formatter},
|
|
||||||
ops::{Range, RangeInclusive},
|
|
||||||
};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::{Chunk, FunctionType, RangeableType, Type};
|
|
||||||
|
|
||||||
/// Dust value representation
|
|
||||||
///
|
|
||||||
/// See the [module-level documentation][self] for more.
|
|
||||||
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
|
||||||
pub enum Value {
|
|
||||||
Concrete(ConcreteValue),
|
|
||||||
Abstract(AbstractValue),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Value {
|
|
||||||
pub fn boolean(value: bool) -> Self {
|
|
||||||
Value::Concrete(ConcreteValue::Boolean(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn byte(value: u8) -> Self {
|
|
||||||
Value::Concrete(ConcreteValue::Byte(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn character(value: char) -> Self {
|
|
||||||
Value::Concrete(ConcreteValue::Character(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn float(value: f64) -> Self {
|
|
||||||
Value::Concrete(ConcreteValue::Float(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn function(body: Chunk, r#type: FunctionType) -> Self {
|
|
||||||
Value::Concrete(ConcreteValue::Function(Function {
|
|
||||||
chunk: body,
|
|
||||||
r#type: Type::Function(r#type),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn integer<T: Into<i64>>(into_i64: T) -> Self {
|
|
||||||
Value::Concrete(ConcreteValue::Integer(into_i64.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list<T: Into<Vec<Value>>>(items: T) -> Self {
|
|
||||||
Value::Concrete(ConcreteValue::List(items.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn abstract_list(start: u8, end: u8, item_type: Type) -> Self {
|
|
||||||
Value::Abstract(AbstractValue::List {
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
item_type,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn string<T: ToString>(to_string: T) -> Self {
|
|
||||||
Value::Concrete(ConcreteValue::String(to_string.to_string()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_string(&self) -> Option<&String> {
|
|
||||||
if let Value::Concrete(ConcreteValue::String(string)) = self {
|
|
||||||
Some(string)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_function(&self) -> bool {
|
|
||||||
matches!(self, Value::Concrete(ConcreteValue::Function(_)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn r#type(&self) -> Type {
|
|
||||||
match self {
|
|
||||||
Value::Concrete(data) => data.r#type(),
|
|
||||||
Value::Abstract(AbstractValue::List {
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
item_type,
|
|
||||||
}) => {
|
|
||||||
let length = (end - start + 1) as usize;
|
|
||||||
|
|
||||||
Type::List {
|
|
||||||
length,
|
|
||||||
item_type: Box::new(item_type.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add(&self, other: &Value) -> Result<Value, ValueError> {
|
|
||||||
use ConcreteValue::*;
|
|
||||||
use Value::*;
|
|
||||||
|
|
||||||
let sum = match (self, other) {
|
|
||||||
(Concrete(Byte(left)), Concrete(Byte(right))) => {
|
|
||||||
Value::byte(left.saturating_add(*right))
|
|
||||||
}
|
|
||||||
(Concrete(Float(left)), Concrete(Float(right))) => Value::float(left + right),
|
|
||||||
(Concrete(Integer(left)), Concrete(Integer(right))) => {
|
|
||||||
Value::integer(left.saturating_add(*right))
|
|
||||||
}
|
|
||||||
(Concrete(String(left)), Concrete(String(right))) => {
|
|
||||||
Value::string(format!("{}{}", left, right))
|
|
||||||
}
|
|
||||||
_ => return Err(ValueError::CannotAdd(self.clone(), other.clone())),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(sum)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn subtract(&self, other: &Value) -> Result<Value, ValueError> {
|
|
||||||
use ConcreteValue::*;
|
|
||||||
use Value::*;
|
|
||||||
|
|
||||||
let different = match (self, other) {
|
|
||||||
(Concrete(Byte(left)), Concrete(Byte(right))) => {
|
|
||||||
Value::byte(left.saturating_sub(*right))
|
|
||||||
}
|
|
||||||
(Concrete(Float(left)), Concrete(Float(right))) => Value::float(left - right),
|
|
||||||
(Concrete(Integer(left)), Concrete(Integer(right))) => {
|
|
||||||
Value::integer(left.saturating_sub(*right))
|
|
||||||
}
|
|
||||||
_ => return Err(ValueError::CannotSubtract(self.clone(), other.clone())),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(different)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn multiply(&self, other: &Value) -> Result<Value, ValueError> {
|
|
||||||
use ConcreteValue::*;
|
|
||||||
use Value::*;
|
|
||||||
|
|
||||||
let product = match (self, other) {
|
|
||||||
(Concrete(Byte(left)), Concrete(Byte(right))) => {
|
|
||||||
Value::byte(left.saturating_mul(*right))
|
|
||||||
}
|
|
||||||
(Concrete(Float(left)), Concrete(Float(right))) => Value::float(left * right),
|
|
||||||
(Concrete(Integer(left)), Concrete(Integer(right))) => {
|
|
||||||
Value::integer(left.saturating_mul(*right))
|
|
||||||
}
|
|
||||||
_ => return Err(ValueError::CannotAdd(self.clone(), other.clone())),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(product)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn divide(&self, other: &Value) -> Result<Value, ValueError> {
|
|
||||||
use ConcreteValue::*;
|
|
||||||
use Value::*;
|
|
||||||
|
|
||||||
let product = match (self, other) {
|
|
||||||
(Concrete(Byte(left)), Concrete(Byte(right))) => {
|
|
||||||
Value::byte(left.saturating_div(*right))
|
|
||||||
}
|
|
||||||
(Concrete(Float(left)), Concrete(Float(right))) => Value::float(left / right),
|
|
||||||
(Concrete(Integer(left)), Concrete(Integer(right))) => {
|
|
||||||
Value::integer(left.saturating_div(*right))
|
|
||||||
}
|
|
||||||
_ => return Err(ValueError::CannotDivide(self.clone(), other.clone())),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(product)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn modulo(&self, other: &Value) -> Result<Value, ValueError> {
|
|
||||||
use ConcreteValue::*;
|
|
||||||
use Value::*;
|
|
||||||
|
|
||||||
let product = match (self, other) {
|
|
||||||
(Concrete(Byte(left)), Concrete(Byte(right))) => Value::byte(left % right),
|
|
||||||
(Concrete(Float(left)), Concrete(Float(right))) => Value::float(left % right),
|
|
||||||
(Concrete(Integer(left)), Concrete(Integer(right))) => Value::integer(left % right),
|
|
||||||
_ => return Err(ValueError::CannotModulo(self.clone(), other.clone())),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(product)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn less_than(&self, other: &Value) -> Result<Value, ValueError> {
|
|
||||||
let (left, right) = match (self, other) {
|
|
||||||
(Value::Concrete(left), Value::Concrete(right)) => (left, right),
|
|
||||||
_ => return Err(ValueError::CannotCompare(self.clone(), other.clone())),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Value::boolean(left < right))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn less_than_or_equal(&self, other: &Value) -> Result<Value, ValueError> {
|
|
||||||
let (left, right) = match (self, other) {
|
|
||||||
(Value::Concrete(left), Value::Concrete(right)) => (left, right),
|
|
||||||
_ => return Err(ValueError::CannotCompare(self.clone(), other.clone())),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Value::boolean(left <= right))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn equal(&self, other: &Value) -> Result<Value, ValueError> {
|
|
||||||
let (left, right) = match (self, other) {
|
|
||||||
(Value::Concrete(left), Value::Concrete(right)) => (left, right),
|
|
||||||
_ => return Err(ValueError::CannotCompare(self.clone(), other.clone())),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Value::boolean(left == right))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn negate(&self) -> Result<Value, ValueError> {
|
|
||||||
use ConcreteValue::*;
|
|
||||||
use Value::*;
|
|
||||||
|
|
||||||
let negated = match self {
|
|
||||||
Concrete(Integer(integer)) => Value::integer(-integer),
|
|
||||||
Concrete(Float(float)) => Value::float(-float),
|
|
||||||
_ => return Err(ValueError::CannotNot(self.clone())),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(negated)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn not(&self) -> Result<Value, ValueError> {
|
|
||||||
use ConcreteValue::*;
|
|
||||||
use Value::*;
|
|
||||||
|
|
||||||
let not = match self {
|
|
||||||
Concrete(Boolean(boolean)) => Value::boolean(!boolean),
|
|
||||||
Concrete(Byte(byte)) => Value::byte(!byte),
|
|
||||||
Concrete(Integer(integer)) => Value::integer(!integer),
|
|
||||||
_ => return Err(ValueError::CannotNot(self.clone())),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(not)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<bool> for Value {
|
|
||||||
fn from(value: bool) -> Self {
|
|
||||||
Value::boolean(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u8> for Value {
|
|
||||||
fn from(value: u8) -> Self {
|
|
||||||
Value::byte(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<char> for Value {
|
|
||||||
fn from(value: char) -> Self {
|
|
||||||
Value::character(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<f64> for Value {
|
|
||||||
fn from(value: f64) -> Self {
|
|
||||||
Value::float(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<i32> for Value {
|
|
||||||
fn from(value: i32) -> Self {
|
|
||||||
Value::integer(value as i64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<i64> for Value {
|
|
||||||
fn from(value: i64) -> Self {
|
|
||||||
Value::integer(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for Value {
|
|
||||||
fn from(value: String) -> Self {
|
|
||||||
Value::string(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&str> for Value {
|
|
||||||
fn from(str: &str) -> Self {
|
|
||||||
Value::string(str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for Value {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
log::trace!("Cloning value {self}");
|
|
||||||
|
|
||||||
match self {
|
|
||||||
Value::Abstract(object) => Value::Abstract(object.clone()),
|
|
||||||
Value::Concrete(concrete) => Value::Concrete(concrete.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Value {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Value::Abstract(object) => write!(f, "{object}"),
|
|
||||||
Value::Concrete(concrete) => write!(f, "{concrete}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Value representation that can be resolved to a concrete value by the VM.
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
|
||||||
pub enum AbstractValue {
|
|
||||||
List { start: u8, end: u8, item_type: Type },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for AbstractValue {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
AbstractValue::List { start, end, .. } => {
|
|
||||||
write!(f, "List [R{}..=R{}]", start, end)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
|
||||||
pub enum ConcreteValue {
|
|
||||||
Boolean(bool),
|
|
||||||
Byte(u8),
|
|
||||||
Character(char),
|
|
||||||
Float(f64),
|
|
||||||
Function(Function),
|
|
||||||
Integer(i64),
|
|
||||||
List(Vec<Value>),
|
|
||||||
Range(RangeValue),
|
|
||||||
String(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConcreteValue {
|
|
||||||
pub fn r#type(&self) -> Type {
|
|
||||||
match self {
|
|
||||||
ConcreteValue::Boolean(_) => Type::Boolean,
|
|
||||||
ConcreteValue::Byte(_) => Type::Byte,
|
|
||||||
ConcreteValue::Character(_) => Type::Character,
|
|
||||||
ConcreteValue::Float(_) => Type::Float,
|
|
||||||
ConcreteValue::Function(Function { r#type, .. }) => r#type.clone(),
|
|
||||||
ConcreteValue::Integer(_) => Type::Integer,
|
|
||||||
ConcreteValue::List(list) => Type::List {
|
|
||||||
item_type: list
|
|
||||||
.first()
|
|
||||||
.map(|value| Box::new(value.r#type()))
|
|
||||||
.unwrap_or_else(|| Box::new(Type::Any)),
|
|
||||||
length: list.len(),
|
|
||||||
},
|
|
||||||
ConcreteValue::Range(range) => range.r#type(),
|
|
||||||
ConcreteValue::String(string) => Type::String {
|
|
||||||
length: Some(string.len()),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_rangeable(&self) -> bool {
|
|
||||||
matches!(
|
|
||||||
self,
|
|
||||||
ConcreteValue::Integer(_)
|
|
||||||
| ConcreteValue::Float(_)
|
|
||||||
| ConcreteValue::Character(_)
|
|
||||||
| ConcreteValue::Byte(_)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for ConcreteValue {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
ConcreteValue::Boolean(boolean) => write!(f, "{boolean}"),
|
|
||||||
ConcreteValue::Byte(byte) => write!(f, "0x{byte:02x}"),
|
|
||||||
ConcreteValue::Character(character) => write!(f, "{character}"),
|
|
||||||
ConcreteValue::Float(float) => {
|
|
||||||
write!(f, "{float}")?;
|
|
||||||
|
|
||||||
if float.fract() == 0.0 {
|
|
||||||
write!(f, ".0")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
ConcreteValue::Function(Function { r#type, .. }) => {
|
|
||||||
write!(f, "{}", r#type)
|
|
||||||
}
|
|
||||||
ConcreteValue::Integer(integer) => write!(f, "{integer}"),
|
|
||||||
ConcreteValue::List(items) => {
|
|
||||||
write!(f, "[")?;
|
|
||||||
|
|
||||||
for (index, item) in items.iter().enumerate() {
|
|
||||||
if index > 0 {
|
|
||||||
write!(f, ", ")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, "{item}")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, "]")
|
|
||||||
}
|
|
||||||
ConcreteValue::Range(range_value) => {
|
|
||||||
write!(f, "{range_value}")
|
|
||||||
}
|
|
||||||
ConcreteValue::String(string) => write!(f, "{string}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for ConcreteValue {}
|
|
||||||
|
|
||||||
impl PartialOrd for ConcreteValue {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for ConcreteValue {
|
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
|
||||||
match (self, other) {
|
|
||||||
(ConcreteValue::Boolean(left), ConcreteValue::Boolean(right)) => left.cmp(right),
|
|
||||||
(ConcreteValue::Boolean(_), _) => Ordering::Greater,
|
|
||||||
(ConcreteValue::Byte(left), ConcreteValue::Byte(right)) => left.cmp(right),
|
|
||||||
(ConcreteValue::Byte(_), _) => Ordering::Greater,
|
|
||||||
(ConcreteValue::Character(left), ConcreteValue::Character(right)) => left.cmp(right),
|
|
||||||
(ConcreteValue::Character(_), _) => Ordering::Greater,
|
|
||||||
(ConcreteValue::Float(left), ConcreteValue::Float(right)) => {
|
|
||||||
if left.is_nan() && right.is_nan() {
|
|
||||||
Ordering::Equal
|
|
||||||
} else if left.is_nan() {
|
|
||||||
Ordering::Less
|
|
||||||
} else if right.is_nan() {
|
|
||||||
Ordering::Greater
|
|
||||||
} else {
|
|
||||||
left.partial_cmp(right).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(ConcreteValue::Float(_), _) => Ordering::Greater,
|
|
||||||
(ConcreteValue::Function(left), ConcreteValue::Function(right)) => left.cmp(right),
|
|
||||||
(ConcreteValue::Function(_), _) => Ordering::Greater,
|
|
||||||
(ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => left.cmp(right),
|
|
||||||
(ConcreteValue::Integer(_), _) => Ordering::Greater,
|
|
||||||
(ConcreteValue::List(left), ConcreteValue::List(right)) => left.cmp(right),
|
|
||||||
(ConcreteValue::List(_), _) => Ordering::Greater,
|
|
||||||
(ConcreteValue::Range(left), ConcreteValue::Range(right)) => left.cmp(right),
|
|
||||||
(ConcreteValue::Range(_), _) => Ordering::Greater,
|
|
||||||
(ConcreteValue::String(left), ConcreteValue::String(right)) => left.cmp(right),
|
|
||||||
(ConcreteValue::String(_), _) => Ordering::Greater,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
|
||||||
pub struct Function {
|
|
||||||
chunk: Chunk,
|
|
||||||
r#type: Type,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Function {
|
|
||||||
pub fn new(chunk: Chunk, r#type: Type) -> Self {
|
|
||||||
Self { chunk, r#type }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn chunk(&self) -> &Chunk {
|
|
||||||
&self.chunk
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn chunk_mut(&mut self) -> &mut Chunk {
|
|
||||||
&mut self.chunk
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn take_chunk(self) -> Chunk {
|
|
||||||
self.chunk
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn r#type(&self) -> &Type {
|
|
||||||
&self.r#type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Function {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.r#type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub enum RangeValue {
|
|
||||||
ByteRange(Range<u8>),
|
|
||||||
ByteRangeInclusive(RangeInclusive<u8>),
|
|
||||||
CharacterRange(Range<char>),
|
|
||||||
CharacterRangeInclusive(RangeInclusive<char>),
|
|
||||||
FloatRange(Range<f64>),
|
|
||||||
FloatRangeInclusive(RangeInclusive<f64>),
|
|
||||||
IntegerRange(Range<i64>),
|
|
||||||
IntegerRangeInclusive(RangeInclusive<i64>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RangeValue {
|
|
||||||
pub fn r#type(&self) -> Type {
|
|
||||||
let inner_type = match self {
|
|
||||||
RangeValue::ByteRange(_) => RangeableType::Byte,
|
|
||||||
RangeValue::ByteRangeInclusive(_) => RangeableType::Byte,
|
|
||||||
RangeValue::CharacterRange(_) => RangeableType::Character,
|
|
||||||
RangeValue::CharacterRangeInclusive(_) => RangeableType::Character,
|
|
||||||
RangeValue::FloatRange(_) => RangeableType::Float,
|
|
||||||
RangeValue::FloatRangeInclusive(_) => RangeableType::Float,
|
|
||||||
RangeValue::IntegerRange(_) => RangeableType::Integer,
|
|
||||||
RangeValue::IntegerRangeInclusive(_) => RangeableType::Integer,
|
|
||||||
};
|
|
||||||
|
|
||||||
Type::Range { r#type: inner_type }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Range<u8>> for RangeValue {
|
|
||||||
fn from(range: Range<u8>) -> Self {
|
|
||||||
RangeValue::ByteRange(range)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<RangeInclusive<u8>> for RangeValue {
|
|
||||||
fn from(range: RangeInclusive<u8>) -> Self {
|
|
||||||
RangeValue::ByteRangeInclusive(range)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Range<char>> for RangeValue {
|
|
||||||
fn from(range: Range<char>) -> Self {
|
|
||||||
RangeValue::CharacterRange(range)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<RangeInclusive<char>> for RangeValue {
|
|
||||||
fn from(range: RangeInclusive<char>) -> Self {
|
|
||||||
RangeValue::CharacterRangeInclusive(range)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Range<f64>> for RangeValue {
|
|
||||||
fn from(range: Range<f64>) -> Self {
|
|
||||||
RangeValue::FloatRange(range)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<RangeInclusive<f64>> for RangeValue {
|
|
||||||
fn from(range: RangeInclusive<f64>) -> Self {
|
|
||||||
RangeValue::FloatRangeInclusive(range)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Range<i32>> for RangeValue {
|
|
||||||
fn from(range: Range<i32>) -> Self {
|
|
||||||
RangeValue::IntegerRange(range.start as i64..range.end as i64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<RangeInclusive<i32>> for RangeValue {
|
|
||||||
fn from(range: RangeInclusive<i32>) -> Self {
|
|
||||||
RangeValue::IntegerRangeInclusive(*range.start() as i64..=*range.end() as i64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Range<i64>> for RangeValue {
|
|
||||||
fn from(range: Range<i64>) -> Self {
|
|
||||||
RangeValue::IntegerRange(range)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<RangeInclusive<i64>> for RangeValue {
|
|
||||||
fn from(range: RangeInclusive<i64>) -> Self {
|
|
||||||
RangeValue::IntegerRangeInclusive(range)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for RangeValue {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
RangeValue::ByteRange(range) => write!(f, "{}..{}", range.start, range.end),
|
|
||||||
RangeValue::ByteRangeInclusive(range) => {
|
|
||||||
write!(f, "{}..={}", range.start(), range.end())
|
|
||||||
}
|
|
||||||
RangeValue::CharacterRange(range) => write!(f, "{}..{}", range.start, range.end),
|
|
||||||
RangeValue::CharacterRangeInclusive(range) => {
|
|
||||||
write!(f, "{}..={}", range.start(), range.end())
|
|
||||||
}
|
|
||||||
RangeValue::FloatRange(range) => write!(f, "{}..{}", range.start, range.end),
|
|
||||||
RangeValue::FloatRangeInclusive(range) => {
|
|
||||||
write!(f, "{}..={}", range.start(), range.end())
|
|
||||||
}
|
|
||||||
RangeValue::IntegerRange(range) => write!(f, "{}..{}", range.start, range.end),
|
|
||||||
RangeValue::IntegerRangeInclusive(range) => {
|
|
||||||
write!(f, "{}..={}", range.start(), range.end())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for RangeValue {}
|
|
||||||
|
|
||||||
impl PartialOrd for RangeValue {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for RangeValue {
|
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
|
||||||
match (self, other) {
|
|
||||||
(RangeValue::ByteRange(left), RangeValue::ByteRange(right)) => {
|
|
||||||
let start_cmp = left.start.cmp(&right.start);
|
|
||||||
|
|
||||||
if start_cmp != Ordering::Equal {
|
|
||||||
start_cmp
|
|
||||||
} else {
|
|
||||||
left.end.cmp(&right.end)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(RangeValue::ByteRange(_), _) => Ordering::Greater,
|
|
||||||
(RangeValue::ByteRangeInclusive(left), RangeValue::ByteRangeInclusive(right)) => {
|
|
||||||
let start_cmp = left.start().cmp(right.start());
|
|
||||||
|
|
||||||
if start_cmp != Ordering::Equal {
|
|
||||||
start_cmp
|
|
||||||
} else {
|
|
||||||
left.end().cmp(right.end())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(RangeValue::ByteRangeInclusive(_), _) => Ordering::Greater,
|
|
||||||
(RangeValue::CharacterRange(left), RangeValue::CharacterRange(right)) => {
|
|
||||||
let start_cmp = left.start.cmp(&right.start);
|
|
||||||
|
|
||||||
if start_cmp != Ordering::Equal {
|
|
||||||
start_cmp
|
|
||||||
} else {
|
|
||||||
left.end.cmp(&right.end)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(RangeValue::CharacterRange(_), _) => Ordering::Greater,
|
|
||||||
(
|
|
||||||
RangeValue::CharacterRangeInclusive(left),
|
|
||||||
RangeValue::CharacterRangeInclusive(right),
|
|
||||||
) => {
|
|
||||||
let start_cmp = left.start().cmp(right.start());
|
|
||||||
|
|
||||||
if start_cmp != Ordering::Equal {
|
|
||||||
start_cmp
|
|
||||||
} else {
|
|
||||||
left.end().cmp(right.end())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(RangeValue::CharacterRangeInclusive(_), _) => Ordering::Greater,
|
|
||||||
(RangeValue::FloatRange(left), RangeValue::FloatRange(right)) => {
|
|
||||||
let start_cmp = left.start.to_bits().cmp(&right.start.to_bits());
|
|
||||||
|
|
||||||
if start_cmp != Ordering::Equal {
|
|
||||||
start_cmp
|
|
||||||
} else {
|
|
||||||
left.end.to_bits().cmp(&right.end.to_bits())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(RangeValue::FloatRange(_), _) => Ordering::Greater,
|
|
||||||
(RangeValue::FloatRangeInclusive(left), RangeValue::FloatRangeInclusive(right)) => {
|
|
||||||
let start_cmp = left.start().to_bits().cmp(&right.start().to_bits());
|
|
||||||
|
|
||||||
if start_cmp != Ordering::Equal {
|
|
||||||
start_cmp
|
|
||||||
} else {
|
|
||||||
left.end().to_bits().cmp(&right.end().to_bits())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(RangeValue::FloatRangeInclusive(_), _) => Ordering::Greater,
|
|
||||||
(RangeValue::IntegerRange(left), RangeValue::IntegerRange(right)) => {
|
|
||||||
let start_cmp = left.start.cmp(&right.start);
|
|
||||||
|
|
||||||
if start_cmp != Ordering::Equal {
|
|
||||||
start_cmp
|
|
||||||
} else {
|
|
||||||
left.end.cmp(&right.end)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(RangeValue::IntegerRange(_), _) => Ordering::Greater,
|
|
||||||
(RangeValue::IntegerRangeInclusive(left), RangeValue::IntegerRangeInclusive(right)) => {
|
|
||||||
let start_cmp = left.start().cmp(right.start());
|
|
||||||
|
|
||||||
if start_cmp != Ordering::Equal {
|
|
||||||
start_cmp
|
|
||||||
} else {
|
|
||||||
left.end().cmp(right.end())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(RangeValue::IntegerRangeInclusive(_), _) => Ordering::Greater,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum ValueError {
|
|
||||||
CannotAdd(Value, Value),
|
|
||||||
CannotAnd(Value, Value),
|
|
||||||
CannotCompare(Value, Value),
|
|
||||||
CannotDivide(Value, Value),
|
|
||||||
CannotModulo(Value, Value),
|
|
||||||
CannotMultiply(Value, Value),
|
|
||||||
CannotNegate(Value),
|
|
||||||
CannotNot(Value),
|
|
||||||
CannotSubtract(Value, Value),
|
|
||||||
CannotOr(Value, Value),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for ValueError {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
ValueError::CannotAdd(left, right) => {
|
|
||||||
write!(f, "Cannot add {left} and {right}")
|
|
||||||
}
|
|
||||||
ValueError::CannotAnd(left, right) => {
|
|
||||||
write!(f, "Cannot use logical AND operation on {left} and {right}")
|
|
||||||
}
|
|
||||||
ValueError::CannotCompare(left, right) => {
|
|
||||||
write!(f, "Cannot compare {left} and {right}")
|
|
||||||
}
|
|
||||||
ValueError::CannotDivide(left, right) => {
|
|
||||||
write!(f, "Cannot divide {left} by {right}")
|
|
||||||
}
|
|
||||||
ValueError::CannotModulo(left, right) => {
|
|
||||||
write!(f, "Cannot use modulo operation on {left} and {right}")
|
|
||||||
}
|
|
||||||
ValueError::CannotMultiply(left, right) => {
|
|
||||||
write!(f, "Cannot multiply {left} by {right}")
|
|
||||||
}
|
|
||||||
ValueError::CannotNegate(value) => {
|
|
||||||
write!(f, "Cannot negate {value}")
|
|
||||||
}
|
|
||||||
ValueError::CannotNot(value) => {
|
|
||||||
write!(f, "Cannot use logical NOT operation on {value}")
|
|
||||||
}
|
|
||||||
ValueError::CannotSubtract(left, right) => {
|
|
||||||
write!(f, "Cannot subtract {right} from {left}")
|
|
||||||
}
|
|
||||||
ValueError::CannotOr(left, right) => {
|
|
||||||
write!(f, "Cannot use logical OR operation on {left} and {right}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,765 +0,0 @@
|
|||||||
//! Virtual machine and errors
|
|
||||||
use std::{
|
|
||||||
cmp::Ordering,
|
|
||||||
fmt::{self, Display, Formatter},
|
|
||||||
ops::Range,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
compile, value::ConcreteValue, AnnotatedError, Chunk, ChunkError, DustError, FunctionType,
|
|
||||||
Instruction, Local, NativeFunction, NativeFunctionError, Operation, Span, Type, Value,
|
|
||||||
ValueError,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn run(source: &str) -> Result<Option<Value>, DustError> {
|
|
||||||
let mut chunk = compile(source)?;
|
|
||||||
let mut vm = Vm::new(&mut chunk, None);
|
|
||||||
|
|
||||||
vm.run()
|
|
||||||
.map(|option| option.cloned())
|
|
||||||
.map_err(|error| DustError::Runtime { error, source })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_and_display_output(source: &str) {
|
|
||||||
match run(source) {
|
|
||||||
Ok(Some(value)) => println!("{}", value),
|
|
||||||
Ok(None) => {}
|
|
||||||
Err(error) => eprintln!("{}", error.report()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dust virtual machine.
|
|
||||||
///
|
|
||||||
/// See the [module-level documentation](index.html) for more information.
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
|
||||||
pub struct Vm<'chunk, 'parent> {
|
|
||||||
ip: usize,
|
|
||||||
chunk: &'chunk mut Chunk,
|
|
||||||
stack: Vec<Register>,
|
|
||||||
last_assigned_register: Option<u8>,
|
|
||||||
parent: Option<&'parent Vm<'chunk, 'parent>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'chunk, 'parent> Vm<'chunk, 'parent> {
|
|
||||||
const STACK_LIMIT: usize = u16::MAX as usize;
|
|
||||||
|
|
||||||
pub fn new(chunk: &'chunk mut Chunk, parent: Option<&'parent Vm<'chunk, 'parent>>) -> Self {
|
|
||||||
Self {
|
|
||||||
ip: 0,
|
|
||||||
chunk,
|
|
||||||
stack: Vec::new(),
|
|
||||||
last_assigned_register: None,
|
|
||||||
parent,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(&mut self) -> Result<Option<&Value>, VmError> {
|
|
||||||
// DRY helper to get constant or register values for binary operations
|
|
||||||
fn get_arguments<'a>(
|
|
||||||
vm: &'a mut Vm,
|
|
||||||
instruction: Instruction,
|
|
||||||
position: Span,
|
|
||||||
) -> Result<(&'a Value, &'a Value), VmError> {
|
|
||||||
let left = if instruction.b_is_constant() {
|
|
||||||
vm.get_constant(instruction.b(), position)?
|
|
||||||
} else {
|
|
||||||
vm.open_register(instruction.b(), position)?
|
|
||||||
};
|
|
||||||
let right = if instruction.c_is_constant() {
|
|
||||||
vm.get_constant(instruction.c(), position)?
|
|
||||||
} else {
|
|
||||||
vm.open_register(instruction.c(), position)?
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((left, right))
|
|
||||||
}
|
|
||||||
|
|
||||||
while let Ok((instruction, position)) = self.read(Span(0, 0)).copied() {
|
|
||||||
log::info!(
|
|
||||||
"{} | {} | {} | {}",
|
|
||||||
self.ip - 1,
|
|
||||||
position,
|
|
||||||
instruction.operation(),
|
|
||||||
instruction.disassembly_info(self.chunk)
|
|
||||||
);
|
|
||||||
|
|
||||||
match instruction.operation() {
|
|
||||||
Operation::Move => {
|
|
||||||
let to_register = instruction.a();
|
|
||||||
let from_register = instruction.b();
|
|
||||||
let from_register_has_value = self
|
|
||||||
.stack
|
|
||||||
.get(from_register as usize)
|
|
||||||
.is_some_and(|register| !matches!(register, Register::Empty));
|
|
||||||
|
|
||||||
if from_register_has_value {
|
|
||||||
self.set_register(
|
|
||||||
to_register,
|
|
||||||
Register::StackPointer(from_register),
|
|
||||||
position,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Operation::Close => {
|
|
||||||
let from_register = instruction.b();
|
|
||||||
let to_register = instruction.c();
|
|
||||||
|
|
||||||
if self.stack.len() < to_register as usize {
|
|
||||||
return Err(VmError::StackUnderflow { position });
|
|
||||||
}
|
|
||||||
|
|
||||||
for register_index in from_register..to_register {
|
|
||||||
self.stack[register_index as usize] = Register::Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Operation::LoadBoolean => {
|
|
||||||
let to_register = instruction.a();
|
|
||||||
let boolean = instruction.b_as_boolean();
|
|
||||||
let jump = instruction.c_as_boolean();
|
|
||||||
let value = Value::boolean(boolean);
|
|
||||||
|
|
||||||
self.set_register(to_register, Register::Value(value), position)?;
|
|
||||||
|
|
||||||
if jump {
|
|
||||||
self.ip += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Operation::LoadConstant => {
|
|
||||||
let to_register = instruction.a();
|
|
||||||
let from_constant = instruction.b();
|
|
||||||
let jump = instruction.c_as_boolean();
|
|
||||||
|
|
||||||
self.set_register(
|
|
||||||
to_register,
|
|
||||||
Register::ConstantPointer(from_constant),
|
|
||||||
position,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if jump {
|
|
||||||
self.ip += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Operation::LoadList => {
|
|
||||||
let to_register = instruction.a();
|
|
||||||
let start_register = instruction.b();
|
|
||||||
let item_type = (start_register..to_register)
|
|
||||||
.find_map(|register_index| {
|
|
||||||
if let Ok(value) = self.open_register(register_index, position) {
|
|
||||||
Some(value.r#type())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or(Type::Any);
|
|
||||||
let value = Value::abstract_list(start_register, to_register, item_type);
|
|
||||||
|
|
||||||
self.set_register(to_register, Register::Value(value), position)?;
|
|
||||||
}
|
|
||||||
Operation::LoadSelf => {
|
|
||||||
let to_register = instruction.a();
|
|
||||||
let value = Value::function(
|
|
||||||
self.chunk.clone(),
|
|
||||||
FunctionType {
|
|
||||||
type_parameters: None,
|
|
||||||
value_parameters: None,
|
|
||||||
return_type: None,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
self.set_register(to_register, Register::Value(value), position)?;
|
|
||||||
}
|
|
||||||
Operation::DefineLocal => {
|
|
||||||
let from_register = instruction.a();
|
|
||||||
let to_local = instruction.b();
|
|
||||||
|
|
||||||
self.define_local(to_local, from_register, position)?;
|
|
||||||
}
|
|
||||||
Operation::GetLocal => {
|
|
||||||
let to_register = instruction.a();
|
|
||||||
let local_index = instruction.b();
|
|
||||||
let local = self.get_local(local_index, position)?;
|
|
||||||
|
|
||||||
self.set_register(
|
|
||||||
to_register,
|
|
||||||
Register::StackPointer(local.register_index),
|
|
||||||
position,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
Operation::SetLocal => {
|
|
||||||
let register = instruction.a();
|
|
||||||
let local_index = instruction.b();
|
|
||||||
|
|
||||||
self.define_local(local_index, register, position)?;
|
|
||||||
}
|
|
||||||
Operation::Add => {
|
|
||||||
let to_register = instruction.a();
|
|
||||||
let (left, right) = get_arguments(self, instruction, position)?;
|
|
||||||
let sum = left
|
|
||||||
.add(right)
|
|
||||||
.map_err(|error| VmError::Value { error, position })?;
|
|
||||||
|
|
||||||
self.set_register(to_register, Register::Value(sum), position)?;
|
|
||||||
}
|
|
||||||
Operation::Subtract => {
|
|
||||||
let to_register = instruction.a();
|
|
||||||
let (left, right) = get_arguments(self, instruction, position)?;
|
|
||||||
let difference = left
|
|
||||||
.subtract(right)
|
|
||||||
.map_err(|error| VmError::Value { error, position })?;
|
|
||||||
|
|
||||||
self.set_register(to_register, Register::Value(difference), position)?;
|
|
||||||
}
|
|
||||||
Operation::Multiply => {
|
|
||||||
let to_register = instruction.a();
|
|
||||||
let (left, right) = get_arguments(self, instruction, position)?;
|
|
||||||
let product = left
|
|
||||||
.multiply(right)
|
|
||||||
.map_err(|error| VmError::Value { error, position })?;
|
|
||||||
|
|
||||||
self.set_register(to_register, Register::Value(product), position)?;
|
|
||||||
}
|
|
||||||
Operation::Divide => {
|
|
||||||
let to_register = instruction.a();
|
|
||||||
let (left, right) = get_arguments(self, instruction, position)?;
|
|
||||||
let quotient = left
|
|
||||||
.divide(right)
|
|
||||||
.map_err(|error| VmError::Value { error, position })?;
|
|
||||||
|
|
||||||
self.set_register(to_register, Register::Value(quotient), position)?;
|
|
||||||
}
|
|
||||||
Operation::Modulo => {
|
|
||||||
let to_register = instruction.a();
|
|
||||||
let (left, right) = get_arguments(self, instruction, position)?;
|
|
||||||
let remainder = left
|
|
||||||
.modulo(right)
|
|
||||||
.map_err(|error| VmError::Value { error, position })?;
|
|
||||||
|
|
||||||
self.set_register(to_register, Register::Value(remainder), position)?;
|
|
||||||
}
|
|
||||||
Operation::Test => {
|
|
||||||
let register = instruction.a();
|
|
||||||
let test_value = instruction.c_as_boolean();
|
|
||||||
let value = self.open_register(register, position)?;
|
|
||||||
let boolean = if let Value::Concrete(ConcreteValue::Boolean(boolean)) = value {
|
|
||||||
*boolean
|
|
||||||
} else {
|
|
||||||
return Err(VmError::ExpectedBoolean {
|
|
||||||
found: value.clone(),
|
|
||||||
position,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if boolean != test_value {
|
|
||||||
self.ip += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Operation::TestSet => todo!(),
|
|
||||||
Operation::Equal => {
|
|
||||||
debug_assert_eq!(
|
|
||||||
self.get_instruction(self.ip, position)?.0.operation(),
|
|
||||||
Operation::Jump
|
|
||||||
);
|
|
||||||
|
|
||||||
let (left, right) = get_arguments(self, instruction, position)?;
|
|
||||||
let equal_result = left
|
|
||||||
.equal(right)
|
|
||||||
.map_err(|error| VmError::Value { error, position })?;
|
|
||||||
let boolean =
|
|
||||||
if let Value::Concrete(ConcreteValue::Boolean(boolean)) = equal_result {
|
|
||||||
boolean
|
|
||||||
} else {
|
|
||||||
return Err(VmError::ExpectedBoolean {
|
|
||||||
found: equal_result.clone(),
|
|
||||||
position,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
let compare_to = instruction.a_as_boolean();
|
|
||||||
|
|
||||||
if boolean == compare_to {
|
|
||||||
self.ip += 1;
|
|
||||||
} else {
|
|
||||||
let (jump, _) = self.get_instruction(self.ip, position)?;
|
|
||||||
let jump_distance = jump.a();
|
|
||||||
let is_positive = jump.b_as_boolean();
|
|
||||||
let new_ip = if is_positive {
|
|
||||||
self.ip + jump_distance as usize
|
|
||||||
} else {
|
|
||||||
self.ip - jump_distance as usize
|
|
||||||
};
|
|
||||||
|
|
||||||
self.ip = new_ip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Operation::Less => {
|
|
||||||
debug_assert_eq!(
|
|
||||||
self.get_instruction(self.ip, position)?.0.operation(),
|
|
||||||
Operation::Jump
|
|
||||||
);
|
|
||||||
|
|
||||||
let (left, right) = get_arguments(self, instruction, position)?;
|
|
||||||
let less_result = left
|
|
||||||
.less_than(right)
|
|
||||||
.map_err(|error| VmError::Value { error, position })?;
|
|
||||||
let boolean =
|
|
||||||
if let Value::Concrete(ConcreteValue::Boolean(boolean)) = less_result {
|
|
||||||
boolean
|
|
||||||
} else {
|
|
||||||
return Err(VmError::ExpectedBoolean {
|
|
||||||
found: less_result.clone(),
|
|
||||||
position,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
let compare_to = instruction.a_as_boolean();
|
|
||||||
|
|
||||||
if boolean == compare_to {
|
|
||||||
self.ip += 1;
|
|
||||||
} else {
|
|
||||||
let jump = self.get_instruction(self.ip, position)?.0;
|
|
||||||
let jump_distance = jump.a();
|
|
||||||
let is_positive = jump.b_as_boolean();
|
|
||||||
let new_ip = if is_positive {
|
|
||||||
self.ip + jump_distance as usize
|
|
||||||
} else {
|
|
||||||
self.ip - jump_distance as usize
|
|
||||||
};
|
|
||||||
|
|
||||||
self.ip = new_ip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Operation::LessEqual => {
|
|
||||||
debug_assert_eq!(
|
|
||||||
self.get_instruction(self.ip, position)?.0.operation(),
|
|
||||||
Operation::Jump
|
|
||||||
);
|
|
||||||
|
|
||||||
let (left, right) = get_arguments(self, instruction, position)?;
|
|
||||||
let less_or_equal_result = left
|
|
||||||
.less_than_or_equal(right)
|
|
||||||
.map_err(|error| VmError::Value { error, position })?;
|
|
||||||
let boolean = if let Value::Concrete(ConcreteValue::Boolean(boolean)) =
|
|
||||||
less_or_equal_result
|
|
||||||
{
|
|
||||||
boolean
|
|
||||||
} else {
|
|
||||||
return Err(VmError::ExpectedBoolean {
|
|
||||||
found: less_or_equal_result.clone(),
|
|
||||||
position,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
let compare_to = instruction.a_as_boolean();
|
|
||||||
|
|
||||||
if boolean == compare_to {
|
|
||||||
self.ip += 1;
|
|
||||||
} else {
|
|
||||||
let jump = self.get_instruction(self.ip, position)?.0;
|
|
||||||
let jump_distance = jump.a();
|
|
||||||
let is_positive = jump.b_as_boolean();
|
|
||||||
let new_ip = if is_positive {
|
|
||||||
self.ip + jump_distance as usize
|
|
||||||
} else {
|
|
||||||
self.ip - jump_distance as usize
|
|
||||||
};
|
|
||||||
|
|
||||||
self.ip = new_ip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Operation::Negate => {
|
|
||||||
let value = if instruction.b_is_constant() {
|
|
||||||
self.get_constant(instruction.b(), position)?
|
|
||||||
} else {
|
|
||||||
self.open_register(instruction.b(), position)?
|
|
||||||
};
|
|
||||||
let negated = value
|
|
||||||
.negate()
|
|
||||||
.map_err(|error| VmError::Value { error, position })?;
|
|
||||||
|
|
||||||
self.set_register(instruction.a(), Register::Value(negated), position)?;
|
|
||||||
}
|
|
||||||
Operation::Not => {
|
|
||||||
let value = if instruction.b_is_constant() {
|
|
||||||
self.get_constant(instruction.b(), position)?
|
|
||||||
} else {
|
|
||||||
self.open_register(instruction.b(), position)?
|
|
||||||
};
|
|
||||||
let not = value
|
|
||||||
.not()
|
|
||||||
.map_err(|error| VmError::Value { error, position })?;
|
|
||||||
|
|
||||||
self.set_register(instruction.a(), Register::Value(not), position)?;
|
|
||||||
}
|
|
||||||
Operation::Jump => {
|
|
||||||
let jump_distance = instruction.b();
|
|
||||||
let is_positive = instruction.c_as_boolean();
|
|
||||||
let new_ip = if is_positive {
|
|
||||||
self.ip + jump_distance as usize
|
|
||||||
} else {
|
|
||||||
self.ip - jump_distance as usize - 1
|
|
||||||
};
|
|
||||||
self.ip = new_ip;
|
|
||||||
}
|
|
||||||
Operation::Call => {
|
|
||||||
let to_register = instruction.a();
|
|
||||||
let function_register = instruction.b();
|
|
||||||
let argument_count = instruction.c();
|
|
||||||
let value = self.open_register(function_register, position)?.clone();
|
|
||||||
let mut function =
|
|
||||||
if let Value::Concrete(ConcreteValue::Function(function)) = value {
|
|
||||||
function
|
|
||||||
} else {
|
|
||||||
return Err(VmError::ExpectedFunction {
|
|
||||||
found: value,
|
|
||||||
position,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
let mut function_vm = Vm::new(function.chunk_mut(), Some(self));
|
|
||||||
let first_argument_index = function_register + 1;
|
|
||||||
|
|
||||||
for argument_index in
|
|
||||||
first_argument_index..first_argument_index + argument_count
|
|
||||||
{
|
|
||||||
let top_of_stack = function_vm.stack.len() as u8;
|
|
||||||
|
|
||||||
function_vm.set_register(
|
|
||||||
top_of_stack,
|
|
||||||
Register::ParentStackPointer(argument_index),
|
|
||||||
position,
|
|
||||||
)?
|
|
||||||
}
|
|
||||||
|
|
||||||
let return_value = function_vm.run()?.cloned();
|
|
||||||
|
|
||||||
if let Some(value) = return_value {
|
|
||||||
self.set_register(to_register, Register::Value(value), position)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Operation::CallNative => {
|
|
||||||
let native_function = NativeFunction::from(instruction.b());
|
|
||||||
let return_value = native_function.call(instruction, self, position)?;
|
|
||||||
|
|
||||||
if let Some(value) = return_value {
|
|
||||||
let to_register = instruction.a();
|
|
||||||
|
|
||||||
self.set_register(to_register, Register::Value(value), position)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Operation::Return => {
|
|
||||||
let should_return_value = instruction.b_as_boolean();
|
|
||||||
|
|
||||||
if !should_return_value {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
let return_value = if let Some(register_index) = self.last_assigned_register {
|
|
||||||
self.open_register(register_index, position)?
|
|
||||||
} else {
|
|
||||||
return Err(VmError::StackUnderflow { position });
|
|
||||||
};
|
|
||||||
|
|
||||||
return Ok(Some(return_value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_register(
|
|
||||||
&mut self,
|
|
||||||
to_register: u8,
|
|
||||||
register: Register,
|
|
||||||
position: Span,
|
|
||||||
) -> Result<(), VmError> {
|
|
||||||
self.last_assigned_register = Some(to_register);
|
|
||||||
|
|
||||||
let length = self.stack.len();
|
|
||||||
let to_register = to_register as usize;
|
|
||||||
|
|
||||||
if length == Self::STACK_LIMIT {
|
|
||||||
return Err(VmError::StackOverflow { position });
|
|
||||||
}
|
|
||||||
|
|
||||||
match to_register.cmp(&length) {
|
|
||||||
Ordering::Less => {
|
|
||||||
log::trace!("Change R{to_register} to {register}");
|
|
||||||
|
|
||||||
self.stack[to_register] = register;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Ordering::Equal => {
|
|
||||||
log::trace!("Set R{to_register} to {register}");
|
|
||||||
|
|
||||||
self.stack.push(register);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Ordering::Greater => {
|
|
||||||
let difference = to_register - length;
|
|
||||||
|
|
||||||
for index in 0..difference {
|
|
||||||
log::trace!("Set R{index} to {register}");
|
|
||||||
|
|
||||||
self.stack.push(Register::Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
log::trace!("Set R{to_register} to {register}");
|
|
||||||
|
|
||||||
self.stack.push(register);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_constant(&self, index: u8, position: Span) -> Result<&Value, VmError> {
|
|
||||||
self.chunk
|
|
||||||
.get_constant(index)
|
|
||||||
.map_err(|error| VmError::Chunk { error, position })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn open_register(&self, register_index: u8, position: Span) -> Result<&Value, VmError> {
|
|
||||||
let register_index = register_index as usize;
|
|
||||||
let register =
|
|
||||||
self.stack
|
|
||||||
.get(register_index)
|
|
||||||
.ok_or_else(|| VmError::RegisterIndexOutOfBounds {
|
|
||||||
index: register_index,
|
|
||||||
position,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
match register {
|
|
||||||
Register::Value(value) => Ok(value),
|
|
||||||
Register::StackPointer(register_index) => self.open_register(*register_index, position),
|
|
||||||
Register::ConstantPointer(constant_index) => {
|
|
||||||
self.get_constant(*constant_index, position)
|
|
||||||
}
|
|
||||||
Register::ParentStackPointer(register_index) => {
|
|
||||||
let parent = self
|
|
||||||
.parent
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(VmError::ExpectedParent { position })?;
|
|
||||||
|
|
||||||
parent.open_register(*register_index, position)
|
|
||||||
}
|
|
||||||
Register::ParentConstantPointer(constant_index) => {
|
|
||||||
let parent = self
|
|
||||||
.parent
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(VmError::ExpectedParent { position })?;
|
|
||||||
|
|
||||||
parent.get_constant(*constant_index, position)
|
|
||||||
}
|
|
||||||
Register::Empty => Err(VmError::EmptyRegister {
|
|
||||||
index: register_index,
|
|
||||||
position,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn open_nonempty_registers(
|
|
||||||
&self,
|
|
||||||
register_index_range: Range<u8>,
|
|
||||||
position: Span,
|
|
||||||
) -> Result<Vec<&Value>, VmError> {
|
|
||||||
let mut values = Vec::with_capacity(register_index_range.len());
|
|
||||||
|
|
||||||
for register_index in register_index_range.clone() {
|
|
||||||
let register_index = register_index as usize;
|
|
||||||
let register = self.stack.get(register_index).ok_or_else(|| {
|
|
||||||
VmError::RegisterIndexOutOfBounds {
|
|
||||||
index: register_index,
|
|
||||||
position,
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let value = match register {
|
|
||||||
Register::Value(value) => value,
|
|
||||||
Register::StackPointer(register_index) => {
|
|
||||||
self.open_register(*register_index, position)?
|
|
||||||
}
|
|
||||||
Register::ConstantPointer(constant_index) => {
|
|
||||||
self.get_constant(*constant_index, position)?
|
|
||||||
}
|
|
||||||
Register::ParentStackPointer(register_index) => {
|
|
||||||
let parent = self
|
|
||||||
.parent
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(VmError::ExpectedParent { position })?;
|
|
||||||
|
|
||||||
parent.open_register(*register_index, position)?
|
|
||||||
}
|
|
||||||
Register::ParentConstantPointer(constant_index) => {
|
|
||||||
let parent = self
|
|
||||||
.parent
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(VmError::ExpectedParent { position })?;
|
|
||||||
|
|
||||||
parent.get_constant(*constant_index, position)?
|
|
||||||
}
|
|
||||||
Register::Empty => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
values.push(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if values.is_empty() {
|
|
||||||
Err(VmError::EmptyRegisters {
|
|
||||||
indexes: register_index_range,
|
|
||||||
position,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Ok(values)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read(&mut self, position: Span) -> Result<&(Instruction, Span), VmError> {
|
|
||||||
self.chunk
|
|
||||||
.expect_not_poisoned()
|
|
||||||
.map_err(|error| VmError::Chunk { error, position })?;
|
|
||||||
|
|
||||||
let max_ip = self.chunk.len() - 1;
|
|
||||||
|
|
||||||
if self.ip > max_ip {
|
|
||||||
return self.get_instruction(max_ip, position);
|
|
||||||
} else {
|
|
||||||
self.ip += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.get_instruction(self.ip - 1, position)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn define_local(
|
|
||||||
&mut self,
|
|
||||||
local_index: u8,
|
|
||||||
register_index: u8,
|
|
||||||
position: Span,
|
|
||||||
) -> Result<(), VmError> {
|
|
||||||
let local = self
|
|
||||||
.chunk
|
|
||||||
.get_local_mut(local_index)
|
|
||||||
.map_err(|error| VmError::Chunk { error, position })?;
|
|
||||||
|
|
||||||
log::debug!("Define local L{}", local_index);
|
|
||||||
|
|
||||||
local.register_index = register_index;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_local(&self, local_index: u8, position: Span) -> Result<&Local, VmError> {
|
|
||||||
self.chunk
|
|
||||||
.get_local(local_index)
|
|
||||||
.map_err(|error| VmError::Chunk { error, position })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_instruction(
|
|
||||||
&self,
|
|
||||||
index: usize,
|
|
||||||
position: Span,
|
|
||||||
) -> Result<&(Instruction, Span), VmError> {
|
|
||||||
self.chunk
|
|
||||||
.get_instruction(index)
|
|
||||||
.map_err(|error| VmError::Chunk { error, position })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
|
||||||
enum Register {
|
|
||||||
Empty,
|
|
||||||
Value(Value),
|
|
||||||
StackPointer(u8),
|
|
||||||
ConstantPointer(u8),
|
|
||||||
ParentStackPointer(u8),
|
|
||||||
ParentConstantPointer(u8),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Register {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Empty => write!(f, "empty"),
|
|
||||||
Self::Value(value) => write!(f, "{}", value),
|
|
||||||
Self::StackPointer(index) => write!(f, "R{}", index),
|
|
||||||
Self::ConstantPointer(index) => write!(f, "C{}", index),
|
|
||||||
Self::ParentStackPointer(index) => write!(f, "PR{}", index),
|
|
||||||
Self::ParentConstantPointer(index) => write!(f, "PC{}", index),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum VmError {
|
|
||||||
// Stack errors
|
|
||||||
StackOverflow { position: Span },
|
|
||||||
StackUnderflow { position: Span },
|
|
||||||
|
|
||||||
// Register errors
|
|
||||||
EmptyRegister { index: usize, position: Span },
|
|
||||||
EmptyRegisters { indexes: Range<u8>, position: Span },
|
|
||||||
RegisterIndexOutOfBounds { index: usize, position: Span },
|
|
||||||
|
|
||||||
// Execution errors
|
|
||||||
ExpectedBoolean { found: Value, position: Span },
|
|
||||||
ExpectedFunction { found: Value, position: Span },
|
|
||||||
ExpectedParent { position: Span },
|
|
||||||
|
|
||||||
// Wrappers for foreign errors
|
|
||||||
Chunk { error: ChunkError, position: Span },
|
|
||||||
NativeFunction(NativeFunctionError),
|
|
||||||
Value { error: ValueError, position: Span },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AnnotatedError for VmError {
|
|
||||||
fn title() -> &'static str {
|
|
||||||
"Runtime Error"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn description(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Self::Chunk { .. } => "Chunk error",
|
|
||||||
Self::EmptyRegister { .. } => "Empty register",
|
|
||||||
Self::EmptyRegisters { .. } => "Empty registers",
|
|
||||||
Self::ExpectedBoolean { .. } => "Expected boolean",
|
|
||||||
Self::ExpectedFunction { .. } => "Expected function",
|
|
||||||
Self::ExpectedParent { .. } => "Expected parent",
|
|
||||||
Self::NativeFunction(error) => error.description(),
|
|
||||||
Self::RegisterIndexOutOfBounds { .. } => "Register index out of bounds",
|
|
||||||
Self::StackOverflow { .. } => "Stack overflow",
|
|
||||||
Self::StackUnderflow { .. } => "Stack underflow",
|
|
||||||
Self::Value { .. } => "Value error",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn details(&self) -> Option<String> {
|
|
||||||
match self {
|
|
||||||
Self::Chunk { error, .. } => Some(error.to_string()),
|
|
||||||
Self::EmptyRegister { index, .. } => Some(format!("Register R{index} is empty")),
|
|
||||||
Self::EmptyRegisters { indexes: range, .. } => Some(format!(
|
|
||||||
"Registers R{} to R{} are empty",
|
|
||||||
range.start, range.end
|
|
||||||
)),
|
|
||||||
Self::ExpectedFunction { found, .. } => Some(format!("{found} is not a function")),
|
|
||||||
Self::RegisterIndexOutOfBounds { index, .. } => {
|
|
||||||
Some(format!("Register {index} does not exist"))
|
|
||||||
}
|
|
||||||
Self::NativeFunction(error) => error.details(),
|
|
||||||
Self::Value { error, .. } => Some(error.to_string()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn position(&self) -> Span {
|
|
||||||
match self {
|
|
||||||
Self::Chunk { position, .. } => *position,
|
|
||||||
Self::EmptyRegister { position, .. } => *position,
|
|
||||||
Self::EmptyRegisters { position, .. } => *position,
|
|
||||||
Self::ExpectedBoolean { position, .. } => *position,
|
|
||||||
Self::ExpectedFunction { position, .. } => *position,
|
|
||||||
Self::ExpectedParent { position } => *position,
|
|
||||||
Self::NativeFunction(error) => error.position(),
|
|
||||||
Self::RegisterIndexOutOfBounds { position, .. } => *position,
|
|
||||||
Self::StackOverflow { position } => *position,
|
|
||||||
Self::StackUnderflow { position } => *position,
|
|
||||||
Self::Value { position, .. } => *position,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
use dust_lang::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn constant() {
|
|
||||||
let source = "42";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::load_constant(0, 0, false), Span(0, 2)),
|
|
||||||
(Instruction::r#return(true), Span(2, 2))
|
|
||||||
],
|
|
||||||
vec![Value::integer(42)],
|
|
||||||
vec![]
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::integer(42))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty() {
|
|
||||||
let source = "";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![(Instruction::r#return(false), Span(0, 0))],
|
|
||||||
vec![],
|
|
||||||
vec![]
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(run(source), Ok(None));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parentheses_precedence() {
|
|
||||||
let source = "(1 + 2) * 3";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
*Instruction::add(0, 0, 1)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(3, 4)
|
|
||||||
),
|
|
||||||
(
|
|
||||||
*Instruction::multiply(1, 0, 2).set_c_is_constant(),
|
|
||||||
Span(8, 9)
|
|
||||||
),
|
|
||||||
(Instruction::r#return(true), Span(11, 11)),
|
|
||||||
],
|
|
||||||
vec![Value::integer(1), Value::integer(2), Value::integer(3)],
|
|
||||||
vec![]
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::integer(9))));
|
|
||||||
}
|
|
@ -1,169 +0,0 @@
|
|||||||
use dust_lang::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn equal() {
|
|
||||||
let source = "1 == 2";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
*Instruction::equal(true, 0, 1)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(2, 4)
|
|
||||||
),
|
|
||||||
(Instruction::jump(1, true), Span(2, 4)),
|
|
||||||
(Instruction::load_boolean(0, true, true), Span(2, 4)),
|
|
||||||
(Instruction::load_boolean(0, false, false), Span(2, 4)),
|
|
||||||
(Instruction::r#return(true), Span(6, 6)),
|
|
||||||
],
|
|
||||||
vec![Value::integer(1), Value::integer(2)],
|
|
||||||
vec![]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn greater() {
|
|
||||||
let source = "1 > 2";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
*Instruction::less_equal(false, 0, 1)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(2, 3)
|
|
||||||
),
|
|
||||||
(Instruction::jump(1, true), Span(2, 3)),
|
|
||||||
(Instruction::load_boolean(0, true, true), Span(2, 3)),
|
|
||||||
(Instruction::load_boolean(0, false, false), Span(2, 3)),
|
|
||||||
(Instruction::r#return(true), Span(5, 5)),
|
|
||||||
],
|
|
||||||
vec![Value::integer(1), Value::integer(2)],
|
|
||||||
vec![]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn greater_than_or_equal() {
|
|
||||||
let source = "1 >= 2";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
*Instruction::less(false, 0, 1)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(2, 4)
|
|
||||||
),
|
|
||||||
(Instruction::jump(1, true), Span(2, 4)),
|
|
||||||
(Instruction::load_boolean(0, true, true), Span(2, 4)),
|
|
||||||
(Instruction::load_boolean(0, false, false), Span(2, 4)),
|
|
||||||
(Instruction::r#return(true), Span(6, 6)),
|
|
||||||
],
|
|
||||||
vec![Value::integer(1), Value::integer(2)],
|
|
||||||
vec![]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn less_than() {
|
|
||||||
let source = "1 < 2";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
*Instruction::less(true, 0, 1)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(2, 3)
|
|
||||||
),
|
|
||||||
(Instruction::jump(1, true), Span(2, 3)),
|
|
||||||
(Instruction::load_boolean(0, true, true), Span(2, 3)),
|
|
||||||
(Instruction::load_boolean(0, false, false), Span(2, 3)),
|
|
||||||
(Instruction::r#return(true), Span(5, 5)),
|
|
||||||
],
|
|
||||||
vec![Value::integer(1), Value::integer(2)],
|
|
||||||
vec![]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn less_than_or_equal() {
|
|
||||||
let source = "1 <= 2";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
*Instruction::less_equal(true, 0, 1)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(2, 4)
|
|
||||||
),
|
|
||||||
(Instruction::jump(1, true), Span(2, 4)),
|
|
||||||
(Instruction::load_boolean(0, true, true), Span(2, 4)),
|
|
||||||
(Instruction::load_boolean(0, false, false), Span(2, 4)),
|
|
||||||
(Instruction::r#return(true), Span(6, 6)),
|
|
||||||
],
|
|
||||||
vec![Value::integer(1), Value::integer(2)],
|
|
||||||
vec![]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn not_equal() {
|
|
||||||
let source = "1 != 2";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
*Instruction::equal(false, 0, 1)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(2, 4)
|
|
||||||
),
|
|
||||||
(Instruction::jump(1, true), Span(2, 4)),
|
|
||||||
(Instruction::load_boolean(0, true, true), Span(2, 4)),
|
|
||||||
(Instruction::load_boolean(0, false, false), Span(2, 4)),
|
|
||||||
(Instruction::r#return(true), Span(6, 6)),
|
|
||||||
],
|
|
||||||
vec![Value::integer(1), Value::integer(2)],
|
|
||||||
vec![]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
|
|
||||||
}
|
|
@ -1,420 +0,0 @@
|
|||||||
use dust_lang::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn equality_assignment_long() {
|
|
||||||
let source = "let a = if 4 == 4 { true } else { false }; a";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
*Instruction::equal(true, 0, 0)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(13, 15)
|
|
||||||
),
|
|
||||||
(Instruction::jump(1, true), Span(18, 19)),
|
|
||||||
(Instruction::load_boolean(0, true, true), Span(20, 24)),
|
|
||||||
(Instruction::load_boolean(0, false, false), Span(34, 39)),
|
|
||||||
(Instruction::define_local(0, 0, false), Span(4, 5)),
|
|
||||||
(Instruction::get_local(1, 0), Span(43, 44)),
|
|
||||||
(Instruction::r#return(true), Span(44, 44)),
|
|
||||||
],
|
|
||||||
vec![Value::integer(4), Value::string("a")],
|
|
||||||
vec![Local::new(
|
|
||||||
1,
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
Scope {
|
|
||||||
depth: 0,
|
|
||||||
block_index: 0
|
|
||||||
},
|
|
||||||
0
|
|
||||||
)]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn equality_assignment_short() {
|
|
||||||
let source = "let a = 4 == 4 a";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
*Instruction::equal(true, 0, 0)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(10, 12)
|
|
||||||
),
|
|
||||||
(Instruction::jump(1, true), Span(10, 12)),
|
|
||||||
(Instruction::load_boolean(0, true, true), Span(10, 12)),
|
|
||||||
(Instruction::load_boolean(0, false, false), Span(10, 12)),
|
|
||||||
(Instruction::define_local(0, 0, false), Span(4, 5)),
|
|
||||||
(Instruction::get_local(1, 0), Span(15, 16)),
|
|
||||||
(Instruction::r#return(true), Span(16, 16)),
|
|
||||||
],
|
|
||||||
vec![Value::integer(4), Value::string("a")],
|
|
||||||
vec![Local::new(1, None, false, Scope::default(), 0)]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn if_else_assigment_false() {
|
|
||||||
let source = r#"
|
|
||||||
let a = if 4 == 3 {
|
|
||||||
1; 2; 3; 4;
|
|
||||||
panic()
|
|
||||||
} else {
|
|
||||||
1; 2; 3; 4;
|
|
||||||
42
|
|
||||||
};
|
|
||||||
a"#;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
*Instruction::equal(true, 0, 1)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(22, 24)
|
|
||||||
),
|
|
||||||
(Instruction::jump(6, true), Span(27, 28)),
|
|
||||||
(Instruction::load_constant(0, 2, false), Span(41, 42)),
|
|
||||||
(Instruction::load_constant(1, 3, false), Span(44, 45)),
|
|
||||||
(Instruction::load_constant(2, 1, false), Span(47, 48)),
|
|
||||||
(Instruction::load_constant(3, 0, false), Span(50, 51)),
|
|
||||||
(
|
|
||||||
Instruction::call_native(4, NativeFunction::Panic, 0),
|
|
||||||
Span(65, 72)
|
|
||||||
),
|
|
||||||
(Instruction::jump(5, true), Span(138, 139)),
|
|
||||||
(Instruction::load_constant(5, 2, false), Span(102, 103)),
|
|
||||||
(Instruction::load_constant(6, 3, false), Span(105, 106)),
|
|
||||||
(Instruction::load_constant(7, 1, false), Span(108, 109)),
|
|
||||||
(Instruction::load_constant(8, 0, false), Span(111, 112)),
|
|
||||||
(Instruction::load_constant(9, 4, false), Span(126, 128)),
|
|
||||||
(Instruction::r#move(9, 4), Span(138, 139)),
|
|
||||||
(Instruction::define_local(9, 0, false), Span(13, 14)),
|
|
||||||
(Instruction::get_local(10, 0), Span(148, 149)),
|
|
||||||
(Instruction::r#return(true), Span(149, 149)),
|
|
||||||
],
|
|
||||||
vec![
|
|
||||||
Value::integer(4),
|
|
||||||
Value::integer(3),
|
|
||||||
Value::integer(1),
|
|
||||||
Value::integer(2),
|
|
||||||
Value::integer(42),
|
|
||||||
Value::string("a")
|
|
||||||
],
|
|
||||||
vec![Local::new(5, None, false, Scope::default(), 0)]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::integer(42))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn if_else_assigment_true() {
|
|
||||||
let source = r#"
|
|
||||||
let a = if 4 == 4 {
|
|
||||||
1; 2; 3; 4;
|
|
||||||
42
|
|
||||||
} else {
|
|
||||||
1; 2; 3; 4;
|
|
||||||
panic()
|
|
||||||
};
|
|
||||||
a"#;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
*Instruction::equal(true, 0, 0)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(22, 24)
|
|
||||||
),
|
|
||||||
(Instruction::jump(6, true), Span(27, 28)),
|
|
||||||
(Instruction::load_constant(0, 1, false), Span(41, 42)),
|
|
||||||
(Instruction::load_constant(1, 2, false), Span(44, 45)),
|
|
||||||
(Instruction::load_constant(2, 3, false), Span(47, 48)),
|
|
||||||
(Instruction::load_constant(3, 0, false), Span(50, 51)),
|
|
||||||
(Instruction::load_constant(4, 4, false), Span(65, 67)),
|
|
||||||
(Instruction::jump(5, true), Span(138, 139)),
|
|
||||||
(Instruction::load_constant(5, 1, false), Span(97, 98)),
|
|
||||||
(Instruction::load_constant(6, 2, false), Span(100, 101)),
|
|
||||||
(Instruction::load_constant(7, 3, false), Span(103, 104)),
|
|
||||||
(Instruction::load_constant(8, 0, false), Span(106, 107)),
|
|
||||||
(
|
|
||||||
Instruction::call_native(9, NativeFunction::Panic, 0),
|
|
||||||
Span(121, 128)
|
|
||||||
),
|
|
||||||
(Instruction::r#move(9, 4), Span(138, 139)),
|
|
||||||
(Instruction::define_local(9, 0, false), Span(13, 14)),
|
|
||||||
(Instruction::get_local(10, 0), Span(148, 149)),
|
|
||||||
(Instruction::r#return(true), Span(149, 149)),
|
|
||||||
],
|
|
||||||
vec![
|
|
||||||
Value::integer(4),
|
|
||||||
Value::integer(1),
|
|
||||||
Value::integer(2),
|
|
||||||
Value::integer(3),
|
|
||||||
Value::integer(42),
|
|
||||||
Value::string("a")
|
|
||||||
],
|
|
||||||
vec![Local::new(5, None, false, Scope::default(), 0)]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::integer(42))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn if_else_complex() {
|
|
||||||
let source = "
|
|
||||||
if 1 == 1 {
|
|
||||||
1; 2; 3; 4;
|
|
||||||
} else {
|
|
||||||
1; 2; 3; 4;
|
|
||||||
}";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
*Instruction::equal(true, 0, 0)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(14, 16)
|
|
||||||
),
|
|
||||||
(Instruction::jump(5, true), Span(19, 20)),
|
|
||||||
(Instruction::load_constant(0, 0, false), Span(33, 34)),
|
|
||||||
(Instruction::load_constant(1, 1, false), Span(36, 37)),
|
|
||||||
(Instruction::load_constant(2, 2, false), Span(39, 40)),
|
|
||||||
(Instruction::load_constant(3, 3, false), Span(42, 43)),
|
|
||||||
(Instruction::jump(4, true), Span(95, 95)),
|
|
||||||
(Instruction::load_constant(4, 0, false), Span(74, 75)),
|
|
||||||
(Instruction::load_constant(5, 1, false), Span(77, 78)),
|
|
||||||
(Instruction::load_constant(6, 2, false), Span(80, 81)),
|
|
||||||
(Instruction::load_constant(7, 3, false), Span(83, 84)),
|
|
||||||
(Instruction::r#move(7, 3), Span(95, 95)),
|
|
||||||
(Instruction::r#return(false), Span(95, 95)),
|
|
||||||
],
|
|
||||||
vec![
|
|
||||||
Value::integer(1),
|
|
||||||
Value::integer(2),
|
|
||||||
Value::integer(3),
|
|
||||||
Value::integer(4),
|
|
||||||
],
|
|
||||||
vec![]
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(None));
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn if_else_nested() {
|
|
||||||
// let source = r#"
|
|
||||||
// if 0 == 1 {
|
|
||||||
// if 0 == 2 {
|
|
||||||
// 1;
|
|
||||||
// } else {
|
|
||||||
// 2;
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// if 0 == 3 {
|
|
||||||
// 3;
|
|
||||||
// } else {
|
|
||||||
// 4;
|
|
||||||
// }
|
|
||||||
// }"#;
|
|
||||||
|
|
||||||
// assert_eq!(
|
|
||||||
// parse(source),
|
|
||||||
// Ok(Chunk::with_data(
|
|
||||||
// None,
|
|
||||||
// vec![
|
|
||||||
// (
|
|
||||||
// *Instruction::equal(true, 0, 1)
|
|
||||||
// .set_b_is_constant()
|
|
||||||
// .set_c_is_constant(),
|
|
||||||
// Span(14, 16)
|
|
||||||
// ),
|
|
||||||
// (Instruction::jump(7, true), Span(14, 16)),
|
|
||||||
// (
|
|
||||||
// *Instruction::equal(true, 0, 2)
|
|
||||||
// .set_b_is_constant()
|
|
||||||
// .set_c_is_constant(),
|
|
||||||
// Span(38, 41)
|
|
||||||
// ),
|
|
||||||
// (Instruction::jump(3, true), Span(38, 41)),
|
|
||||||
// (Instruction::load_constant(0, 1, false), Span(61, 62)),
|
|
||||||
// (Instruction::jump(1, true1), Span(95, 95)),
|
|
||||||
// (
|
|
||||||
// *Instruction::equal(true, 0, 3)
|
|
||||||
// .set_b_is_constant()
|
|
||||||
// .set_c_is_constant(),
|
|
||||||
// Span(77, 79)
|
|
||||||
// ),
|
|
||||||
// (Instruction::jump(3, true), Span(77, 79)),
|
|
||||||
// (Instruction::load_constant(0, 2, false), Span(94, 95)),
|
|
||||||
// (Instruction::jump(1, true1), Span(95, 95)),
|
|
||||||
// (Instruction::load_constant(0, 3, false), Span(114, 115)),
|
|
||||||
// (Instruction::jump(1, true1), Span(95, 95)),
|
|
||||||
// (Instruction::load_constant(0, 4, false), Span(134, 135)),
|
|
||||||
// (Instruction::r#return(true), Span(146, 146)),
|
|
||||||
// ],
|
|
||||||
// vec![
|
|
||||||
// Value::integer(0),
|
|
||||||
// Value::integer(1),
|
|
||||||
// Value::integer(0),
|
|
||||||
// Value::integer(2),
|
|
||||||
// Value::integer(1),
|
|
||||||
// Value::integer(0),
|
|
||||||
// Value::integer(3),
|
|
||||||
// Value::integer(3),
|
|
||||||
// Value::integer(4)
|
|
||||||
// ],
|
|
||||||
// vec![]
|
|
||||||
// ))
|
|
||||||
// );
|
|
||||||
|
|
||||||
// assert_eq!(run(source), Ok(Some(Value::integer(4))));
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn if_else_false() {
|
|
||||||
let source = "if 1 == 2 { panic() } else { 42 }";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
*Instruction::equal(true, 0, 1)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(5, 7)
|
|
||||||
),
|
|
||||||
(Instruction::jump(1, true), Span(10, 11)),
|
|
||||||
(
|
|
||||||
Instruction::call_native(0, NativeFunction::Panic, 0),
|
|
||||||
Span(12, 19)
|
|
||||||
),
|
|
||||||
(Instruction::load_constant(1, 2, true), Span(29, 31)),
|
|
||||||
(Instruction::r#move(1, 0), Span(33, 33)),
|
|
||||||
(Instruction::r#return(true), Span(33, 33)),
|
|
||||||
],
|
|
||||||
vec![Value::integer(1), Value::integer(2), Value::integer(42)],
|
|
||||||
vec![]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::integer(42))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn if_else_true() {
|
|
||||||
let source = "if 1 == 1 { 42 } else { panic() }";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
*Instruction::equal(true, 0, 0)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(5, 7)
|
|
||||||
),
|
|
||||||
(Instruction::jump(1, true), Span(10, 11)),
|
|
||||||
(Instruction::load_constant(0, 1, true), Span(12, 14)),
|
|
||||||
(
|
|
||||||
Instruction::call_native(1, NativeFunction::Panic, 0),
|
|
||||||
Span(24, 31)
|
|
||||||
),
|
|
||||||
(Instruction::r#move(1, 0), Span(33, 33)),
|
|
||||||
(Instruction::r#return(true), Span(33, 33))
|
|
||||||
],
|
|
||||||
vec![Value::integer(1), Value::integer(42)],
|
|
||||||
vec![]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::integer(42))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn if_false() {
|
|
||||||
let source = "if 1 == 2 { 2 }";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
*Instruction::equal(true, 0, 1)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(5, 7)
|
|
||||||
),
|
|
||||||
(Instruction::jump(1, true), Span(10, 11)),
|
|
||||||
(Instruction::load_constant(0, 1, false), Span(12, 13)),
|
|
||||||
(Instruction::r#return(false), Span(15, 15))
|
|
||||||
],
|
|
||||||
vec![Value::integer(1), Value::integer(2)],
|
|
||||||
vec![]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(None));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn if_true() {
|
|
||||||
let source = "if 1 == 1 { 2 }";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
*Instruction::equal(true, 0, 0)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(5, 7)
|
|
||||||
),
|
|
||||||
(Instruction::jump(1, true), Span(10, 11)),
|
|
||||||
(Instruction::load_constant(0, 1, false), Span(12, 13)),
|
|
||||||
(Instruction::r#return(false), Span(15, 15))
|
|
||||||
],
|
|
||||||
vec![Value::integer(1), Value::integer(2)],
|
|
||||||
vec![]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(None));
|
|
||||||
}
|
|
@ -1,126 +0,0 @@
|
|||||||
use dust_lang::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn function() {
|
|
||||||
let source = "fn(a: int, b: int) -> int { a + b }";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
run(source),
|
|
||||||
Ok(Some(Value::function(
|
|
||||||
Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::add(2, 0, 1), Span(30, 31)),
|
|
||||||
(Instruction::r#return(true), Span(35, 35)),
|
|
||||||
],
|
|
||||||
vec![Value::string("a"), Value::string("b"),],
|
|
||||||
vec![
|
|
||||||
Local::new(0, Some(Type::Integer), false, Scope::default(), 0),
|
|
||||||
Local::new(1, Some(Type::Integer), false, Scope::default(), 1)
|
|
||||||
]
|
|
||||||
),
|
|
||||||
FunctionType {
|
|
||||||
type_parameters: None,
|
|
||||||
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
|
|
||||||
return_type: Some(Box::new(Type::Integer)),
|
|
||||||
}
|
|
||||||
)))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn function_call() {
|
|
||||||
let source = "fn(a: int, b: int) -> int { a + b }(1, 2)";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::load_constant(0, 0, false), Span(0, 36)),
|
|
||||||
(Instruction::load_constant(1, 1, false), Span(36, 37)),
|
|
||||||
(Instruction::load_constant(2, 2, false), Span(39, 40)),
|
|
||||||
(Instruction::call(3, 0, 2), Span(35, 41)),
|
|
||||||
(Instruction::r#return(true), Span(41, 41)),
|
|
||||||
],
|
|
||||||
vec![
|
|
||||||
Value::function(
|
|
||||||
Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::add(2, 0, 1), Span(30, 31)),
|
|
||||||
(Instruction::r#return(true), Span(35, 36)),
|
|
||||||
],
|
|
||||||
vec![Value::string("a"), Value::string("b"),],
|
|
||||||
vec![
|
|
||||||
Local::new(0, Some(Type::Integer), false, Scope::default(), 0),
|
|
||||||
Local::new(1, Some(Type::Integer), false, Scope::default(), 1)
|
|
||||||
]
|
|
||||||
),
|
|
||||||
FunctionType {
|
|
||||||
type_parameters: None,
|
|
||||||
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
|
|
||||||
return_type: Some(Box::new(Type::Integer)),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
Value::integer(1),
|
|
||||||
Value::integer(2)
|
|
||||||
],
|
|
||||||
vec![]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::integer(3))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn function_declaration() {
|
|
||||||
let source = "fn add (a: int, b: int) -> int { a + b }";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::load_constant(0, 1, false), Span(0, 40)),
|
|
||||||
(Instruction::define_local(0, 0, false), Span(3, 6)),
|
|
||||||
(Instruction::r#return(false), Span(40, 40))
|
|
||||||
],
|
|
||||||
vec![
|
|
||||||
Value::string("add"),
|
|
||||||
Value::function(
|
|
||||||
Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::add(2, 0, 1), Span(35, 36)),
|
|
||||||
(Instruction::r#return(true), Span(40, 40)),
|
|
||||||
],
|
|
||||||
vec![Value::string("a"), Value::string("b")],
|
|
||||||
vec![
|
|
||||||
Local::new(0, Some(Type::Integer), false, Scope::default(), 0),
|
|
||||||
Local::new(1, Some(Type::Integer), false, Scope::default(), 1)
|
|
||||||
]
|
|
||||||
),
|
|
||||||
FunctionType {
|
|
||||||
type_parameters: None,
|
|
||||||
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
|
|
||||||
return_type: Some(Box::new(Type::Integer)),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
vec![Local::new(
|
|
||||||
0,
|
|
||||||
Some(Type::Function(FunctionType {
|
|
||||||
type_parameters: None,
|
|
||||||
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
|
|
||||||
return_type: Some(Box::new(Type::Integer)),
|
|
||||||
})),
|
|
||||||
false,
|
|
||||||
Scope::default(),
|
|
||||||
0
|
|
||||||
),],
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(None));
|
|
||||||
}
|
|
@ -1,127 +0,0 @@
|
|||||||
use dust_lang::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty_list() {
|
|
||||||
let source = "[]";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::load_list(0, 0), Span(0, 2)),
|
|
||||||
(Instruction::r#return(true), Span(2, 2)),
|
|
||||||
],
|
|
||||||
vec![],
|
|
||||||
vec![]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::abstract_list(0, 0, Type::Any))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn list() {
|
|
||||||
let source = "[1, 2, 3]";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::load_constant(0, 0, false), Span(1, 2)),
|
|
||||||
(Instruction::load_constant(1, 1, false), Span(4, 5)),
|
|
||||||
(Instruction::load_constant(2, 2, false), Span(7, 8)),
|
|
||||||
(Instruction::load_list(3, 0), Span(0, 9)),
|
|
||||||
(Instruction::r#return(true), Span(9, 9)),
|
|
||||||
],
|
|
||||||
vec![Value::integer(1), Value::integer(2), Value::integer(3)],
|
|
||||||
vec![]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
run(source),
|
|
||||||
Ok(Some(Value::abstract_list(0, 3, Type::Integer)))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn list_with_complex_expression() {
|
|
||||||
let source = "[1, 2 + 3 - 4 * 5]";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::load_constant(0, 0, false), Span(1, 2)),
|
|
||||||
(
|
|
||||||
*Instruction::add(1, 1, 2)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(6, 7)
|
|
||||||
),
|
|
||||||
(
|
|
||||||
*Instruction::multiply(2, 3, 4)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(14, 15)
|
|
||||||
),
|
|
||||||
(Instruction::subtract(3, 1, 2), Span(10, 11)),
|
|
||||||
(Instruction::close(1, 3), Span(17, 18)),
|
|
||||||
(Instruction::load_list(4, 0), Span(0, 18)),
|
|
||||||
(Instruction::r#return(true), Span(18, 18)),
|
|
||||||
],
|
|
||||||
vec![
|
|
||||||
Value::integer(1),
|
|
||||||
Value::integer(2),
|
|
||||||
Value::integer(3),
|
|
||||||
Value::integer(4),
|
|
||||||
Value::integer(5)
|
|
||||||
],
|
|
||||||
vec![]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
run(source),
|
|
||||||
Ok(Some(Value::abstract_list(0, 4, Type::Integer)))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn list_with_simple_expression() {
|
|
||||||
let source = "[1, 2 + 3, 4]";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::load_constant(0, 0, false), Span(1, 2)),
|
|
||||||
(
|
|
||||||
*Instruction::add(1, 1, 2)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(6, 7)
|
|
||||||
),
|
|
||||||
(Instruction::load_constant(2, 3, false), Span(11, 12)),
|
|
||||||
(Instruction::load_list(3, 0), Span(0, 13)),
|
|
||||||
(Instruction::r#return(true), Span(13, 13)),
|
|
||||||
],
|
|
||||||
vec![
|
|
||||||
Value::integer(1),
|
|
||||||
Value::integer(2),
|
|
||||||
Value::integer(3),
|
|
||||||
Value::integer(4),
|
|
||||||
],
|
|
||||||
vec![]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
run(source),
|
|
||||||
Ok(Some(Value::abstract_list(0, 3, Type::Integer)))
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
use dust_lang::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn and() {
|
|
||||||
let source = "true && false";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::load_boolean(0, true, false), Span(0, 4)),
|
|
||||||
(Instruction::test(0, false), Span(5, 7)),
|
|
||||||
(Instruction::jump(1, true), Span(5, 7)),
|
|
||||||
(Instruction::load_boolean(1, false, false), Span(8, 13)),
|
|
||||||
(Instruction::r#return(true), Span(13, 13)),
|
|
||||||
],
|
|
||||||
vec![],
|
|
||||||
vec![]
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn or() {
|
|
||||||
let source = "true || false";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::load_boolean(0, true, false), Span(0, 4)),
|
|
||||||
(Instruction::test(0, true), Span(5, 7)),
|
|
||||||
(Instruction::jump(1, true), Span(5, 7)),
|
|
||||||
(Instruction::load_boolean(1, false, false), Span(8, 13)),
|
|
||||||
(Instruction::r#return(true), Span(13, 13)),
|
|
||||||
],
|
|
||||||
vec![],
|
|
||||||
vec![]
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn variable_and() {
|
|
||||||
let source = "let a = true; let b = false; a && b";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::load_boolean(0, true, false), Span(8, 12)),
|
|
||||||
(Instruction::define_local(0, 0, false), Span(4, 5)),
|
|
||||||
(Instruction::load_boolean(1, false, false), Span(22, 27)),
|
|
||||||
(Instruction::define_local(1, 1, false), Span(18, 19)),
|
|
||||||
(Instruction::get_local(2, 0), Span(29, 30)),
|
|
||||||
(Instruction::test(2, false), Span(31, 33)),
|
|
||||||
(Instruction::jump(1, true), Span(31, 33)),
|
|
||||||
(Instruction::get_local(3, 1), Span(34, 35)),
|
|
||||||
(Instruction::r#return(true), Span(35, 35)),
|
|
||||||
],
|
|
||||||
vec![Value::string("a"), Value::string("b"),],
|
|
||||||
vec![
|
|
||||||
Local::new(0, None, false, Scope::default(), 0),
|
|
||||||
Local::new(1, None, false, Scope::default(), 1),
|
|
||||||
]
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
use dust_lang::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn r#while() {
|
|
||||||
let source = "let mut x = 0; while x < 5 { x = x + 1 } x";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::load_constant(0, 0, false), Span(12, 13)),
|
|
||||||
(Instruction::define_local(0, 0, true), Span(8, 9)),
|
|
||||||
(
|
|
||||||
*Instruction::less(true, 0, 2).set_c_is_constant(),
|
|
||||||
Span(23, 24)
|
|
||||||
),
|
|
||||||
(Instruction::jump(2, true), Span(41, 42)),
|
|
||||||
(*Instruction::add(0, 0, 3).set_c_is_constant(), Span(39, 40)),
|
|
||||||
(Instruction::jump(3, false), Span(41, 42)),
|
|
||||||
(Instruction::get_local(1, 0), Span(41, 42)),
|
|
||||||
(Instruction::r#return(true), Span(42, 42)),
|
|
||||||
],
|
|
||||||
vec![
|
|
||||||
Value::integer(0),
|
|
||||||
Value::string("x"),
|
|
||||||
Value::integer(5),
|
|
||||||
Value::integer(1),
|
|
||||||
],
|
|
||||||
vec![Local::new(1, None, true, Scope::default(), 0),]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::integer(5))));
|
|
||||||
}
|
|
@ -1,324 +0,0 @@
|
|||||||
use dust_lang::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add() {
|
|
||||||
let source = "1 + 2";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
*Instruction::add(0, 0, 1)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(2, 3)
|
|
||||||
),
|
|
||||||
(Instruction::r#return(true), Span(5, 5))
|
|
||||||
],
|
|
||||||
vec![Value::integer(1), Value::integer(2)],
|
|
||||||
vec![]
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::integer(3))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add_assign() {
|
|
||||||
let source = "let mut a = 1; a += 2; a";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::load_constant(0, 0, false), Span(12, 13)),
|
|
||||||
(Instruction::define_local(0, 0, true), Span(8, 9)),
|
|
||||||
(*Instruction::add(0, 0, 2).set_c_is_constant(), Span(17, 19)),
|
|
||||||
(Instruction::get_local(1, 0), Span(23, 24)),
|
|
||||||
(Instruction::r#return(true), Span(24, 24))
|
|
||||||
],
|
|
||||||
vec![Value::integer(1), Value::string("a"), Value::integer(2)],
|
|
||||||
vec![Local::new(1, None, true, Scope::default(), 0)]
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::integer(3))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add_assign_expects_mutable_variable() {
|
|
||||||
let source = "1 += 2";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Err(DustError::Compile {
|
|
||||||
error: CompileError::ExpectedMutableVariable {
|
|
||||||
found: Token::Integer("1").to_owned(),
|
|
||||||
position: Span(0, 1)
|
|
||||||
},
|
|
||||||
source
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn add_expects_integer_float_or_string() {
|
|
||||||
// let source = "true + false";
|
|
||||||
|
|
||||||
// assert_eq!(
|
|
||||||
// parse(source),
|
|
||||||
// Err(DustError::Parse {
|
|
||||||
// error: ParseError::ExpectedIntegerFloatOrString {
|
|
||||||
// found: Token::True,
|
|
||||||
// position: Span(0, 3)
|
|
||||||
// },
|
|
||||||
// source
|
|
||||||
// })
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn divide() {
|
|
||||||
let source = "2 / 2";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
*Instruction::divide(0, 0, 0)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(2, 3)
|
|
||||||
),
|
|
||||||
(Instruction::r#return(true), Span(5, 5))
|
|
||||||
],
|
|
||||||
vec![Value::integer(2)],
|
|
||||||
vec![]
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::integer(1))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn divide_assign() {
|
|
||||||
let source = "let mut a = 2; a /= 2; a";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::load_constant(0, 0, false), Span(12, 13)),
|
|
||||||
(Instruction::define_local(0, 0, true), Span(8, 9)),
|
|
||||||
(
|
|
||||||
*Instruction::divide(0, 0, 0).set_c_is_constant(),
|
|
||||||
Span(17, 19)
|
|
||||||
),
|
|
||||||
(Instruction::get_local(1, 0), Span(23, 24)),
|
|
||||||
(Instruction::r#return(true), Span(24, 24))
|
|
||||||
],
|
|
||||||
vec![Value::integer(2), Value::string("a")],
|
|
||||||
vec![Local::new(1, None, true, Scope::default(), 0)]
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::integer(1))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn divide_assign_expects_mutable_variable() {
|
|
||||||
let source = "1 -= 2";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Err(DustError::Compile {
|
|
||||||
error: CompileError::ExpectedMutableVariable {
|
|
||||||
found: Token::Integer("1").to_owned(),
|
|
||||||
position: Span(0, 1)
|
|
||||||
},
|
|
||||||
source
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn math_operator_precedence() {
|
|
||||||
let source = "1 + 2 - 3 * 4 / 5";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
*Instruction::add(0, 0, 1)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(2, 3)
|
|
||||||
),
|
|
||||||
(
|
|
||||||
*Instruction::multiply(1, 2, 3)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(10, 11)
|
|
||||||
),
|
|
||||||
(
|
|
||||||
*Instruction::divide(2, 1, 4).set_c_is_constant(),
|
|
||||||
Span(14, 15)
|
|
||||||
),
|
|
||||||
(Instruction::subtract(3, 0, 2), Span(6, 7)),
|
|
||||||
(Instruction::r#return(true), Span(17, 17)),
|
|
||||||
],
|
|
||||||
vec![
|
|
||||||
Value::integer(1),
|
|
||||||
Value::integer(2),
|
|
||||||
Value::integer(3),
|
|
||||||
Value::integer(4),
|
|
||||||
Value::integer(5),
|
|
||||||
],
|
|
||||||
vec![]
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::integer(1))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn multiply() {
|
|
||||||
let source = "1 * 2";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
*Instruction::multiply(0, 0, 1)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(2, 3)
|
|
||||||
),
|
|
||||||
(Instruction::r#return(true), Span(5, 5)),
|
|
||||||
],
|
|
||||||
vec![Value::integer(1), Value::integer(2)],
|
|
||||||
vec![]
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::integer(2))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn multiply_assign() {
|
|
||||||
let source = "let mut a = 2; a *= 3 a";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::load_constant(0, 0, false), Span(12, 13)),
|
|
||||||
(Instruction::define_local(0, 0, true), Span(8, 9)),
|
|
||||||
(
|
|
||||||
*Instruction::multiply(0, 0, 2).set_c_is_constant(),
|
|
||||||
Span(17, 19)
|
|
||||||
),
|
|
||||||
(Instruction::get_local(1, 0), Span(22, 23)),
|
|
||||||
(Instruction::r#return(true), Span(23, 23))
|
|
||||||
],
|
|
||||||
vec![Value::integer(2), Value::string("a"), Value::integer(3)],
|
|
||||||
vec![Local::new(1, None, true, Scope::default(), 0),]
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::integer(6))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn multiply_assign_expects_mutable_variable() {
|
|
||||||
let source = "1 *= 2";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Err(DustError::Compile {
|
|
||||||
error: CompileError::ExpectedMutableVariable {
|
|
||||||
found: Token::Integer("1").to_owned(),
|
|
||||||
position: Span(0, 1)
|
|
||||||
},
|
|
||||||
source
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn subtract() {
|
|
||||||
let source = "1 - 2";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
*Instruction::subtract(0, 0, 1)
|
|
||||||
.set_b_is_constant()
|
|
||||||
.set_c_is_constant(),
|
|
||||||
Span(2, 3)
|
|
||||||
),
|
|
||||||
(Instruction::r#return(true), Span(5, 5)),
|
|
||||||
],
|
|
||||||
vec![Value::integer(1), Value::integer(2)],
|
|
||||||
vec![]
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::integer(-1))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn subtract_assign() {
|
|
||||||
let source = "let mut x = 42; x -= 2; x";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::load_constant(0, 0, false), Span(12, 14)),
|
|
||||||
(Instruction::define_local(0, 0, true), Span(8, 9)),
|
|
||||||
(
|
|
||||||
*Instruction::subtract(0, 0, 2).set_c_is_constant(),
|
|
||||||
Span(18, 20)
|
|
||||||
),
|
|
||||||
(Instruction::get_local(1, 0), Span(24, 25)),
|
|
||||||
(Instruction::r#return(true), Span(25, 25)),
|
|
||||||
],
|
|
||||||
vec![Value::integer(42), Value::string("x"), Value::integer(2)],
|
|
||||||
vec![Local::new(1, None, true, Scope::default(), 0)]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::integer(40))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn subtract_assign_expects_mutable_variable() {
|
|
||||||
let source = "1 -= 2";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Err(DustError::Compile {
|
|
||||||
error: CompileError::ExpectedMutableVariable {
|
|
||||||
found: Token::Integer("1").to_owned(),
|
|
||||||
position: Span(0, 1)
|
|
||||||
},
|
|
||||||
source
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
use dust_lang::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn panic() {
|
|
||||||
let source = "panic(\"Goodbye world!\", 42)";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::load_constant(0, 0, false), Span(6, 22)),
|
|
||||||
(Instruction::load_constant(1, 1, false), Span(24, 26)),
|
|
||||||
(
|
|
||||||
Instruction::call_native(2, NativeFunction::Panic, 2),
|
|
||||||
Span(0, 27)
|
|
||||||
),
|
|
||||||
(Instruction::r#return(true), Span(27, 27))
|
|
||||||
],
|
|
||||||
vec![Value::string("Goodbye world!"), Value::integer(42)],
|
|
||||||
vec![]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
run(source),
|
|
||||||
Err(DustError::Runtime {
|
|
||||||
error: VmError::NativeFunction(NativeFunctionError::Panic {
|
|
||||||
message: Some("Goodbye world! 42".to_string()),
|
|
||||||
position: Span(0, 27)
|
|
||||||
}),
|
|
||||||
source
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn to_string() {
|
|
||||||
let source = "to_string(42)";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::load_constant(0, 0, false), Span(10, 12)),
|
|
||||||
(
|
|
||||||
Instruction::call_native(1, NativeFunction::ToString, 1),
|
|
||||||
Span(0, 13)
|
|
||||||
),
|
|
||||||
(Instruction::r#return(true), Span(13, 13))
|
|
||||||
],
|
|
||||||
vec![Value::integer(42)],
|
|
||||||
vec![]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::string("42"))))
|
|
||||||
}
|
|
@ -1,244 +0,0 @@
|
|||||||
use dust_lang::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn allow_access_to_parent_scope() {
|
|
||||||
let source = r#"
|
|
||||||
let x = 1;
|
|
||||||
{
|
|
||||||
x
|
|
||||||
}
|
|
||||||
"#;
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::integer(1))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn block_scope() {
|
|
||||||
let source = "
|
|
||||||
let a = 0;
|
|
||||||
{
|
|
||||||
let b = 42;
|
|
||||||
{
|
|
||||||
let c = 1;
|
|
||||||
}
|
|
||||||
let d = 2;
|
|
||||||
}
|
|
||||||
let e = 1;
|
|
||||||
";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::load_constant(0, 0, false), Span(17, 18)),
|
|
||||||
(Instruction::define_local(0, 0, false), Span(13, 14)),
|
|
||||||
(Instruction::load_constant(1, 2, false), Span(50, 52)),
|
|
||||||
(Instruction::define_local(1, 1, false), Span(46, 47)),
|
|
||||||
(Instruction::load_constant(2, 4, false), Span(92, 93)),
|
|
||||||
(Instruction::define_local(2, 2, false), Span(88, 89)),
|
|
||||||
(Instruction::load_constant(3, 6, false), Span(129, 130)),
|
|
||||||
(Instruction::define_local(3, 3, false), Span(125, 126)),
|
|
||||||
(Instruction::load_constant(4, 4, false), Span(158, 159)),
|
|
||||||
(Instruction::define_local(4, 4, false), Span(154, 155)),
|
|
||||||
(Instruction::r#return(false), Span(165, 165))
|
|
||||||
],
|
|
||||||
vec![
|
|
||||||
Value::integer(0),
|
|
||||||
Value::string("a"),
|
|
||||||
Value::integer(42),
|
|
||||||
Value::string("b"),
|
|
||||||
Value::integer(1),
|
|
||||||
Value::string("c"),
|
|
||||||
Value::integer(2),
|
|
||||||
Value::string("d"),
|
|
||||||
Value::string("e"),
|
|
||||||
],
|
|
||||||
vec![
|
|
||||||
Local::new(1, None, false, Scope::new(0, 0), 0),
|
|
||||||
Local::new(3, None, false, Scope::new(1, 1), 1),
|
|
||||||
Local::new(5, None, false, Scope::new(2, 2), 2),
|
|
||||||
Local::new(7, None, false, Scope::new(1, 1), 3),
|
|
||||||
Local::new(8, None, false, Scope::new(0, 0), 4),
|
|
||||||
]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(None));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn multiple_block_scopes() {
|
|
||||||
let source = "
|
|
||||||
let a = 0;
|
|
||||||
{
|
|
||||||
let b = 42;
|
|
||||||
{
|
|
||||||
let c = 1;
|
|
||||||
}
|
|
||||||
let d = 2;
|
|
||||||
}
|
|
||||||
let q = 42;
|
|
||||||
{
|
|
||||||
let b = 42;
|
|
||||||
{
|
|
||||||
let c = 1;
|
|
||||||
}
|
|
||||||
let d = 2;
|
|
||||||
}
|
|
||||||
let e = 1;
|
|
||||||
";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::load_constant(0, 0, false), Span(17, 18)),
|
|
||||||
(Instruction::define_local(0, 0, false), Span(13, 14)),
|
|
||||||
(Instruction::load_constant(1, 2, false), Span(50, 52)),
|
|
||||||
(Instruction::define_local(1, 1, false), Span(46, 47)),
|
|
||||||
(Instruction::load_constant(2, 4, false), Span(92, 93)),
|
|
||||||
(Instruction::define_local(2, 2, false), Span(88, 89)),
|
|
||||||
(Instruction::load_constant(3, 6, false), Span(129, 130)),
|
|
||||||
(Instruction::define_local(3, 3, false), Span(125, 126)),
|
|
||||||
(Instruction::load_constant(4, 2, false), Span(158, 160)),
|
|
||||||
(Instruction::define_local(4, 4, false), Span(154, 155)),
|
|
||||||
(Instruction::load_constant(5, 2, false), Span(192, 194)),
|
|
||||||
(Instruction::define_local(5, 5, false), Span(188, 189)),
|
|
||||||
(Instruction::load_constant(6, 4, false), Span(234, 235)),
|
|
||||||
(Instruction::define_local(6, 6, false), Span(230, 231)),
|
|
||||||
(Instruction::load_constant(7, 6, false), Span(271, 272)),
|
|
||||||
(Instruction::define_local(7, 7, false), Span(267, 268)),
|
|
||||||
(Instruction::load_constant(8, 4, false), Span(300, 301)),
|
|
||||||
(Instruction::define_local(8, 8, false), Span(296, 297)),
|
|
||||||
(Instruction::r#return(false), Span(307, 307))
|
|
||||||
],
|
|
||||||
vec![
|
|
||||||
Value::integer(0),
|
|
||||||
Value::string("a"),
|
|
||||||
Value::integer(42),
|
|
||||||
Value::string("b"),
|
|
||||||
Value::integer(1),
|
|
||||||
Value::string("c"),
|
|
||||||
Value::integer(2),
|
|
||||||
Value::string("d"),
|
|
||||||
Value::string("q"),
|
|
||||||
Value::string("e"),
|
|
||||||
],
|
|
||||||
vec![
|
|
||||||
Local::new(1, None, false, Scope::new(0, 0), 0),
|
|
||||||
Local::new(3, None, false, Scope::new(1, 1), 1),
|
|
||||||
Local::new(5, None, false, Scope::new(2, 2), 2),
|
|
||||||
Local::new(7, None, false, Scope::new(1, 1), 3),
|
|
||||||
Local::new(8, None, false, Scope::new(0, 0), 4),
|
|
||||||
Local::new(3, None, false, Scope::new(1, 3), 5),
|
|
||||||
Local::new(5, None, false, Scope::new(2, 4), 6),
|
|
||||||
Local::new(7, None, false, Scope::new(1, 3), 7),
|
|
||||||
Local::new(9, None, false, Scope::new(0, 0), 8),
|
|
||||||
]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(None));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn disallow_access_to_child_scope() {
|
|
||||||
let source = r#"
|
|
||||||
{
|
|
||||||
let x = 1;
|
|
||||||
}
|
|
||||||
x
|
|
||||||
"#;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
run(source),
|
|
||||||
Err(DustError::Compile {
|
|
||||||
error: CompileError::VariableOutOfScope {
|
|
||||||
identifier: "x".to_string(),
|
|
||||||
position: Span(52, 53),
|
|
||||||
variable_scope: Scope::new(1, 1),
|
|
||||||
access_scope: Scope::new(0, 0),
|
|
||||||
},
|
|
||||||
source
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn disallow_access_to_child_scope_nested() {
|
|
||||||
let source = r#"
|
|
||||||
{
|
|
||||||
{
|
|
||||||
let x = 1;
|
|
||||||
}
|
|
||||||
x
|
|
||||||
}
|
|
||||||
"#;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
run(source),
|
|
||||||
Err(DustError::Compile {
|
|
||||||
error: CompileError::VariableOutOfScope {
|
|
||||||
identifier: "x".to_string(),
|
|
||||||
position: Span(78, 79),
|
|
||||||
variable_scope: Scope::new(2, 2),
|
|
||||||
access_scope: Scope::new(1, 1),
|
|
||||||
},
|
|
||||||
source
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn disallow_access_to_sibling_scope() {
|
|
||||||
let source = r#"
|
|
||||||
{
|
|
||||||
let x = 1;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
x
|
|
||||||
}
|
|
||||||
"#;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
run(source),
|
|
||||||
Err(DustError::Compile {
|
|
||||||
error: CompileError::VariableOutOfScope {
|
|
||||||
identifier: "x".to_string(),
|
|
||||||
variable_scope: Scope::new(1, 1),
|
|
||||||
access_scope: Scope::new(1, 2),
|
|
||||||
position: Span(66, 67),
|
|
||||||
},
|
|
||||||
source
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn disallow_access_to_sibling_scope_nested() {
|
|
||||||
let source = r#"
|
|
||||||
{
|
|
||||||
{
|
|
||||||
let x = 1;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
x
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"#;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
run(source),
|
|
||||||
Err(DustError::Compile {
|
|
||||||
error: CompileError::VariableOutOfScope {
|
|
||||||
identifier: "x".to_string(),
|
|
||||||
variable_scope: Scope::new(2, 2),
|
|
||||||
access_scope: Scope::new(2, 3),
|
|
||||||
position: Span(96, 97),
|
|
||||||
},
|
|
||||||
source
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
use dust_lang::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn negate() {
|
|
||||||
let source = "-(42)";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(*Instruction::negate(0, 0).set_b_is_constant(), Span(0, 1)),
|
|
||||||
(Instruction::r#return(true), Span(5, 5)),
|
|
||||||
],
|
|
||||||
vec![Value::integer(42)],
|
|
||||||
vec![]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::integer(-42))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn not() {
|
|
||||||
let source = "!true";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::load_boolean(0, true, false), Span(1, 5)),
|
|
||||||
(Instruction::not(1, 0), Span(0, 1)),
|
|
||||||
(Instruction::r#return(true), Span(5, 5)),
|
|
||||||
],
|
|
||||||
vec![],
|
|
||||||
vec![]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
use dust_lang::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn define_local() {
|
|
||||||
let source = "let x = 42;";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::load_constant(0, 0, false), Span(8, 10)),
|
|
||||||
(Instruction::define_local(0, 0, false), Span(4, 5)),
|
|
||||||
(Instruction::r#return(false), Span(11, 11))
|
|
||||||
],
|
|
||||||
vec![Value::integer(42), Value::string("x")],
|
|
||||||
vec![Local::new(1, None, false, Scope::default(), 0)]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(None));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn let_statement_expects_identifier() {
|
|
||||||
let source = "let 1 = 2";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Err(DustError::Compile {
|
|
||||||
error: CompileError::ExpectedToken {
|
|
||||||
expected: TokenKind::Identifier,
|
|
||||||
found: Token::Integer("1").to_owned(),
|
|
||||||
position: Span(4, 5)
|
|
||||||
},
|
|
||||||
source
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_local() {
|
|
||||||
let source = "let mut x = 41; x = 42; x";
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compile(source),
|
|
||||||
Ok(Chunk::with_data(
|
|
||||||
None,
|
|
||||||
vec![
|
|
||||||
(Instruction::load_constant(0, 0, false), Span(12, 14)),
|
|
||||||
(Instruction::define_local(0, 0, true), Span(8, 9)),
|
|
||||||
(Instruction::load_constant(1, 2, false), Span(20, 22)),
|
|
||||||
(Instruction::set_local(1, 0), Span(16, 17)),
|
|
||||||
(Instruction::get_local(2, 0), Span(24, 25)),
|
|
||||||
(Instruction::r#return(true), Span(25, 25)),
|
|
||||||
],
|
|
||||||
vec![Value::integer(41), Value::string("x"), Value::integer(42)],
|
|
||||||
vec![Local::new(1, None, true, Scope::default(), 0)]
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(Some(Value::integer(42))));
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "dust-shell"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
readme.workspace = true
|
|
||||||
repository.workspace = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
clap = { version = "4.5.14", features = ["derive"] }
|
|
||||||
colored = "2.1.0"
|
|
||||||
dust-lang = { path = "../dust-lang" }
|
|
||||||
env_logger = "0.11.5"
|
|
||||||
log = "0.4.22"
|
|
@ -1,138 +0,0 @@
|
|||||||
use std::{fs::read_to_string, io::Write};
|
|
||||||
|
|
||||||
use clap::Parser;
|
|
||||||
use colored::Colorize;
|
|
||||||
use dust_lang::{compile, format, run};
|
|
||||||
use log::{Level, LevelFilter};
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
|
||||||
struct Cli {
|
|
||||||
/// Source code sent via command line
|
|
||||||
#[arg(short, long)]
|
|
||||||
command: Option<String>,
|
|
||||||
|
|
||||||
/// Whether to output formatted source code
|
|
||||||
#[arg(short, long)]
|
|
||||||
format: bool,
|
|
||||||
|
|
||||||
/// Whether to output line numbers in formatted source code
|
|
||||||
#[arg(long)]
|
|
||||||
format_line_numbers: Option<bool>,
|
|
||||||
|
|
||||||
/// Whether to output colors in formatted source code
|
|
||||||
#[arg(long)]
|
|
||||||
format_colored: Option<bool>,
|
|
||||||
|
|
||||||
/// Whether to output the disassembled chunk
|
|
||||||
#[arg(short, long)]
|
|
||||||
parse: bool,
|
|
||||||
|
|
||||||
/// Whether to style the disassembled chunk
|
|
||||||
#[arg(long)]
|
|
||||||
style_disassembly: Option<bool>,
|
|
||||||
|
|
||||||
/// Log level
|
|
||||||
#[arg(short, long)]
|
|
||||||
log: Option<LevelFilter>,
|
|
||||||
|
|
||||||
/// Path to a source code file
|
|
||||||
path: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let args = Cli::parse();
|
|
||||||
let mut logger = env_logger::builder();
|
|
||||||
|
|
||||||
logger.format(|buf, record| {
|
|
||||||
let level_display = match record.level() {
|
|
||||||
Level::Info => "INFO".bold().white(),
|
|
||||||
Level::Debug => "DEBUG".bold().blue(),
|
|
||||||
Level::Warn => "WARN".bold().yellow(),
|
|
||||||
Level::Error => "ERROR".bold().red(),
|
|
||||||
Level::Trace => "TRACE".bold().purple(),
|
|
||||||
};
|
|
||||||
let module = record
|
|
||||||
.module_path()
|
|
||||||
.map(|path| path.split("::").last().unwrap_or(path))
|
|
||||||
.unwrap_or("unknown")
|
|
||||||
.dimmed();
|
|
||||||
let display = format!("{level_display:5} {module:^6} {args}", args = record.args());
|
|
||||||
|
|
||||||
writeln!(buf, "{display}")
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(level) = args.log {
|
|
||||||
logger.filter_level(level).init();
|
|
||||||
} else {
|
|
||||||
logger.parse_env("DUST_LOG").init();
|
|
||||||
}
|
|
||||||
|
|
||||||
let source = if let Some(path) = &args.path {
|
|
||||||
&read_to_string(path).expect("Failed to read file")
|
|
||||||
} else if let Some(command) = &args.command {
|
|
||||||
command
|
|
||||||
} else {
|
|
||||||
eprintln!("No input provided");
|
|
||||||
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if args.format {
|
|
||||||
let line_numbers = args.format_line_numbers.unwrap_or(true);
|
|
||||||
let colored = args.format_colored.unwrap_or(true);
|
|
||||||
|
|
||||||
log::info!("Formatting source");
|
|
||||||
|
|
||||||
match format(source, line_numbers, colored) {
|
|
||||||
Ok(formatted) => println!("{}", formatted),
|
|
||||||
Err(error) => {
|
|
||||||
eprintln!("{}", error.report());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.parse {
|
|
||||||
let styled = args.style_disassembly.unwrap_or(true);
|
|
||||||
|
|
||||||
log::info!("Parsing source");
|
|
||||||
|
|
||||||
match compile(source) {
|
|
||||||
Ok(chunk) => {
|
|
||||||
let disassembly = chunk
|
|
||||||
.disassembler()
|
|
||||||
.source(source)
|
|
||||||
.styled(styled)
|
|
||||||
.disassemble();
|
|
||||||
|
|
||||||
println!("{}", disassembly);
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
eprintln!("{}", error.report());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.format || args.parse {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
match run(source) {
|
|
||||||
Ok(Some(value)) => println!("{}", value),
|
|
||||||
Ok(None) => {}
|
|
||||||
Err(error) => {
|
|
||||||
eprintln!("{}", error.report());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use clap::CommandFactory;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn verify_cli() {
|
|
||||||
Cli::command().debug_assert();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
var i = 0;
|
|
||||||
|
|
||||||
while (i < 10000) {
|
|
||||||
i++;
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
[
|
|
||||||
{ "name": "Sammy", "type": "shark", "clams": 5 },
|
|
||||||
{ "name": "Bubbles", "type": "orca", "clams": 3 },
|
|
||||||
{ "name": "Splish", "type": "dolphin", "clams": 2 },
|
|
||||||
{ "name": "Splash", "type": "dolphin", "clams": 2 }
|
|
||||||
]
|
|
@ -1,11 +0,0 @@
|
|||||||
function fib(n) {
|
|
||||||
if (n <= 0) {
|
|
||||||
return 0;
|
|
||||||
} else if (n === 1) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return fib(n - 1) + fib(n - 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(fib(25));
|
|
19
examples/async.ds
Normal file
19
examples/async.ds
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
create_random_numbers = (count <int>) <none> {
|
||||||
|
numbers = []
|
||||||
|
|
||||||
|
while length(numbers) < count {
|
||||||
|
numbers += random:integer()
|
||||||
|
}
|
||||||
|
|
||||||
|
output("Made " + length(numbers) + " numbers.")
|
||||||
|
}
|
||||||
|
|
||||||
|
output("This will print first.")
|
||||||
|
|
||||||
|
async {
|
||||||
|
create_random_numbers(1000)
|
||||||
|
create_random_numbers(100)
|
||||||
|
create_random_numbers(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
output("This will print last.")
|
17
examples/async_commands.ds
Normal file
17
examples/async_commands.ds
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
async {
|
||||||
|
{
|
||||||
|
^echo 'Starting 1...'
|
||||||
|
^sleep 1
|
||||||
|
^echo 'Finished 1.'
|
||||||
|
}
|
||||||
|
{
|
||||||
|
^echo 'Starting 2...'
|
||||||
|
^sleep 2
|
||||||
|
^echo 'Finished 2.'
|
||||||
|
}
|
||||||
|
{
|
||||||
|
^echo 'Starting 3...'
|
||||||
|
^sleep 3
|
||||||
|
^echo 'Finished 3.'
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +0,0 @@
|
|||||||
count_slowly = fn (
|
|
||||||
multiplier: int,
|
|
||||||
) {
|
|
||||||
i = 0
|
|
||||||
|
|
||||||
while i < 10 {
|
|
||||||
sleep_time = i * multiplier;
|
|
||||||
|
|
||||||
thread.sleep(sleep_time)
|
|
||||||
thread.write_line(i as str)
|
|
||||||
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async {
|
|
||||||
count_slowly(50)
|
|
||||||
count_slowly(100)
|
|
||||||
count_slowly(200)
|
|
||||||
count_slowly(250)
|
|
||||||
}
|
|
20
examples/async_download.ds
Normal file
20
examples/async_download.ds
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
cast_len = 0
|
||||||
|
characters_len = 0
|
||||||
|
episodes_len = 0
|
||||||
|
|
||||||
|
async {
|
||||||
|
{
|
||||||
|
cast = download("https://api.sampleapis.com/futurama/cast")
|
||||||
|
cast_len = length(from_json(cast))
|
||||||
|
}
|
||||||
|
{
|
||||||
|
characters = download("https://api.sampleapis.com/futurama/characters")
|
||||||
|
characters_len = length(from_json(characters))
|
||||||
|
}
|
||||||
|
{
|
||||||
|
episodes = download("https://api.sampleapis.com/futurama/episodes")
|
||||||
|
episodes_len = length(from_json(episodes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output ([cast_len, characters_len, episodes_len])
|
53
examples/clue_solver.ds
Normal file
53
examples/clue_solver.ds
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
cards = {
|
||||||
|
rooms = ['Library' 'Kitchen' 'Conservatory']
|
||||||
|
suspects = ['White' 'Green' 'Scarlett']
|
||||||
|
weapons = ['Rope' 'Lead_Pipe' 'Knife']
|
||||||
|
}
|
||||||
|
|
||||||
|
is_ready_to_solve = (cards <map>) <bool> {
|
||||||
|
(length(cards:suspects) == 1)
|
||||||
|
&& (length(cards:rooms) == 1)
|
||||||
|
&& (length(cards:weapons) == 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_card = (cards <map>, opponent_card <str>) <none> {
|
||||||
|
cards:rooms -= opponent_card
|
||||||
|
cards:suspects -= opponent_card
|
||||||
|
cards:weapons -= opponent_card
|
||||||
|
}
|
||||||
|
|
||||||
|
make_guess = (cards <map>, current_room <str>) <none> {
|
||||||
|
if is_ready_to_solve(cards) {
|
||||||
|
output(
|
||||||
|
'I accuse '
|
||||||
|
+ cards:suspects:0
|
||||||
|
+ ' in the '
|
||||||
|
+ cards:rooms:0
|
||||||
|
+ ' with the '
|
||||||
|
+ cards:weapons:0
|
||||||
|
+ '!'
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
output(
|
||||||
|
'I question '
|
||||||
|
+ random:from(cards:suspects)
|
||||||
|
+ ' in the '
|
||||||
|
+ current_room
|
||||||
|
+ ' with the '
|
||||||
|
+ random:from(cards:weapons)
|
||||||
|
+ '.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
take_turn = (cards <map>, opponent_card <str>, current_room <str>) <none> {
|
||||||
|
remove_card(cards opponent_card)
|
||||||
|
make_guess(cards current_room)
|
||||||
|
}
|
||||||
|
|
||||||
|
take_turn(cards 'Rope' 'Kitchen')
|
||||||
|
take_turn(cards 'Library' 'Kitchen')
|
||||||
|
take_turn(cards 'Conservatory' 'Kitchen')
|
||||||
|
take_turn(cards 'White' 'Kitchen')
|
||||||
|
take_turn(cards 'Green' 'Kitchen')
|
||||||
|
take_turn(cards 'Knife' 'Kitchen')
|
@ -1,5 +0,0 @@
|
|||||||
let mut i = 0;
|
|
||||||
|
|
||||||
while i < 10000 {
|
|
||||||
i += 1;
|
|
||||||
}
|
|
10
examples/download.ds
Normal file
10
examples/download.ds
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
raw_data = download("https://api.sampleapis.com/futurama/cast")
|
||||||
|
cast_data = from_json(raw_data)
|
||||||
|
|
||||||
|
names = []
|
||||||
|
|
||||||
|
for cast_member in cast_data {
|
||||||
|
names += cast_member:name
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal("Billy West", names:0)
|
@ -1,8 +1,9 @@
|
|||||||
fn fib (n: int) -> int {
|
fib = (i <int>) <int> {
|
||||||
if n <= 0 { return 0 }
|
if i <= 1 {
|
||||||
if n == 1 { return 1 }
|
1
|
||||||
|
} else {
|
||||||
fib(n - 1) + fib(n - 2)
|
fib(i - 1) + fib(i - 2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
write_line(fib(25))
|
fib(8)
|
||||||
|
@ -1,20 +1,18 @@
|
|||||||
let mut count = 1
|
count = 1
|
||||||
|
|
||||||
while count <= 15 {
|
while count <= 15 {
|
||||||
let divides_by_3 = count % 3 == 0
|
divides_by_3 = count % 3 == 0
|
||||||
let divides_by_5 = count % 5 == 0
|
divides_by_5 = count % 5 == 0
|
||||||
|
|
||||||
let output = if divides_by_3 && divides_by_5 {
|
if divides_by_3 && divides_by_5 {
|
||||||
"fizzbuzz"
|
output('fizzbuzz')
|
||||||
} else if divides_by_3 {
|
} else if divides_by_3 {
|
||||||
"fizz"
|
output('fizz')
|
||||||
} else if divides_by_5 {
|
} else if divides_by_5 {
|
||||||
"buzz"
|
output('buzz')
|
||||||
} else {
|
} else {
|
||||||
to_string(count)
|
output(count)
|
||||||
}
|
}
|
||||||
|
|
||||||
write_line(output)
|
|
||||||
|
|
||||||
count += 1
|
count += 1
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,26 @@
|
|||||||
write_line("Guess the number.")
|
# This is a Dust version of an example from the Rust Book.
|
||||||
|
#
|
||||||
|
# https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html
|
||||||
|
|
||||||
let secret_number = random(0..100);
|
output("Guess the number.")
|
||||||
|
|
||||||
|
secret_number = int:random_range(0..=100);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
write_line("Input your guess.")
|
output("Please input your guess.")
|
||||||
|
|
||||||
let input = io.read_line();
|
input = io:stdin():expect("Failed to read line.")
|
||||||
let guess = int.parse(input);
|
guess = int:parse(input);
|
||||||
|
|
||||||
if guess < secret_number {
|
output("You guessed: " + guess)
|
||||||
io.write_line("Too low!")
|
|
||||||
} else if guess > secret_number {
|
match cmp(guess, secret_number) {
|
||||||
io.write_line("Too high!")
|
Ordering::Less -> output("Too small!"),
|
||||||
} else {
|
Ordering::Greater -> output("Too big!"),
|
||||||
io.write_line("You win!")
|
Ordering::Equal -> {
|
||||||
break
|
output("You win!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1 @@
|
|||||||
write_line("Hello, world!")
|
output('Hello, world!')
|
||||||
write_line("Enter your name...")
|
|
||||||
|
|
||||||
let name = read_line()
|
|
||||||
|
|
||||||
write_line("Hello " + name + "!")
|
|
||||||
|
12
examples/jq_data.ds
Normal file
12
examples/jq_data.ds
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
data = json:parse(fs:read_file('examples/assets/jq_data.json'))
|
||||||
|
|
||||||
|
new_data = []
|
||||||
|
|
||||||
|
for commit_data in data as collection {
|
||||||
|
new_data += {
|
||||||
|
message = commit_data:commit:message
|
||||||
|
name = commit_data:commit:committer:name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new_data
|
@ -1,4 +0,0 @@
|
|||||||
input = fs.read_file('examples/assets/data.json')
|
|
||||||
data = json.parse(input)
|
|
||||||
|
|
||||||
length(data)
|
|
13
examples/random.ds
Normal file
13
examples/random.ds
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
stuff = [
|
||||||
|
random:integer()
|
||||||
|
random:integer()
|
||||||
|
random:integer()
|
||||||
|
random:float()
|
||||||
|
random:float()
|
||||||
|
random:float()
|
||||||
|
random:boolean()
|
||||||
|
random:boolean()
|
||||||
|
random:boolean()
|
||||||
|
]
|
||||||
|
|
||||||
|
random:from(stuff)
|
19
examples/sea_creatures.ds
Normal file
19
examples/sea_creatures.ds
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
raw_data = fs:read_file('examples/assets/seaCreatures.json')
|
||||||
|
sea_creatures = json:parse(raw_data)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
creatures = []
|
||||||
|
total_clams = 0
|
||||||
|
dolphin_clams = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
for creature in sea_creatures {
|
||||||
|
data:creatures += creature:name
|
||||||
|
data:total_clams += creature:clams
|
||||||
|
|
||||||
|
if creature:type == 'dolphin' {
|
||||||
|
data:dolphin_clams += creature:clams
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data
|
@ -1,20 +0,0 @@
|
|||||||
// This function returns its argument.
|
|
||||||
foo = fn <T>(x: T) -> T { x }
|
|
||||||
|
|
||||||
// Use turbofish to supply type information.
|
|
||||||
bar = foo::<str>("hi")
|
|
||||||
|
|
||||||
// Use type annotation
|
|
||||||
baz: str = foo("hi")
|
|
||||||
|
|
||||||
// The `json.parse` function takes a string and returns the specified type
|
|
||||||
|
|
||||||
// Use turbofish
|
|
||||||
x = json.parse::<int>("1")
|
|
||||||
|
|
||||||
// Use type annotation
|
|
||||||
x: int = json.parse("1")
|
|
||||||
|
|
||||||
x: int = {
|
|
||||||
json.parse("1")
|
|
||||||
}
|
|
35
scripts/bench.fish
Executable file
35
scripts/bench.fish
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/fish
|
||||||
|
# This script is has the following prerequisites (aside from fish):
|
||||||
|
# - hyperfine
|
||||||
|
# - dust (can be installed with "cargo install dust-lang")
|
||||||
|
# - jq
|
||||||
|
# - nodejs
|
||||||
|
# - nushell
|
||||||
|
# - dielectron.json (can be downloaded from https://opendata.cern.ch/record/304)
|
||||||
|
|
||||||
|
hyperfine \
|
||||||
|
--shell none \
|
||||||
|
--parameter-list data_path examples/assets/seaCreatures.json \
|
||||||
|
--warmup 3 \
|
||||||
|
"dust -c 'length(json:parse(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'"
|
||||||
|
|
||||||
|
hyperfine \
|
||||||
|
--shell none \
|
||||||
|
--parameter-list data_path examples/assets/jq_data.json \
|
||||||
|
--warmup 3 \
|
||||||
|
"dust -c 'length(json:parse(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'"
|
||||||
|
|
||||||
|
hyperfine \
|
||||||
|
--shell none \
|
||||||
|
--parameter-list data_path dielectron.json \
|
||||||
|
--warmup 3 \
|
||||||
|
"dust -c 'length(json:parse(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'"
|
8
scripts/build_debug.fish
Executable file
8
scripts/build_debug.fish
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/fish
|
||||||
|
# Build the project in debug mode.
|
||||||
|
|
||||||
|
cd tree-sitter-dust/
|
||||||
|
tree-sitter generate --debug-build --no-bindings
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
cargo build
|
8
scripts/build_release.fish
Executable file
8
scripts/build_release.fish
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/fish
|
||||||
|
# Build the project in release mode.
|
||||||
|
|
||||||
|
cd tree-sitter-dust/
|
||||||
|
tree-sitter generate --no-bindings
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
cargo build --release
|
9
scripts/test.fish
Executable file
9
scripts/test.fish
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/fish
|
||||||
|
# Build the project in debug mode.
|
||||||
|
|
||||||
|
cd tree-sitter-dust/
|
||||||
|
tree-sitter generate --debug-build --no-bindings
|
||||||
|
tree-sitter test
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
cargo test
|
131
src/abstract_tree/as.rs
Normal file
131
src/abstract_tree/as.rs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tree_sitter::Node;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Context, Expression, Format, List, SourcePosition, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct As {
|
||||||
|
expression: Expression,
|
||||||
|
r#type: Type,
|
||||||
|
position: SourcePosition,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for As {
|
||||||
|
fn from_syntax(node: Node, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("as", node)?;
|
||||||
|
|
||||||
|
let expression_node = node.child(0).unwrap();
|
||||||
|
let expression = Expression::from_syntax(expression_node, source, context)?;
|
||||||
|
|
||||||
|
let type_node = node.child(2).unwrap();
|
||||||
|
let r#type = Type::from_syntax(type_node, source, context)?;
|
||||||
|
|
||||||
|
Ok(As {
|
||||||
|
expression,
|
||||||
|
r#type,
|
||||||
|
position: SourcePosition::from(node.range()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
Ok(self.r#type.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
|
||||||
|
let initial_type = self.expression.expected_type(context)?;
|
||||||
|
|
||||||
|
if self.r#type.accepts(&initial_type) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Type::ListOf(item_type) = &self.r#type {
|
||||||
|
match &initial_type {
|
||||||
|
Type::ListOf(expected_item_type) => {
|
||||||
|
println!("{item_type} {expected_item_type}");
|
||||||
|
|
||||||
|
if !item_type.accepts(&expected_item_type) {
|
||||||
|
return Err(ValidationError::TypeCheck {
|
||||||
|
expected: self.r#type.clone(),
|
||||||
|
actual: initial_type.clone(),
|
||||||
|
position: self.position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Type::String => {
|
||||||
|
if let Type::String = item_type.as_ref() {
|
||||||
|
} else {
|
||||||
|
return Err(ValidationError::ConversionImpossible {
|
||||||
|
initial_type,
|
||||||
|
target_type: self.r#type.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Type::Any => {
|
||||||
|
// Do no validation when converting from "any" to a list.
|
||||||
|
// This effectively defers to runtime behavior, potentially
|
||||||
|
// causing a runtime error.
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(ValidationError::ConversionImpossible {
|
||||||
|
initial_type,
|
||||||
|
target_type: self.r#type.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
let value = self.expression.run(source, context)?;
|
||||||
|
let converted_value = if self.r#type.accepts(&value.r#type()?) {
|
||||||
|
return Ok(value);
|
||||||
|
} else if let Type::ListOf(_) = self.r#type {
|
||||||
|
match value {
|
||||||
|
Value::List(list) => Value::List(list),
|
||||||
|
Value::String(string) => {
|
||||||
|
let chars = string
|
||||||
|
.chars()
|
||||||
|
.map(|char| Value::String(char.to_string()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Value::List(List::with_items(chars))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(RuntimeError::ConversionImpossible {
|
||||||
|
from: value.r#type()?,
|
||||||
|
to: self.r#type.clone(),
|
||||||
|
position: self.position.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Type::Integer = self.r#type {
|
||||||
|
match value {
|
||||||
|
Value::Integer(integer) => Value::Integer(integer),
|
||||||
|
Value::Float(float) => Value::Integer(float as i64),
|
||||||
|
_ => {
|
||||||
|
return Err(RuntimeError::ConversionImpossible {
|
||||||
|
from: value.r#type()?,
|
||||||
|
to: self.r#type.clone(),
|
||||||
|
position: self.position.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
todo!()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(converted_value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for As {
|
||||||
|
fn format(&self, _output: &mut String, _indent_level: u8) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
186
src/abstract_tree/assignment.rs
Normal file
186
src/abstract_tree/assignment.rs
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
context::Context,
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, AssignmentOperator, Format, Function, Identifier, SourcePosition, Statement,
|
||||||
|
SyntaxNode, Type, TypeSpecification, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Variable assignment, including add-assign and subtract-assign operations.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct Assignment {
|
||||||
|
identifier: Identifier,
|
||||||
|
type_specification: Option<TypeSpecification>,
|
||||||
|
operator: AssignmentOperator,
|
||||||
|
statement: Statement,
|
||||||
|
syntax_position: SourcePosition,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for Assignment {
|
||||||
|
fn from_syntax(
|
||||||
|
syntax_node: SyntaxNode,
|
||||||
|
source: &str,
|
||||||
|
context: &Context,
|
||||||
|
) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("assignment", syntax_node)?;
|
||||||
|
|
||||||
|
let child_count = syntax_node.child_count();
|
||||||
|
|
||||||
|
let identifier_node = syntax_node.child(0).unwrap();
|
||||||
|
let identifier = Identifier::from_syntax(identifier_node, source, context)?;
|
||||||
|
|
||||||
|
let type_node = syntax_node.child(1).unwrap();
|
||||||
|
let type_specification = if type_node.kind() == "type_specification" {
|
||||||
|
Some(TypeSpecification::from_syntax(type_node, source, context)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let operator_node = syntax_node.child(child_count - 2).unwrap();
|
||||||
|
let operator = AssignmentOperator::from_syntax(operator_node, source, context)?;
|
||||||
|
|
||||||
|
let statement_node = syntax_node.child(child_count - 1).unwrap();
|
||||||
|
let statement = Statement::from_syntax(statement_node, source, context)?;
|
||||||
|
|
||||||
|
Ok(Assignment {
|
||||||
|
identifier,
|
||||||
|
type_specification,
|
||||||
|
operator,
|
||||||
|
statement,
|
||||||
|
syntax_position: syntax_node.range().into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, source: &str, context: &Context) -> Result<(), ValidationError> {
|
||||||
|
if let AssignmentOperator::Equal = self.operator {
|
||||||
|
let r#type = if let Some(definition) = &self.type_specification {
|
||||||
|
definition.inner().clone()
|
||||||
|
} else {
|
||||||
|
self.statement.expected_type(context)?
|
||||||
|
};
|
||||||
|
|
||||||
|
log::info!("Setting type: {} <{}>", self.identifier, r#type);
|
||||||
|
|
||||||
|
context.set_type(self.identifier.clone(), r#type)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(type_specification) = &self.type_specification {
|
||||||
|
match self.operator {
|
||||||
|
AssignmentOperator::Equal => {
|
||||||
|
let expected = type_specification.inner();
|
||||||
|
let actual = self.statement.expected_type(context)?;
|
||||||
|
|
||||||
|
if !expected.accepts(&actual) {
|
||||||
|
return Err(ValidationError::TypeCheck {
|
||||||
|
expected: expected.clone(),
|
||||||
|
actual,
|
||||||
|
position: self.syntax_position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AssignmentOperator::PlusEqual => {
|
||||||
|
if let Type::ListOf(expected) = type_specification.inner() {
|
||||||
|
let actual = self.identifier.expected_type(context)?;
|
||||||
|
|
||||||
|
if !expected.accepts(&actual) {
|
||||||
|
return Err(ValidationError::TypeCheck {
|
||||||
|
expected: expected.as_ref().clone(),
|
||||||
|
actual,
|
||||||
|
position: self.syntax_position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let expected = type_specification.inner();
|
||||||
|
let actual = self.identifier.expected_type(context)?;
|
||||||
|
|
||||||
|
if !expected.accepts(&actual) {
|
||||||
|
return Err(ValidationError::TypeCheck {
|
||||||
|
expected: expected.clone(),
|
||||||
|
actual,
|
||||||
|
position: self.syntax_position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AssignmentOperator::MinusEqual => todo!(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match self.operator {
|
||||||
|
AssignmentOperator::Equal => {}
|
||||||
|
AssignmentOperator::PlusEqual => {
|
||||||
|
if let Type::ListOf(expected) = self.identifier.expected_type(context)? {
|
||||||
|
let actual = self.statement.expected_type(context)?;
|
||||||
|
|
||||||
|
if !expected.accepts(&actual) {
|
||||||
|
return Err(ValidationError::TypeCheck {
|
||||||
|
expected: expected.as_ref().clone(),
|
||||||
|
actual,
|
||||||
|
position: self.syntax_position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AssignmentOperator::MinusEqual => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.statement.validate(source, context)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
let right = self.statement.run(source, context)?;
|
||||||
|
|
||||||
|
let new_value = match self.operator {
|
||||||
|
AssignmentOperator::PlusEqual => {
|
||||||
|
let left = self.identifier.run(source, context)?;
|
||||||
|
|
||||||
|
left.add(right, self.syntax_position)?
|
||||||
|
}
|
||||||
|
AssignmentOperator::MinusEqual => {
|
||||||
|
if let Some(left) = context.get_value(&self.identifier)? {
|
||||||
|
left.subtract(right, self.syntax_position)?
|
||||||
|
} else {
|
||||||
|
return Err(RuntimeError::ValidationFailure(
|
||||||
|
ValidationError::VariableIdentifierNotFound(self.identifier.clone()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AssignmentOperator::Equal => right,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Value::Function(Function::ContextDefined(function_node)) = &new_value {
|
||||||
|
function_node
|
||||||
|
.context()
|
||||||
|
.set_value(self.identifier.clone(), new_value.clone())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("RUN assignment: {} = {}", self.identifier, new_value);
|
||||||
|
|
||||||
|
context.set_value(self.identifier.clone(), new_value)?;
|
||||||
|
|
||||||
|
Ok(Value::none())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
Ok(Type::None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for Assignment {
|
||||||
|
fn format(&self, output: &mut String, indent_level: u8) {
|
||||||
|
self.identifier.format(output, indent_level);
|
||||||
|
|
||||||
|
if let Some(type_specification) = &self.type_specification {
|
||||||
|
type_specification.format(output, indent_level);
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push(' ');
|
||||||
|
self.operator.format(output, indent_level);
|
||||||
|
output.push(' ');
|
||||||
|
|
||||||
|
self.statement.format(output, 0);
|
||||||
|
}
|
||||||
|
}
|
62
src/abstract_tree/assignment_operator.rs
Normal file
62
src/abstract_tree/assignment_operator.rs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Context, Format, SyntaxNode, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Operators that be used in an assignment statement.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub enum AssignmentOperator {
|
||||||
|
Equal,
|
||||||
|
PlusEqual,
|
||||||
|
MinusEqual,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for AssignmentOperator {
|
||||||
|
fn from_syntax(
|
||||||
|
node: SyntaxNode,
|
||||||
|
_source: &str,
|
||||||
|
_context: &Context,
|
||||||
|
) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("assignment_operator", node)?;
|
||||||
|
|
||||||
|
let operator_node = node.child(0).unwrap();
|
||||||
|
let operator = match operator_node.kind() {
|
||||||
|
"=" => AssignmentOperator::Equal,
|
||||||
|
"+=" => AssignmentOperator::PlusEqual,
|
||||||
|
"-=" => AssignmentOperator::MinusEqual,
|
||||||
|
_ => {
|
||||||
|
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||||
|
expected: "=, += or -=".to_string(),
|
||||||
|
actual: operator_node.kind().to_string(),
|
||||||
|
position: node.range().into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(operator)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
Ok(Value::none())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
Ok(Type::None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for AssignmentOperator {
|
||||||
|
fn format(&self, output: &mut String, _indent_level: u8) {
|
||||||
|
match self {
|
||||||
|
AssignmentOperator::Equal => output.push('='),
|
||||||
|
AssignmentOperator::PlusEqual => output.push_str("+="),
|
||||||
|
AssignmentOperator::MinusEqual => output.push_str("-="),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
170
src/abstract_tree/block.rs
Normal file
170
src/abstract_tree/block.rs
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
use std::{
|
||||||
|
fmt::{self, Formatter},
|
||||||
|
sync::RwLock,
|
||||||
|
};
|
||||||
|
|
||||||
|
use rayon::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{rw_lock_error::RwLockError, RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Context, Format, Statement, SyntaxNode, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Abstract representation of a block.
|
||||||
|
///
|
||||||
|
/// A block is almost identical to the root except that it must have curly
|
||||||
|
/// braces and can optionally be asynchronous. A block evaluates to the value of
|
||||||
|
/// its final statement but an async block will short-circuit if a statement
|
||||||
|
/// results in an error. Note that this will be the first statement to encounter
|
||||||
|
/// an error at runtime, not necessarilly the first statement as they are
|
||||||
|
/// written.
|
||||||
|
#[derive(Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct Block {
|
||||||
|
is_async: bool,
|
||||||
|
contains_return: bool,
|
||||||
|
statements: Vec<Statement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Block {
|
||||||
|
pub fn contains_return(&self) -> bool {
|
||||||
|
self.contains_return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for Block {
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("block", node)?;
|
||||||
|
|
||||||
|
let first_child = node.child(0).unwrap();
|
||||||
|
let is_async = first_child.kind() == "async";
|
||||||
|
let mut contains_return = false;
|
||||||
|
|
||||||
|
let statement_count = if is_async {
|
||||||
|
node.child_count() - 3
|
||||||
|
} else {
|
||||||
|
node.child_count() - 2
|
||||||
|
};
|
||||||
|
let mut statements = Vec::with_capacity(statement_count);
|
||||||
|
let block_context = Context::with_variables_from(context)?;
|
||||||
|
|
||||||
|
for index in 1..node.child_count() - 1 {
|
||||||
|
let child_node = node.child(index).unwrap();
|
||||||
|
|
||||||
|
if child_node.kind() == "statement" {
|
||||||
|
let statement = Statement::from_syntax(child_node, source, &block_context)?;
|
||||||
|
|
||||||
|
if statement.is_return() {
|
||||||
|
contains_return = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
statements.push(statement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Block {
|
||||||
|
is_async,
|
||||||
|
contains_return,
|
||||||
|
statements,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||||
|
for statement in &self.statements {
|
||||||
|
statement.validate(_source, _context)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
if self.is_async {
|
||||||
|
let statements = &self.statements;
|
||||||
|
let final_result = RwLock::new(Ok(Value::none()));
|
||||||
|
|
||||||
|
statements
|
||||||
|
.into_par_iter()
|
||||||
|
.enumerate()
|
||||||
|
.find_map_first(|(index, statement)| {
|
||||||
|
let result = statement.run(_source, _context);
|
||||||
|
let should_return = if self.contains_return {
|
||||||
|
statement.is_return()
|
||||||
|
} else {
|
||||||
|
index == statements.len() - 1
|
||||||
|
};
|
||||||
|
|
||||||
|
if should_return {
|
||||||
|
let get_write_lock = final_result.write();
|
||||||
|
|
||||||
|
match get_write_lock {
|
||||||
|
Ok(mut final_result) => {
|
||||||
|
*final_result = result;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Err(_error) => Some(Err(RuntimeError::RwLock(RwLockError))),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(final_result.into_inner().map_err(|_| RwLockError)?)
|
||||||
|
} else {
|
||||||
|
for (index, statement) in self.statements.iter().enumerate() {
|
||||||
|
if statement.is_return() {
|
||||||
|
return statement.run(_source, _context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if index == self.statements.len() - 1 {
|
||||||
|
return statement.run(_source, _context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::none())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
for (index, statement) in self.statements.iter().enumerate() {
|
||||||
|
if statement.is_return() {
|
||||||
|
return statement.expected_type(_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if index == self.statements.len() - 1 {
|
||||||
|
return statement.expected_type(_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Type::None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for Block {
|
||||||
|
fn format(&self, output: &mut String, indent_level: u8) {
|
||||||
|
if self.is_async {
|
||||||
|
output.push_str("async {\n");
|
||||||
|
} else {
|
||||||
|
output.push_str("{\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (index, statement) in self.statements.iter().enumerate() {
|
||||||
|
if index > 0 {
|
||||||
|
output.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
statement.format(output, indent_level + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push('\n');
|
||||||
|
Block::indent(output, indent_level);
|
||||||
|
output.push('}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Block {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("Block")
|
||||||
|
.field("is_async", &self.is_async)
|
||||||
|
.field("statements", &self.statements)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
76
src/abstract_tree/command.rs
Normal file
76
src/abstract_tree/command.rs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
use std::process::{self, Stdio};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tree_sitter::Node as SyntaxNode;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Context, Format, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An external program invokation.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct Command {
|
||||||
|
command_text: String,
|
||||||
|
command_arguments: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for Command {
|
||||||
|
fn from_syntax(
|
||||||
|
node: SyntaxNode,
|
||||||
|
source: &str,
|
||||||
|
_context: &Context,
|
||||||
|
) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("command", node)?;
|
||||||
|
|
||||||
|
let command_text_node = node.child(1).unwrap();
|
||||||
|
let command_text = source[command_text_node.byte_range()].to_string();
|
||||||
|
|
||||||
|
let mut command_arguments = Vec::new();
|
||||||
|
|
||||||
|
for index in 2..node.child_count() {
|
||||||
|
let text_node = node.child(index).unwrap();
|
||||||
|
let mut text = source[text_node.byte_range()].to_string();
|
||||||
|
|
||||||
|
if (text.starts_with('\'') && text.ends_with('\''))
|
||||||
|
|| (text.starts_with('"') && text.ends_with('"'))
|
||||||
|
|| (text.starts_with('`') && text.ends_with('`'))
|
||||||
|
{
|
||||||
|
text = text[1..text.len() - 1].to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
command_arguments.push(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Command {
|
||||||
|
command_text,
|
||||||
|
command_arguments,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
Ok(Type::String)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
let output = process::Command::new(&self.command_text)
|
||||||
|
.args(&self.command_arguments)
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.spawn()?
|
||||||
|
.wait_with_output()?
|
||||||
|
.stdout;
|
||||||
|
|
||||||
|
Ok(Value::String(String::from_utf8(output)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for Command {
|
||||||
|
fn format(&self, _output: &mut String, _indent_level: u8) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
92
src/abstract_tree/enum_defintion.rs
Normal file
92
src/abstract_tree/enum_defintion.rs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tree_sitter::Node as SyntaxNode;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Context, EnumInstance, Format, Identifier, Type, TypeDefinition, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct EnumDefinition {
|
||||||
|
identifier: Identifier,
|
||||||
|
variants: Vec<(Identifier, Vec<Type>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EnumDefinition {
|
||||||
|
pub fn new(identifier: Identifier, variants: Vec<(Identifier, Vec<Type>)>) -> Self {
|
||||||
|
Self {
|
||||||
|
identifier,
|
||||||
|
variants,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn instantiate(&self, variant: Identifier, content: Option<Value>) -> EnumInstance {
|
||||||
|
EnumInstance::new(self.identifier.clone(), variant, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn identifier(&self) -> &Identifier {
|
||||||
|
&self.identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn variants(&self) -> &Vec<(Identifier, Vec<Type>)> {
|
||||||
|
&self.variants
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for EnumDefinition {
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("enum_definition", node)?;
|
||||||
|
|
||||||
|
let identifier_node = node.child(1).unwrap();
|
||||||
|
let identifier = Identifier::from_syntax(identifier_node, source, context)?;
|
||||||
|
|
||||||
|
let mut variants = Vec::new();
|
||||||
|
let mut current_identifier: Option<Identifier> = None;
|
||||||
|
let mut types = Vec::new();
|
||||||
|
|
||||||
|
for index in 3..node.child_count() - 1 {
|
||||||
|
let child = node.child(index).unwrap();
|
||||||
|
|
||||||
|
if child.kind() == "identifier" {
|
||||||
|
if let Some(identifier) = ¤t_identifier {
|
||||||
|
variants.push((identifier.clone(), types));
|
||||||
|
}
|
||||||
|
|
||||||
|
current_identifier = Some(Identifier::from_syntax(child, source, context)?);
|
||||||
|
types = Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
if child.kind() == "type" {
|
||||||
|
let r#type = Type::from_syntax(child, source, context)?;
|
||||||
|
|
||||||
|
types.push(r#type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(EnumDefinition {
|
||||||
|
identifier,
|
||||||
|
variants,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
Ok(Type::None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
|
||||||
|
context.set_definition(self.identifier.clone(), TypeDefinition::Enum(self.clone()))?;
|
||||||
|
self.identifier.validate(_source, context)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
Ok(Value::none())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for EnumDefinition {
|
||||||
|
fn format(&self, _output: &mut String, _indent_level: u8) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
70
src/abstract_tree/enum_pattern.rs
Normal file
70
src/abstract_tree/enum_pattern.rs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tree_sitter::Node as SyntaxNode;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Context, Format, Identifier, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct EnumPattern {
|
||||||
|
name: Identifier,
|
||||||
|
variant: Identifier,
|
||||||
|
inner_identifier: Option<Identifier>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EnumPattern {
|
||||||
|
pub fn name(&self) -> &Identifier {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn variant(&self) -> &Identifier {
|
||||||
|
&self.variant
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner_identifier(&self) -> &Option<Identifier> {
|
||||||
|
&self.inner_identifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for EnumPattern {
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("enum_pattern", node)?;
|
||||||
|
|
||||||
|
let enum_name_node = node.child(0).unwrap();
|
||||||
|
let name = Identifier::from_syntax(enum_name_node, source, context)?;
|
||||||
|
|
||||||
|
let enum_variant_node = node.child(2).unwrap();
|
||||||
|
let variant = Identifier::from_syntax(enum_variant_node, source, context)?;
|
||||||
|
|
||||||
|
let inner_identifier = if let Some(child) = node.child(4) {
|
||||||
|
Some(Identifier::from_syntax(child, source, context)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(EnumPattern {
|
||||||
|
name,
|
||||||
|
variant,
|
||||||
|
inner_identifier,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
Ok(Type::None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
Ok(Value::none())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for EnumPattern {
|
||||||
|
fn format(&self, _output: &mut String, _indent_level: u8) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
120
src/abstract_tree/expression.rs
Normal file
120
src/abstract_tree/expression.rs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
value_node::ValueNode,
|
||||||
|
AbstractTree, As, Command, Context, Format, FunctionCall, Identifier, Index, Logic, Math,
|
||||||
|
SyntaxNode, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Abstract representation of an expression statement.
|
||||||
|
///
|
||||||
|
/// Unlike statements, which can involve complex logic, an expression is
|
||||||
|
/// expected to evaluate to a value. However, an expression can still contain
|
||||||
|
/// nested statements and may evaluate to an empty value.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub enum Expression {
|
||||||
|
Value(ValueNode),
|
||||||
|
Identifier(Identifier),
|
||||||
|
Index(Box<Index>),
|
||||||
|
Math(Box<Math>),
|
||||||
|
Logic(Box<Logic>),
|
||||||
|
FunctionCall(Box<FunctionCall>),
|
||||||
|
Command(Command),
|
||||||
|
As(Box<As>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for Expression {
|
||||||
|
fn from_syntax(
|
||||||
|
node: SyntaxNode,
|
||||||
|
source: &str,
|
||||||
|
_context: &Context,
|
||||||
|
) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("expression", node)?;
|
||||||
|
|
||||||
|
let child = if node.child(0).unwrap().is_named() {
|
||||||
|
node.child(0).unwrap()
|
||||||
|
} else {
|
||||||
|
node.child(1).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let expression = match child.kind() {
|
||||||
|
"as" => Expression::As(Box::new(As::from_syntax(child, source, _context)?)),
|
||||||
|
"value" => Expression::Value(ValueNode::from_syntax(child, source, _context)?),
|
||||||
|
"identifier" => {
|
||||||
|
Expression::Identifier(Identifier::from_syntax(child, source, _context)?)
|
||||||
|
}
|
||||||
|
"index" => Expression::Index(Box::new(Index::from_syntax(child, source, _context)?)),
|
||||||
|
"math" => Expression::Math(Box::new(Math::from_syntax(child, source, _context)?)),
|
||||||
|
"logic" => Expression::Logic(Box::new(Logic::from_syntax(child, source, _context)?)),
|
||||||
|
"function_call" => Expression::FunctionCall(Box::new(FunctionCall::from_syntax(
|
||||||
|
child, source, _context,
|
||||||
|
)?)),
|
||||||
|
"command" => Expression::Command(Command::from_syntax(child, source, _context)?),
|
||||||
|
_ => {
|
||||||
|
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||||
|
expected: "value, identifier, index, math, logic, function call, as or command"
|
||||||
|
.to_string(),
|
||||||
|
actual: child.kind().to_string(),
|
||||||
|
position: node.range().into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(expression)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
match self {
|
||||||
|
Expression::Value(value_node) => value_node.expected_type(_context),
|
||||||
|
Expression::Identifier(identifier) => identifier.expected_type(_context),
|
||||||
|
Expression::Math(math) => math.expected_type(_context),
|
||||||
|
Expression::Logic(logic) => logic.expected_type(_context),
|
||||||
|
Expression::FunctionCall(function_call) => function_call.expected_type(_context),
|
||||||
|
Expression::Index(index) => index.expected_type(_context),
|
||||||
|
Expression::Command(command) => command.expected_type(_context),
|
||||||
|
Expression::As(r#as) => r#as.expected_type(_context),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||||
|
match self {
|
||||||
|
Expression::Value(value_node) => value_node.validate(_source, _context),
|
||||||
|
Expression::Identifier(identifier) => identifier.validate(_source, _context),
|
||||||
|
Expression::Math(math) => math.validate(_source, _context),
|
||||||
|
Expression::Logic(logic) => logic.validate(_source, _context),
|
||||||
|
Expression::FunctionCall(function_call) => function_call.validate(_source, _context),
|
||||||
|
Expression::Index(index) => index.validate(_source, _context),
|
||||||
|
Expression::Command(command) => command.validate(_source, _context),
|
||||||
|
Expression::As(r#as) => r#as.validate(_source, _context),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
match self {
|
||||||
|
Expression::Value(value_node) => value_node.run(_source, _context),
|
||||||
|
Expression::Identifier(identifier) => identifier.run(_source, _context),
|
||||||
|
Expression::Math(math) => math.run(_source, _context),
|
||||||
|
Expression::Logic(logic) => logic.run(_source, _context),
|
||||||
|
Expression::FunctionCall(function_call) => function_call.run(_source, _context),
|
||||||
|
Expression::Index(index) => index.run(_source, _context),
|
||||||
|
Expression::Command(command) => command.run(_source, _context),
|
||||||
|
Expression::As(r#as) => r#as.run(_source, _context),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for Expression {
|
||||||
|
fn format(&self, _output: &mut String, _indent_level: u8) {
|
||||||
|
match self {
|
||||||
|
Expression::Value(value_node) => value_node.format(_output, _indent_level),
|
||||||
|
Expression::Identifier(identifier) => identifier.format(_output, _indent_level),
|
||||||
|
Expression::Math(math) => math.format(_output, _indent_level),
|
||||||
|
Expression::Logic(logic) => logic.format(_output, _indent_level),
|
||||||
|
Expression::FunctionCall(function_call) => function_call.format(_output, _indent_level),
|
||||||
|
Expression::Index(index) => index.format(_output, _indent_level),
|
||||||
|
Expression::Command(command) => command.format(_output, _indent_level),
|
||||||
|
Expression::As(r#as) => r#as.format(_output, _indent_level),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
153
src/abstract_tree/for.rs
Normal file
153
src/abstract_tree/for.rs
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
use rayon::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Block, Context, Expression, Format, Identifier, SourcePosition, SyntaxNode, Type,
|
||||||
|
Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Abstract representation of a for loop statement.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct For {
|
||||||
|
is_async: bool,
|
||||||
|
item_id: Identifier,
|
||||||
|
collection: Expression,
|
||||||
|
block: Block,
|
||||||
|
source_position: SourcePosition,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
context: Context,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for For {
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("for", node)?;
|
||||||
|
|
||||||
|
let for_node = node.child(0).unwrap();
|
||||||
|
let is_async = match for_node.kind() {
|
||||||
|
"for" => false,
|
||||||
|
"async for" => true,
|
||||||
|
_ => {
|
||||||
|
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||||
|
expected: "for or async for".to_string(),
|
||||||
|
actual: for_node.kind().to_string(),
|
||||||
|
position: node.range().into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let identifier_node = node.child(1).unwrap();
|
||||||
|
let identifier = Identifier::from_syntax(identifier_node, source, context)?;
|
||||||
|
|
||||||
|
let expression_node = node.child(3).unwrap();
|
||||||
|
let expression = Expression::from_syntax(expression_node, source, context)?;
|
||||||
|
|
||||||
|
let loop_context = Context::with_variables_from(context)?;
|
||||||
|
|
||||||
|
let item_node = node.child(4).unwrap();
|
||||||
|
let item = Block::from_syntax(item_node, source, &loop_context)?;
|
||||||
|
|
||||||
|
Ok(For {
|
||||||
|
is_async,
|
||||||
|
item_id: identifier,
|
||||||
|
collection: expression,
|
||||||
|
block: item,
|
||||||
|
source_position: SourcePosition::from(node.range()),
|
||||||
|
context: loop_context,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
Ok(Type::None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
|
||||||
|
self.collection.validate(_source, context)?;
|
||||||
|
|
||||||
|
let collection_type = self.collection.expected_type(context)?;
|
||||||
|
let item_type = match collection_type {
|
||||||
|
Type::Any => Type::Any,
|
||||||
|
Type::Collection => Type::Any,
|
||||||
|
Type::List => Type::Any,
|
||||||
|
Type::ListOf(_) => todo!(),
|
||||||
|
Type::ListExact(_) => todo!(),
|
||||||
|
Type::Map(_) => todo!(),
|
||||||
|
Type::String => todo!(),
|
||||||
|
Type::Range => todo!(),
|
||||||
|
_ => {
|
||||||
|
return Err(ValidationError::TypeCheck {
|
||||||
|
expected: Type::Collection,
|
||||||
|
actual: collection_type,
|
||||||
|
position: self.source_position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let key = self.item_id.clone();
|
||||||
|
|
||||||
|
self.context.inherit_all_from(context)?;
|
||||||
|
self.context.set_type(key, item_type)?;
|
||||||
|
self.item_id.validate(_source, &self.context)?;
|
||||||
|
self.block.validate(_source, &self.context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
self.context.inherit_all_from(context)?;
|
||||||
|
|
||||||
|
let expression_run = self.collection.run(source, context)?;
|
||||||
|
let key = &self.item_id;
|
||||||
|
|
||||||
|
if let Value::Range(range) = expression_run {
|
||||||
|
if self.is_async {
|
||||||
|
range.into_par_iter().try_for_each(|integer| {
|
||||||
|
self.context.add_allowance(key)?;
|
||||||
|
self.context
|
||||||
|
.set_value(key.clone(), Value::Integer(integer))?;
|
||||||
|
self.block.run(source, &self.context).map(|_value| ())
|
||||||
|
})?;
|
||||||
|
} else {
|
||||||
|
for i in range {
|
||||||
|
self.context.add_allowance(key)?;
|
||||||
|
self.context.set_value(key.clone(), Value::Integer(i))?;
|
||||||
|
self.block.run(source, &self.context)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(Value::none());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Value::List(list) = &expression_run {
|
||||||
|
if self.is_async {
|
||||||
|
list.items()?.par_iter().try_for_each(|value| {
|
||||||
|
self.context.add_allowance(key)?;
|
||||||
|
self.context.set_value(key.clone(), value.clone())?;
|
||||||
|
self.block.run(source, &self.context).map(|_value| ())
|
||||||
|
})?;
|
||||||
|
} else {
|
||||||
|
for value in list.items()?.iter() {
|
||||||
|
self.context.add_allowance(key)?;
|
||||||
|
self.context.set_value(key.clone(), value.clone())?;
|
||||||
|
self.block.run(source, &self.context)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::none())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for For {
|
||||||
|
fn format(&self, output: &mut String, indent_level: u8) {
|
||||||
|
if self.is_async {
|
||||||
|
output.push_str("async for ");
|
||||||
|
} else {
|
||||||
|
output.push_str("for ");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.item_id.format(output, indent_level);
|
||||||
|
output.push_str(" in ");
|
||||||
|
self.collection.format(output, indent_level);
|
||||||
|
output.push(' ');
|
||||||
|
self.block.format(output, indent_level);
|
||||||
|
}
|
||||||
|
}
|
202
src/abstract_tree/function_call.rs
Normal file
202
src/abstract_tree/function_call.rs
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
built_in_functions::Callable,
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Context, Expression, Format, Function, FunctionExpression, SourcePosition,
|
||||||
|
SyntaxNode, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A function being invoked and the arguments it is being passed.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct FunctionCall {
|
||||||
|
function_expression: FunctionExpression,
|
||||||
|
arguments: Vec<Expression>,
|
||||||
|
syntax_position: SourcePosition,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FunctionCall {
|
||||||
|
/// Returns a new FunctionCall.
|
||||||
|
pub fn new(
|
||||||
|
function_expression: FunctionExpression,
|
||||||
|
arguments: Vec<Expression>,
|
||||||
|
syntax_position: SourcePosition,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
function_expression,
|
||||||
|
arguments,
|
||||||
|
syntax_position,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for FunctionCall {
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("function_call", node)?;
|
||||||
|
|
||||||
|
let function_node = node.child(0).unwrap();
|
||||||
|
let function_expression = FunctionExpression::from_syntax(function_node, source, context)?;
|
||||||
|
|
||||||
|
let mut arguments = Vec::new();
|
||||||
|
|
||||||
|
for index in 2..node.child_count() - 1 {
|
||||||
|
let child = node.child(index).unwrap();
|
||||||
|
|
||||||
|
if child.is_named() {
|
||||||
|
let expression = Expression::from_syntax(child, source, context)?;
|
||||||
|
|
||||||
|
arguments.push(expression);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(FunctionCall {
|
||||||
|
function_expression,
|
||||||
|
arguments,
|
||||||
|
syntax_position: node.range().into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
match &self.function_expression {
|
||||||
|
FunctionExpression::Identifier(identifier) => {
|
||||||
|
let identifier_type = identifier.expected_type(context)?;
|
||||||
|
|
||||||
|
if let Type::Function {
|
||||||
|
parameter_types: _,
|
||||||
|
return_type,
|
||||||
|
} = &identifier_type
|
||||||
|
{
|
||||||
|
Ok(*return_type.clone())
|
||||||
|
} else {
|
||||||
|
Ok(identifier_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FunctionExpression::FunctionCall(function_call) => function_call.expected_type(context),
|
||||||
|
FunctionExpression::Value(value_node) => {
|
||||||
|
let value_type = value_node.expected_type(context)?;
|
||||||
|
|
||||||
|
if let Type::Function { return_type, .. } = value_type {
|
||||||
|
Ok(*return_type)
|
||||||
|
} else {
|
||||||
|
Ok(value_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FunctionExpression::Index(index) => {
|
||||||
|
let index_type = index.expected_type(context)?;
|
||||||
|
|
||||||
|
if let Type::Function { return_type, .. } = index_type {
|
||||||
|
Ok(*return_type)
|
||||||
|
} else {
|
||||||
|
Ok(index_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
|
||||||
|
self.function_expression.validate(_source, context)?;
|
||||||
|
|
||||||
|
let function_expression_type = self.function_expression.expected_type(context)?;
|
||||||
|
|
||||||
|
let parameter_types = if let Type::Function {
|
||||||
|
parameter_types, ..
|
||||||
|
} = function_expression_type
|
||||||
|
{
|
||||||
|
parameter_types
|
||||||
|
} else {
|
||||||
|
return Err(ValidationError::TypeCheckExpectedFunction {
|
||||||
|
actual: function_expression_type,
|
||||||
|
position: self.syntax_position,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.arguments.len() != parameter_types.len() {
|
||||||
|
return Err(ValidationError::ExpectedFunctionArgumentAmount {
|
||||||
|
expected: parameter_types.len(),
|
||||||
|
actual: self.arguments.len(),
|
||||||
|
position: self.syntax_position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (index, expression) in self.arguments.iter().enumerate() {
|
||||||
|
expression.validate(_source, context)?;
|
||||||
|
|
||||||
|
if let Some(expected) = parameter_types.get(index) {
|
||||||
|
let actual = expression.expected_type(context)?;
|
||||||
|
|
||||||
|
if !expected.accepts(&actual) {
|
||||||
|
return Err(ValidationError::TypeCheck {
|
||||||
|
expected: expected.clone(),
|
||||||
|
actual,
|
||||||
|
position: self.syntax_position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
let value = match &self.function_expression {
|
||||||
|
FunctionExpression::Identifier(identifier) => {
|
||||||
|
if let Some(value) = context.get_value(identifier)? {
|
||||||
|
value.clone()
|
||||||
|
} else {
|
||||||
|
return Err(RuntimeError::ValidationFailure(
|
||||||
|
ValidationError::VariableIdentifierNotFound(identifier.clone()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FunctionExpression::FunctionCall(function_call) => {
|
||||||
|
function_call.run(source, context)?
|
||||||
|
}
|
||||||
|
FunctionExpression::Value(value_node) => value_node.run(source, context)?,
|
||||||
|
FunctionExpression::Index(index) => index.run(source, context)?,
|
||||||
|
};
|
||||||
|
let function = value.as_function()?;
|
||||||
|
|
||||||
|
match function {
|
||||||
|
Function::BuiltIn(built_in_function) => {
|
||||||
|
let mut arguments = Vec::with_capacity(self.arguments.len());
|
||||||
|
|
||||||
|
for expression in &self.arguments {
|
||||||
|
let value = expression.run(source, context)?;
|
||||||
|
|
||||||
|
arguments.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
built_in_function.call(&arguments, source, context)
|
||||||
|
}
|
||||||
|
Function::ContextDefined(function_node) => {
|
||||||
|
let call_context = Context::with_variables_from(function_node.context())?;
|
||||||
|
|
||||||
|
call_context.inherit_from(context)?;
|
||||||
|
|
||||||
|
let parameter_expression_pairs =
|
||||||
|
function_node.parameters().iter().zip(self.arguments.iter());
|
||||||
|
|
||||||
|
for (identifier, expression) in parameter_expression_pairs {
|
||||||
|
let value = expression.run(source, context)?;
|
||||||
|
|
||||||
|
call_context.set_value(identifier.clone(), value)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
function_node.body().run(source, &call_context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for FunctionCall {
|
||||||
|
fn format(&self, output: &mut String, indent_level: u8) {
|
||||||
|
self.function_expression.format(output, indent_level);
|
||||||
|
output.push('(');
|
||||||
|
|
||||||
|
for expression in &self.arguments {
|
||||||
|
expression.format(output, indent_level);
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push(')');
|
||||||
|
}
|
||||||
|
}
|
91
src/abstract_tree/function_expression.rs
Normal file
91
src/abstract_tree/function_expression.rs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Context, Format, FunctionCall, Identifier, Index, SyntaxNode, Type, Value,
|
||||||
|
ValueNode,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub enum FunctionExpression {
|
||||||
|
Identifier(Identifier),
|
||||||
|
FunctionCall(Box<FunctionCall>),
|
||||||
|
Value(ValueNode),
|
||||||
|
Index(Index),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for FunctionExpression {
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("function_expression", node)?;
|
||||||
|
|
||||||
|
let first_child = node.child(0).unwrap();
|
||||||
|
let child = if first_child.is_named() {
|
||||||
|
first_child
|
||||||
|
} else {
|
||||||
|
node.child(1).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let function_expression = match child.kind() {
|
||||||
|
"identifier" => {
|
||||||
|
FunctionExpression::Identifier(Identifier::from_syntax(child, source, context)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
"function_call" => FunctionExpression::FunctionCall(Box::new(
|
||||||
|
FunctionCall::from_syntax(child, source, context)?,
|
||||||
|
)),
|
||||||
|
"value" => FunctionExpression::Value(ValueNode::from_syntax(child, source, context)?),
|
||||||
|
"index" => FunctionExpression::Index(Index::from_syntax(child, source, context)?),
|
||||||
|
_ => {
|
||||||
|
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||||
|
expected: "identifier, function call, value or index".to_string(),
|
||||||
|
actual: child.kind().to_string(),
|
||||||
|
position: node.range().into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(function_expression)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
match self {
|
||||||
|
FunctionExpression::Identifier(identifier) => identifier.expected_type(context),
|
||||||
|
FunctionExpression::FunctionCall(function_call) => function_call.expected_type(context),
|
||||||
|
FunctionExpression::Value(value_node) => value_node.expected_type(context),
|
||||||
|
FunctionExpression::Index(index) => index.expected_type(context),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||||
|
match self {
|
||||||
|
FunctionExpression::Identifier(identifier) => identifier.validate(_source, _context),
|
||||||
|
FunctionExpression::FunctionCall(function_call) => {
|
||||||
|
function_call.validate(_source, _context)
|
||||||
|
}
|
||||||
|
FunctionExpression::Value(value_node) => value_node.validate(_source, _context),
|
||||||
|
FunctionExpression::Index(index) => index.validate(_source, _context),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
match self {
|
||||||
|
FunctionExpression::Identifier(identifier) => identifier.run(source, context),
|
||||||
|
FunctionExpression::FunctionCall(function_call) => function_call.run(source, context),
|
||||||
|
FunctionExpression::Value(value_node) => value_node.run(source, context),
|
||||||
|
FunctionExpression::Index(index) => index.run(source, context),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for FunctionExpression {
|
||||||
|
fn format(&self, output: &mut String, indent_level: u8) {
|
||||||
|
match self {
|
||||||
|
FunctionExpression::Value(value_node) => value_node.format(output, indent_level),
|
||||||
|
FunctionExpression::Identifier(identifier) => identifier.format(output, indent_level),
|
||||||
|
FunctionExpression::FunctionCall(function_call) => {
|
||||||
|
function_call.format(output, indent_level)
|
||||||
|
}
|
||||||
|
FunctionExpression::Index(index) => index.format(output, indent_level),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
179
src/abstract_tree/function_node.rs
Normal file
179
src/abstract_tree/function_node.rs
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Block, Context, Format, Function, Identifier, SourcePosition, SyntaxNode, Type,
|
||||||
|
TypeSpecification, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct FunctionNode {
|
||||||
|
parameters: Vec<Identifier>,
|
||||||
|
body: Block,
|
||||||
|
r#type: Type,
|
||||||
|
syntax_position: SourcePosition,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
context: Context,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FunctionNode {
|
||||||
|
pub fn parameters(&self) -> &Vec<Identifier> {
|
||||||
|
&self.parameters
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn body(&self) -> &Block {
|
||||||
|
&self.body
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn r#type(&self) -> &Type {
|
||||||
|
&self.r#type
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn syntax_position(&self) -> &SourcePosition {
|
||||||
|
&self.syntax_position
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn context(&self) -> &Context {
|
||||||
|
&self.context
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn return_type(&self) -> &Type {
|
||||||
|
match &self.r#type {
|
||||||
|
Type::Function {
|
||||||
|
parameter_types: _,
|
||||||
|
return_type,
|
||||||
|
} => return_type.as_ref(),
|
||||||
|
_ => &Type::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for FunctionNode {
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("function", node)?;
|
||||||
|
|
||||||
|
let child_count = node.child_count();
|
||||||
|
let mut parameters = Vec::new();
|
||||||
|
let mut parameter_types = Vec::new();
|
||||||
|
|
||||||
|
for index in 1..child_count - 3 {
|
||||||
|
let child = node.child(index).unwrap();
|
||||||
|
|
||||||
|
if child.kind() == "identifier" {
|
||||||
|
let identifier = Identifier::from_syntax(child, source, context)?;
|
||||||
|
|
||||||
|
parameters.push(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
if child.kind() == "type_specification" {
|
||||||
|
let type_specification = TypeSpecification::from_syntax(child, source, context)?;
|
||||||
|
|
||||||
|
parameter_types.push(type_specification.take_inner());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let return_type_node = node.child(child_count - 2).unwrap();
|
||||||
|
let return_type = TypeSpecification::from_syntax(return_type_node, source, context)?;
|
||||||
|
|
||||||
|
let function_context = Context::with_variables_from(context)?;
|
||||||
|
|
||||||
|
let body_node = node.child(child_count - 1).unwrap();
|
||||||
|
let body = Block::from_syntax(body_node, source, &function_context)?;
|
||||||
|
|
||||||
|
let r#type = Type::function(parameter_types, return_type.take_inner());
|
||||||
|
let syntax_position = node.range().into();
|
||||||
|
|
||||||
|
Ok(FunctionNode {
|
||||||
|
parameters,
|
||||||
|
body,
|
||||||
|
r#type,
|
||||||
|
syntax_position,
|
||||||
|
context: function_context,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
Ok(self.r#type().clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, source: &str, context: &Context) -> Result<(), ValidationError> {
|
||||||
|
if let Type::Function {
|
||||||
|
parameter_types,
|
||||||
|
return_type,
|
||||||
|
} = &self.r#type
|
||||||
|
{
|
||||||
|
self.context.inherit_from(context)?;
|
||||||
|
|
||||||
|
for (parameter, r#type) in self.parameters.iter().zip(parameter_types.iter()) {
|
||||||
|
self.context.set_type(parameter.clone(), r#type.clone())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let actual = self.body.expected_type(&self.context)?;
|
||||||
|
|
||||||
|
if !return_type.accepts(&actual) {
|
||||||
|
return Err(ValidationError::TypeCheck {
|
||||||
|
expected: return_type.as_ref().clone(),
|
||||||
|
actual,
|
||||||
|
position: self.syntax_position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.body.validate(source, &self.context)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(ValidationError::TypeCheckExpectedFunction {
|
||||||
|
actual: self.r#type.clone(),
|
||||||
|
position: self.syntax_position,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
self.context.inherit_from(context)?;
|
||||||
|
|
||||||
|
let self_as_value = Value::Function(Function::ContextDefined(self.clone()));
|
||||||
|
|
||||||
|
Ok(self_as_value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for FunctionNode {
|
||||||
|
fn format(&self, output: &mut String, indent_level: u8) {
|
||||||
|
let (parameter_types, return_type) = if let Type::Function {
|
||||||
|
parameter_types,
|
||||||
|
return_type,
|
||||||
|
} = &self.r#type
|
||||||
|
{
|
||||||
|
(parameter_types, return_type)
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
output.push('(');
|
||||||
|
|
||||||
|
for (identifier, r#type) in self.parameters.iter().zip(parameter_types.iter()) {
|
||||||
|
identifier.format(output, indent_level);
|
||||||
|
output.push_str(" <");
|
||||||
|
r#type.format(output, indent_level);
|
||||||
|
output.push('>');
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push_str(") <");
|
||||||
|
return_type.format(output, indent_level);
|
||||||
|
output.push_str("> ");
|
||||||
|
self.body.format(output, indent_level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for FunctionNode {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
let mut string = String::new();
|
||||||
|
|
||||||
|
self.format(&mut string, 0);
|
||||||
|
f.write_str(&string)
|
||||||
|
}
|
||||||
|
}
|
167
src/abstract_tree/identifier.rs
Normal file
167
src/abstract_tree/identifier.rs
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
use std::{
|
||||||
|
fmt::{self, Display, Formatter},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use serde::{de::Visitor, Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
built_in_identifiers::all_built_in_identifiers,
|
||||||
|
built_in_values::all_built_in_values,
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Context, Format, SyntaxNode, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A string by which a variable is known to a context.
|
||||||
|
///
|
||||||
|
/// Every variable is a key-value pair. An identifier holds the key part of that
|
||||||
|
/// pair. Its inner value can be used to retrieve a Value instance from a Map.
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct Identifier(Arc<String>);
|
||||||
|
|
||||||
|
impl Identifier {
|
||||||
|
pub fn new(key: &str) -> Self {
|
||||||
|
for built_in_identifier in all_built_in_identifiers() {
|
||||||
|
let identifier = built_in_identifier.get();
|
||||||
|
|
||||||
|
if &key == identifier.inner().as_ref() {
|
||||||
|
return identifier.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Identifier(Arc::new(key.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_raw_parts(arc: Arc<String>) -> Self {
|
||||||
|
Identifier(arc)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner(&self) -> &Arc<String> {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains(&self, string: &str) -> bool {
|
||||||
|
self.0.as_ref() == string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for Identifier {
|
||||||
|
fn from_syntax(
|
||||||
|
node: SyntaxNode,
|
||||||
|
source: &str,
|
||||||
|
_context: &Context,
|
||||||
|
) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("identifier", node)?;
|
||||||
|
|
||||||
|
let text = &source[node.byte_range()];
|
||||||
|
|
||||||
|
debug_assert!(!text.is_empty());
|
||||||
|
|
||||||
|
Ok(Identifier::new(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
|
||||||
|
let variable_exists = context.add_allowance(self)?;
|
||||||
|
|
||||||
|
if variable_exists {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
for built_in_value in all_built_in_values() {
|
||||||
|
if built_in_value.name() == self.inner().as_ref() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(ValidationError::VariableIdentifierNotFound(self.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
if let Some(r#type) = context.get_type(self)? {
|
||||||
|
Ok(r#type)
|
||||||
|
} else {
|
||||||
|
for built_in_value in all_built_in_values() {
|
||||||
|
if built_in_value.name() == self.inner().as_ref() {
|
||||||
|
return Ok(built_in_value.get().r#type()?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(ValidationError::VariableIdentifierNotFound(self.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
if let Some(value) = context.get_value(self)? {
|
||||||
|
return Ok(value);
|
||||||
|
} else {
|
||||||
|
for built_in_value in all_built_in_values() {
|
||||||
|
if built_in_value.name() == self.inner().as_ref() {
|
||||||
|
return Ok(built_in_value.get().clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(RuntimeError::ValidationFailure(
|
||||||
|
ValidationError::VariableIdentifierNotFound(self.clone()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for Identifier {
|
||||||
|
fn format(&self, output: &mut String, _indent_level: u8) {
|
||||||
|
output.push_str(&self.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for Identifier {
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
Identifier::from_raw_parts(Arc::new(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for Identifier {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
Identifier::new(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Identifier {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Identifier {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(self.0.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Identifier {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_string(IdentifierVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IdentifierVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for IdentifierVisitor {
|
||||||
|
type Value = Identifier;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("valid UFT-8 sequence")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
Ok(Identifier(Arc::new(v)))
|
||||||
|
}
|
||||||
|
}
|
160
src/abstract_tree/if_else.rs
Normal file
160
src/abstract_tree/if_else.rs
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Block, Context, Expression, Format, SourcePosition, SyntaxNode, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct IfElse {
|
||||||
|
if_expression: Expression,
|
||||||
|
if_block: Block,
|
||||||
|
else_if_expressions: Vec<Expression>,
|
||||||
|
else_if_blocks: Vec<Block>,
|
||||||
|
else_block: Option<Block>,
|
||||||
|
source_position: SourcePosition,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for IfElse {
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
let if_expression_node = node.child(0).unwrap().child(1).unwrap();
|
||||||
|
let if_expression = Expression::from_syntax(if_expression_node, source, context)?;
|
||||||
|
|
||||||
|
let if_block_node = node.child(0).unwrap().child(2).unwrap();
|
||||||
|
let if_block = Block::from_syntax(if_block_node, source, context)?;
|
||||||
|
|
||||||
|
let child_count = node.child_count();
|
||||||
|
let mut else_if_expressions = Vec::new();
|
||||||
|
let mut else_if_blocks = Vec::new();
|
||||||
|
let mut else_block = None;
|
||||||
|
|
||||||
|
for index in 1..child_count {
|
||||||
|
let child = node.child(index).unwrap();
|
||||||
|
|
||||||
|
if child.kind() == "else_if" {
|
||||||
|
let expression_node = child.child(1).unwrap();
|
||||||
|
let expression = Expression::from_syntax(expression_node, source, context)?;
|
||||||
|
|
||||||
|
else_if_expressions.push(expression);
|
||||||
|
|
||||||
|
let block_node = child.child(2).unwrap();
|
||||||
|
let block = Block::from_syntax(block_node, source, context)?;
|
||||||
|
|
||||||
|
else_if_blocks.push(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
if child.kind() == "else" {
|
||||||
|
let else_node = child.child(1).unwrap();
|
||||||
|
else_block = Some(Block::from_syntax(else_node, source, context)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(IfElse {
|
||||||
|
if_expression,
|
||||||
|
if_block,
|
||||||
|
else_if_expressions,
|
||||||
|
else_if_blocks,
|
||||||
|
else_block,
|
||||||
|
source_position: SourcePosition::from(node.range()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
self.if_block.expected_type(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
|
||||||
|
self.if_expression.validate(_source, context)?;
|
||||||
|
self.if_block.validate(_source, context)?;
|
||||||
|
|
||||||
|
let expected = self.if_block.expected_type(context)?;
|
||||||
|
let else_ifs = self
|
||||||
|
.else_if_expressions
|
||||||
|
.iter()
|
||||||
|
.zip(self.else_if_blocks.iter());
|
||||||
|
|
||||||
|
for (expression, block) in else_ifs {
|
||||||
|
expression.validate(_source, context)?;
|
||||||
|
block.validate(_source, context)?;
|
||||||
|
|
||||||
|
let actual = block.expected_type(context)?;
|
||||||
|
|
||||||
|
if !expected.accepts(&actual) {
|
||||||
|
return Err(ValidationError::TypeCheck {
|
||||||
|
expected,
|
||||||
|
actual,
|
||||||
|
position: self.source_position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(block) = &self.else_block {
|
||||||
|
block.validate(_source, context)?;
|
||||||
|
|
||||||
|
let actual = block.expected_type(context)?;
|
||||||
|
|
||||||
|
if !expected.accepts(&actual) {
|
||||||
|
return Err(ValidationError::TypeCheck {
|
||||||
|
expected,
|
||||||
|
actual,
|
||||||
|
position: self.source_position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
let if_boolean = self.if_expression.run(source, context)?.as_boolean()?;
|
||||||
|
|
||||||
|
if if_boolean {
|
||||||
|
self.if_block.run(source, context)
|
||||||
|
} else {
|
||||||
|
let else_ifs = self
|
||||||
|
.else_if_expressions
|
||||||
|
.iter()
|
||||||
|
.zip(self.else_if_blocks.iter());
|
||||||
|
|
||||||
|
for (expression, block) in else_ifs {
|
||||||
|
let if_boolean = expression.run(source, context)?.as_boolean()?;
|
||||||
|
|
||||||
|
if if_boolean {
|
||||||
|
return block.run(source, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(block) = &self.else_block {
|
||||||
|
block.run(source, context)
|
||||||
|
} else {
|
||||||
|
Ok(Value::none())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for IfElse {
|
||||||
|
fn format(&self, output: &mut String, indent_level: u8) {
|
||||||
|
output.push_str("if ");
|
||||||
|
self.if_expression.format(output, indent_level);
|
||||||
|
output.push(' ');
|
||||||
|
self.if_block.format(output, indent_level);
|
||||||
|
|
||||||
|
let else_ifs = self
|
||||||
|
.else_if_expressions
|
||||||
|
.iter()
|
||||||
|
.zip(self.else_if_blocks.iter());
|
||||||
|
|
||||||
|
for (expression, block) in else_ifs {
|
||||||
|
output.push_str("else if ");
|
||||||
|
expression.format(output, indent_level);
|
||||||
|
output.push(' ');
|
||||||
|
block.format(output, indent_level);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(block) = &self.else_block {
|
||||||
|
output.push_str("else ");
|
||||||
|
block.format(output, indent_level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
134
src/abstract_tree/index.rs
Normal file
134
src/abstract_tree/index.rs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Context, Format, Identifier, IndexExpression, SourcePosition, SyntaxNode, Type,
|
||||||
|
Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Abstract representation of an index expression.
|
||||||
|
///
|
||||||
|
/// An index is a means of accessing values stored in list, maps and strings.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct Index {
|
||||||
|
pub collection: IndexExpression,
|
||||||
|
pub index: IndexExpression,
|
||||||
|
source_position: SourcePosition,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for Index {
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("index", node)?;
|
||||||
|
|
||||||
|
let collection_node = node.child(0).unwrap();
|
||||||
|
let collection = IndexExpression::from_syntax(collection_node, source, context)?;
|
||||||
|
|
||||||
|
let index_node = node.child(2).unwrap();
|
||||||
|
let index = IndexExpression::from_syntax(index_node, source, context)?;
|
||||||
|
|
||||||
|
Ok(Index {
|
||||||
|
collection,
|
||||||
|
index,
|
||||||
|
source_position: SourcePosition::from(node.range()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
match self.collection.expected_type(context)? {
|
||||||
|
Type::ListOf(item_type) => Ok(*item_type.clone()),
|
||||||
|
Type::Map(map_types_option) => {
|
||||||
|
if let (Some(map_type), IndexExpression::Identifier(identifier)) =
|
||||||
|
(map_types_option, &self.index)
|
||||||
|
{
|
||||||
|
if let Some(r#type) = map_type.get(&identifier) {
|
||||||
|
Ok(r#type.clone())
|
||||||
|
} else {
|
||||||
|
Ok(Type::Any)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(Type::Any)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Type::None => Ok(Type::None),
|
||||||
|
r#type => Ok(r#type),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||||
|
self.collection.validate(_source, _context)?;
|
||||||
|
|
||||||
|
let collection_type = self.collection.expected_type(_context)?;
|
||||||
|
|
||||||
|
if let (Type::Map(type_map_option), IndexExpression::Identifier(identifier)) =
|
||||||
|
(collection_type, &self.index)
|
||||||
|
{
|
||||||
|
if let Some(type_map) = type_map_option {
|
||||||
|
if !type_map.contains_key(identifier) {
|
||||||
|
return Err(ValidationError::VariableIdentifierNotFound(
|
||||||
|
identifier.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.index.validate(_source, _context)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
let value = self.collection.run(source, context)?;
|
||||||
|
|
||||||
|
match value {
|
||||||
|
Value::List(list) => {
|
||||||
|
let index = self.index.run(source, context)?.as_integer()? as usize;
|
||||||
|
let item = list.items()?.get(index).cloned().unwrap_or_default();
|
||||||
|
|
||||||
|
Ok(item)
|
||||||
|
}
|
||||||
|
Value::Map(map) => {
|
||||||
|
let map = map.inner();
|
||||||
|
|
||||||
|
let value = if let IndexExpression::Identifier(identifier) = &self.index {
|
||||||
|
if let Some(value) = map.get(identifier) {
|
||||||
|
value
|
||||||
|
} else {
|
||||||
|
return Err(RuntimeError::ValidationFailure(
|
||||||
|
ValidationError::VariableIdentifierNotFound(identifier.clone()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let index_value = self.index.run(source, context)?;
|
||||||
|
let identifier = Identifier::new(index_value.as_string()?);
|
||||||
|
|
||||||
|
if let Some(value) = map.get(&identifier) {
|
||||||
|
value
|
||||||
|
} else {
|
||||||
|
return Err(RuntimeError::ValidationFailure(
|
||||||
|
ValidationError::VariableIdentifierNotFound(identifier.clone()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(value.clone())
|
||||||
|
}
|
||||||
|
Value::String(string) => {
|
||||||
|
let index = self.index.run(source, context)?.as_integer()? as usize;
|
||||||
|
let item = string.chars().nth(index).unwrap_or_default();
|
||||||
|
|
||||||
|
Ok(Value::string(item.to_string()))
|
||||||
|
}
|
||||||
|
_ => Err(RuntimeError::ValidationFailure(
|
||||||
|
ValidationError::ExpectedCollection { actual: value },
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for Index {
|
||||||
|
fn format(&self, output: &mut String, indent_level: u8) {
|
||||||
|
self.collection.format(output, indent_level);
|
||||||
|
output.push(':');
|
||||||
|
self.index.format(output, indent_level);
|
||||||
|
}
|
||||||
|
}
|
96
src/abstract_tree/index_assignment.rs
Normal file
96
src/abstract_tree/index_assignment.rs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, AssignmentOperator, Context, Format, Identifier, Index, IndexExpression,
|
||||||
|
SourcePosition, Statement, SyntaxNode, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct IndexAssignment {
|
||||||
|
index: Index,
|
||||||
|
operator: AssignmentOperator,
|
||||||
|
statement: Statement,
|
||||||
|
position: SourcePosition,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for IndexAssignment {
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("index_assignment", node)?;
|
||||||
|
|
||||||
|
let index_node = node.child(0).unwrap();
|
||||||
|
let index = Index::from_syntax(index_node, source, context)?;
|
||||||
|
|
||||||
|
let operator_node = node.child(1).unwrap();
|
||||||
|
let operator = AssignmentOperator::from_syntax(operator_node, source, context)?;
|
||||||
|
|
||||||
|
let statement_node = node.child(2).unwrap();
|
||||||
|
let statement = Statement::from_syntax(statement_node, source, context)?;
|
||||||
|
|
||||||
|
Ok(IndexAssignment {
|
||||||
|
index,
|
||||||
|
operator,
|
||||||
|
statement,
|
||||||
|
position: node.range().into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
Ok(Type::None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||||
|
self.index.validate(_source, _context)?;
|
||||||
|
self.statement.validate(_source, _context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
let _index_collection = self.index.collection.run(source, context)?;
|
||||||
|
let index_identifier = if let IndexExpression::Identifier(identifier) = &self.index.index {
|
||||||
|
identifier
|
||||||
|
} else {
|
||||||
|
let index_run = self.index.index.run(source, context)?;
|
||||||
|
let expected_identifier = Identifier::new(index_run.as_string()?);
|
||||||
|
|
||||||
|
return Err(RuntimeError::ValidationFailure(
|
||||||
|
ValidationError::VariableIdentifierNotFound(expected_identifier),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let value = self.statement.run(source, context)?;
|
||||||
|
|
||||||
|
let new_value = match self.operator {
|
||||||
|
AssignmentOperator::PlusEqual => {
|
||||||
|
if let Some(previous_value) = context.get_value(index_identifier)? {
|
||||||
|
previous_value.add(value, self.position)?
|
||||||
|
} else {
|
||||||
|
return Err(RuntimeError::ValidationFailure(
|
||||||
|
ValidationError::VariableIdentifierNotFound(index_identifier.clone()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AssignmentOperator::MinusEqual => {
|
||||||
|
if let Some(previous_value) = context.get_value(index_identifier)? {
|
||||||
|
previous_value.subtract(value, self.position)?
|
||||||
|
} else {
|
||||||
|
Value::none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AssignmentOperator::Equal => value,
|
||||||
|
};
|
||||||
|
|
||||||
|
context.set_value(index_identifier.clone(), new_value)?;
|
||||||
|
|
||||||
|
Ok(Value::none())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for IndexAssignment {
|
||||||
|
fn format(&self, output: &mut String, indent_level: u8) {
|
||||||
|
self.index.format(output, indent_level);
|
||||||
|
output.push(' ');
|
||||||
|
self.operator.format(output, indent_level);
|
||||||
|
output.push(' ');
|
||||||
|
self.statement.format(output, indent_level);
|
||||||
|
}
|
||||||
|
}
|
98
src/abstract_tree/index_expression.rs
Normal file
98
src/abstract_tree/index_expression.rs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
value_node::ValueNode,
|
||||||
|
AbstractTree, Context, Format, FunctionCall, Identifier, Index, SyntaxNode, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub enum IndexExpression {
|
||||||
|
Value(ValueNode),
|
||||||
|
Identifier(Identifier),
|
||||||
|
Index(Box<Index>),
|
||||||
|
FunctionCall(Box<FunctionCall>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for IndexExpression {
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("index_expression", node)?;
|
||||||
|
|
||||||
|
let first_child = node.child(0).unwrap();
|
||||||
|
let child = if first_child.is_named() {
|
||||||
|
first_child
|
||||||
|
} else {
|
||||||
|
node.child(1).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let abstract_node = match child.kind() {
|
||||||
|
"value" => IndexExpression::Value(ValueNode::from_syntax(child, source, context)?),
|
||||||
|
"identifier" => {
|
||||||
|
IndexExpression::Identifier(Identifier::from_syntax(child, source, context)?)
|
||||||
|
}
|
||||||
|
"index" => {
|
||||||
|
IndexExpression::Index(Box::new(Index::from_syntax(child, source, context)?))
|
||||||
|
}
|
||||||
|
"function_call" => IndexExpression::FunctionCall(Box::new(FunctionCall::from_syntax(
|
||||||
|
child, source, context,
|
||||||
|
)?)),
|
||||||
|
_ => {
|
||||||
|
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||||
|
expected: "value, identifier, index or function call".to_string(),
|
||||||
|
actual: child.kind().to_string(),
|
||||||
|
position: node.range().into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(abstract_node)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
match self {
|
||||||
|
IndexExpression::Value(value_node) => value_node.expected_type(context),
|
||||||
|
IndexExpression::Identifier(identifier) => identifier.expected_type(context),
|
||||||
|
IndexExpression::Index(index) => index.expected_type(context),
|
||||||
|
IndexExpression::FunctionCall(function_call) => function_call.expected_type(context),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
|
||||||
|
match self {
|
||||||
|
IndexExpression::Value(value_node) => value_node.validate(_source, context),
|
||||||
|
IndexExpression::Identifier(identifier) => {
|
||||||
|
context.add_allowance(identifier)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
IndexExpression::Index(index) => index.validate(_source, context),
|
||||||
|
IndexExpression::FunctionCall(function_call) => {
|
||||||
|
function_call.validate(_source, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
match self {
|
||||||
|
IndexExpression::Value(value_node) => value_node.run(source, context),
|
||||||
|
IndexExpression::Identifier(identifier) => identifier.run(source, context),
|
||||||
|
IndexExpression::Index(index) => index.run(source, context),
|
||||||
|
IndexExpression::FunctionCall(function_call) => function_call.run(source, context),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for IndexExpression {
|
||||||
|
fn format(&self, output: &mut String, indent_level: u8) {
|
||||||
|
match self {
|
||||||
|
IndexExpression::Value(value_node) => {
|
||||||
|
value_node.format(output, indent_level);
|
||||||
|
}
|
||||||
|
IndexExpression::Identifier(identifier) => identifier.format(output, indent_level),
|
||||||
|
IndexExpression::FunctionCall(function_call) => {
|
||||||
|
function_call.format(output, indent_level)
|
||||||
|
}
|
||||||
|
IndexExpression::Index(index) => index.format(output, indent_level),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
95
src/abstract_tree/logic.rs
Normal file
95
src/abstract_tree/logic.rs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Context, Expression, Format, LogicOperator, SyntaxNode, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Abstract representation of a logic expression.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct Logic {
|
||||||
|
left: Expression,
|
||||||
|
operator: LogicOperator,
|
||||||
|
right: Expression,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for Logic {
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("logic", node)?;
|
||||||
|
|
||||||
|
let first_node = node.child(0).unwrap();
|
||||||
|
let (left_node, operator_node, right_node) = {
|
||||||
|
if first_node.is_named() {
|
||||||
|
(first_node, node.child(1).unwrap(), node.child(2).unwrap())
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
node.child(1).unwrap(),
|
||||||
|
node.child(2).unwrap(),
|
||||||
|
node.child(3).unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let left = Expression::from_syntax(left_node, source, context)?;
|
||||||
|
let operator = LogicOperator::from_syntax(operator_node, source, context)?;
|
||||||
|
let right = Expression::from_syntax(right_node, source, context)?;
|
||||||
|
|
||||||
|
Ok(Logic {
|
||||||
|
left,
|
||||||
|
operator,
|
||||||
|
right,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
Ok(Type::Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||||
|
log::info!("VALIDATE logic expression");
|
||||||
|
|
||||||
|
self.left.validate(_source, _context)?;
|
||||||
|
self.right.validate(_source, _context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
let left = self.left.run(source, context)?;
|
||||||
|
let right = self.right.run(source, context)?;
|
||||||
|
|
||||||
|
log::info!("RUN logic expression: {left} {} {right}", self.operator);
|
||||||
|
|
||||||
|
let result = match self.operator {
|
||||||
|
LogicOperator::Equal => {
|
||||||
|
if let (Ok(left_num), Ok(right_num)) = (left.as_number(), right.as_number()) {
|
||||||
|
left_num == right_num
|
||||||
|
} else {
|
||||||
|
left == right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LogicOperator::NotEqual => {
|
||||||
|
if let (Ok(left_num), Ok(right_num)) = (left.as_number(), right.as_number()) {
|
||||||
|
left_num != right_num
|
||||||
|
} else {
|
||||||
|
left != right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LogicOperator::And => left.as_boolean()? && right.as_boolean()?,
|
||||||
|
LogicOperator::Or => left.as_boolean()? || right.as_boolean()?,
|
||||||
|
LogicOperator::Greater => left > right,
|
||||||
|
LogicOperator::Less => left < right,
|
||||||
|
LogicOperator::GreaterOrEqual => left >= right,
|
||||||
|
LogicOperator::LessOrEqual => left <= right,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Value::Boolean(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for Logic {
|
||||||
|
fn format(&self, output: &mut String, indent_level: u8) {
|
||||||
|
self.left.format(output, indent_level);
|
||||||
|
output.push(' ');
|
||||||
|
self.operator.format(output, indent_level);
|
||||||
|
output.push(' ');
|
||||||
|
self.right.format(output, indent_level);
|
||||||
|
}
|
||||||
|
}
|
93
src/abstract_tree/logic_operator.rs
Normal file
93
src/abstract_tree/logic_operator.rs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Context, Format, SyntaxNode, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub enum LogicOperator {
|
||||||
|
Equal,
|
||||||
|
NotEqual,
|
||||||
|
And,
|
||||||
|
Or,
|
||||||
|
Greater,
|
||||||
|
Less,
|
||||||
|
GreaterOrEqual,
|
||||||
|
LessOrEqual,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for LogicOperator {
|
||||||
|
fn from_syntax(
|
||||||
|
node: SyntaxNode,
|
||||||
|
_source: &str,
|
||||||
|
_context: &Context,
|
||||||
|
) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("logic_operator", node)?;
|
||||||
|
|
||||||
|
let operator_node = node.child(0).unwrap();
|
||||||
|
let operator = match operator_node.kind() {
|
||||||
|
"==" => LogicOperator::Equal,
|
||||||
|
"!=" => LogicOperator::NotEqual,
|
||||||
|
"&&" => LogicOperator::And,
|
||||||
|
"||" => LogicOperator::Or,
|
||||||
|
">" => LogicOperator::Greater,
|
||||||
|
"<" => LogicOperator::Less,
|
||||||
|
">=" => LogicOperator::GreaterOrEqual,
|
||||||
|
"<=" => LogicOperator::LessOrEqual,
|
||||||
|
_ => {
|
||||||
|
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||||
|
expected: "==, !=, &&, ||, >, <, >= or <=".to_string(),
|
||||||
|
actual: operator_node.kind().to_string(),
|
||||||
|
position: node.range().into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(operator)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
Ok(Type::None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
Ok(Value::none())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for LogicOperator {
|
||||||
|
fn format(&self, output: &mut String, _indent_level: u8) {
|
||||||
|
match self {
|
||||||
|
LogicOperator::Equal => output.push('='),
|
||||||
|
LogicOperator::NotEqual => output.push_str("!="),
|
||||||
|
LogicOperator::And => output.push_str("&&"),
|
||||||
|
LogicOperator::Or => output.push_str("||"),
|
||||||
|
LogicOperator::Greater => output.push('>'),
|
||||||
|
LogicOperator::Less => output.push('<'),
|
||||||
|
LogicOperator::GreaterOrEqual => output.push_str(">="),
|
||||||
|
LogicOperator::LessOrEqual => output.push_str("<="),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for LogicOperator {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
LogicOperator::Equal => write!(f, "="),
|
||||||
|
LogicOperator::NotEqual => write!(f, "!="),
|
||||||
|
LogicOperator::And => write!(f, "&&"),
|
||||||
|
LogicOperator::Or => write!(f, "||"),
|
||||||
|
LogicOperator::Greater => write!(f, ">"),
|
||||||
|
LogicOperator::Less => write!(f, "<"),
|
||||||
|
LogicOperator::GreaterOrEqual => write!(f, ">="),
|
||||||
|
LogicOperator::LessOrEqual => write!(f, "<="),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
117
src/abstract_tree/map_node.rs
Normal file
117
src/abstract_tree/map_node.rs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tree_sitter::Node as SyntaxNode;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Context, Format, Identifier, Map, SourcePosition, Statement, Type,
|
||||||
|
TypeSpecification, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct MapNode {
|
||||||
|
properties: BTreeMap<Identifier, (Statement, Option<Type>)>,
|
||||||
|
position: SourcePosition,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MapNode {
|
||||||
|
pub fn properties(&self) -> &BTreeMap<Identifier, (Statement, Option<Type>)> {
|
||||||
|
&self.properties
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for MapNode {
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("map", node)?;
|
||||||
|
|
||||||
|
let mut properties = BTreeMap::new();
|
||||||
|
let mut current_identifier = None;
|
||||||
|
let mut current_type = None;
|
||||||
|
|
||||||
|
for index in 0..node.child_count() - 1 {
|
||||||
|
let child = node.child(index).unwrap();
|
||||||
|
|
||||||
|
if child.kind() == "identifier" {
|
||||||
|
current_identifier = Some(Identifier::from_syntax(child, source, context)?);
|
||||||
|
current_type = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if child.kind() == "type_specification" {
|
||||||
|
current_type =
|
||||||
|
Some(TypeSpecification::from_syntax(child, source, context)?.take_inner());
|
||||||
|
}
|
||||||
|
|
||||||
|
if child.kind() == "statement" {
|
||||||
|
let statement = Statement::from_syntax(child, source, context)?;
|
||||||
|
|
||||||
|
if let Some(identifier) = ¤t_identifier {
|
||||||
|
properties.insert(identifier.clone(), (statement, current_type.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(MapNode {
|
||||||
|
properties,
|
||||||
|
position: SourcePosition::from(node.range()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
if self.properties.is_empty() {
|
||||||
|
return Ok(Type::Map(None));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut type_map = BTreeMap::new();
|
||||||
|
|
||||||
|
for (identifier, (statement, r#type_option)) in &self.properties {
|
||||||
|
let r#type = if let Some(r#type) = type_option {
|
||||||
|
r#type.clone()
|
||||||
|
} else {
|
||||||
|
statement.expected_type(_context)?
|
||||||
|
};
|
||||||
|
|
||||||
|
type_map.insert(identifier.clone(), r#type);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Type::Map(Some(type_map)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
|
||||||
|
for (_key, (statement, r#type)) in &self.properties {
|
||||||
|
statement.validate(_source, context)?;
|
||||||
|
|
||||||
|
if let Some(expected) = r#type {
|
||||||
|
let actual = statement.expected_type(context)?;
|
||||||
|
|
||||||
|
if !expected.accepts(&actual) {
|
||||||
|
return Err(ValidationError::TypeCheck {
|
||||||
|
expected: expected.clone(),
|
||||||
|
actual,
|
||||||
|
position: self.position.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
let mut map = Map::new();
|
||||||
|
|
||||||
|
for (key, (statement, _)) in &self.properties {
|
||||||
|
let value = statement.run(_source, _context)?;
|
||||||
|
|
||||||
|
map.set(key.clone(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::Map(map))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for MapNode {
|
||||||
|
fn format(&self, _output: &mut String, _indent_level: u8) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
145
src/abstract_tree/match.rs
Normal file
145
src/abstract_tree/match.rs
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
//! Pattern matching.
|
||||||
|
//!
|
||||||
|
//! Note that this module is called "match" but is escaped as "r#match" because
|
||||||
|
//! "match" is a keyword in Rust.
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tree_sitter::Node as SyntaxNode;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Context, Expression, Format, MatchPattern, Statement, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Abstract representation of a match statement.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct Match {
|
||||||
|
matcher: Expression,
|
||||||
|
options: Vec<(MatchPattern, Statement)>,
|
||||||
|
fallback: Option<Box<Statement>>,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
context: Context,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for Match {
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("match", node)?;
|
||||||
|
|
||||||
|
let matcher_node = node.child(1).unwrap();
|
||||||
|
let matcher = Expression::from_syntax(matcher_node, source, context)?;
|
||||||
|
|
||||||
|
let mut options = Vec::new();
|
||||||
|
let mut previous_pattern = None;
|
||||||
|
let mut next_statement_is_fallback = false;
|
||||||
|
let mut fallback = None;
|
||||||
|
|
||||||
|
for index in 2..node.child_count() {
|
||||||
|
let child = node.child(index).unwrap();
|
||||||
|
|
||||||
|
if child.kind() == "match_pattern" {
|
||||||
|
previous_pattern = Some(MatchPattern::from_syntax(child, source, context)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
if child.kind() == "statement" {
|
||||||
|
let statement = Statement::from_syntax(child, source, context)?;
|
||||||
|
|
||||||
|
if next_statement_is_fallback {
|
||||||
|
fallback = Some(Box::new(statement));
|
||||||
|
next_statement_is_fallback = false;
|
||||||
|
} else if let Some(expression) = &previous_pattern {
|
||||||
|
options.push((expression.clone(), statement));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Match {
|
||||||
|
matcher,
|
||||||
|
options,
|
||||||
|
fallback,
|
||||||
|
context: Context::default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
let (_, first_statement) = self.options.first().unwrap();
|
||||||
|
|
||||||
|
first_statement.expected_type(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||||
|
self.matcher.validate(_source, _context)?;
|
||||||
|
|
||||||
|
for (match_pattern, statement) in &self.options {
|
||||||
|
if let MatchPattern::EnumPattern(enum_pattern) = match_pattern {
|
||||||
|
if let Some(identifier) = enum_pattern.inner_identifier() {
|
||||||
|
self.context.set_type(identifier.clone(), Type::Any)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match_pattern.validate(_source, _context)?;
|
||||||
|
statement.validate(_source, &self.context)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(statement) = &self.fallback {
|
||||||
|
statement.validate(_source, _context)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
let matcher_value = self.matcher.run(source, context)?;
|
||||||
|
|
||||||
|
for (pattern, statement) in &self.options {
|
||||||
|
if let (Value::Enum(enum_instance), MatchPattern::EnumPattern(enum_pattern)) =
|
||||||
|
(&matcher_value, pattern)
|
||||||
|
{
|
||||||
|
if enum_instance.name() == enum_pattern.name()
|
||||||
|
&& enum_instance.variant() == enum_pattern.variant()
|
||||||
|
{
|
||||||
|
let statement_context = Context::with_variables_from(context)?;
|
||||||
|
|
||||||
|
if let (Some(identifier), Some(value)) =
|
||||||
|
(enum_pattern.inner_identifier(), enum_instance.value())
|
||||||
|
{
|
||||||
|
statement_context.set_value(identifier.clone(), value.as_ref().clone())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
return statement.run(source, &statement_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let pattern_value = pattern.run(source, context)?;
|
||||||
|
|
||||||
|
if matcher_value == pattern_value {
|
||||||
|
return statement.run(source, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(fallback) = &self.fallback {
|
||||||
|
fallback.run(source, context)
|
||||||
|
} else {
|
||||||
|
Ok(Value::none())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for Match {
|
||||||
|
fn format(&self, output: &mut String, indent_level: u8) {
|
||||||
|
output.push_str("match ");
|
||||||
|
self.matcher.format(output, indent_level);
|
||||||
|
output.push_str(" {");
|
||||||
|
|
||||||
|
for (expression, statement) in &self.options {
|
||||||
|
expression.format(output, indent_level);
|
||||||
|
output.push_str(" => ");
|
||||||
|
statement.format(output, indent_level);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(statement) = &self.fallback {
|
||||||
|
output.push_str("* => ");
|
||||||
|
statement.format(output, indent_level);
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push('}');
|
||||||
|
}
|
||||||
|
}
|
64
src/abstract_tree/match_pattern.rs
Normal file
64
src/abstract_tree/match_pattern.rs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tree_sitter::Node as SyntaxNode;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Context, EnumPattern, Format, Type, Value, ValueNode,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub enum MatchPattern {
|
||||||
|
EnumPattern(EnumPattern),
|
||||||
|
Value(ValueNode),
|
||||||
|
Wildcard,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for MatchPattern {
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("match_pattern", node)?;
|
||||||
|
|
||||||
|
let child = node.child(0).unwrap();
|
||||||
|
let pattern = match child.kind() {
|
||||||
|
"enum_pattern" => {
|
||||||
|
MatchPattern::EnumPattern(EnumPattern::from_syntax(child, source, context)?)
|
||||||
|
}
|
||||||
|
"value" => MatchPattern::Value(ValueNode::from_syntax(child, source, context)?),
|
||||||
|
"*" => MatchPattern::Wildcard,
|
||||||
|
_ => {
|
||||||
|
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||||
|
expected: "enum pattern or value".to_string(),
|
||||||
|
actual: child.kind().to_string(),
|
||||||
|
position: node.range().into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
match self {
|
||||||
|
MatchPattern::EnumPattern(enum_pattern) => enum_pattern.expected_type(_context),
|
||||||
|
MatchPattern::Value(value_node) => value_node.expected_type(_context),
|
||||||
|
MatchPattern::Wildcard => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
match self {
|
||||||
|
MatchPattern::EnumPattern(enum_pattern) => enum_pattern.run(_source, _context),
|
||||||
|
MatchPattern::Value(value_node) => value_node.run(_source, _context),
|
||||||
|
MatchPattern::Wildcard => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for MatchPattern {
|
||||||
|
fn format(&self, _output: &mut String, _indent_level: u8) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
74
src/abstract_tree/math.rs
Normal file
74
src/abstract_tree/math.rs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Context, Expression, Format, MathOperator, SourcePosition, SyntaxNode, Type,
|
||||||
|
Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Abstract representation of a math operation.
|
||||||
|
///
|
||||||
|
/// Dust currently supports the four basic operations and the modulo (or
|
||||||
|
/// remainder) operator.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct Math {
|
||||||
|
left: Expression,
|
||||||
|
operator: MathOperator,
|
||||||
|
right: Expression,
|
||||||
|
position: SourcePosition,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for Math {
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("math", node)?;
|
||||||
|
|
||||||
|
let left_node = node.child(0).unwrap();
|
||||||
|
let left = Expression::from_syntax(left_node, source, context)?;
|
||||||
|
|
||||||
|
let operator_node = node.child(1).unwrap();
|
||||||
|
let operator = MathOperator::from_syntax(operator_node, source, context)?;
|
||||||
|
|
||||||
|
let right_node = node.child(2).unwrap();
|
||||||
|
let right = Expression::from_syntax(right_node, source, context)?;
|
||||||
|
|
||||||
|
Ok(Math {
|
||||||
|
left,
|
||||||
|
operator,
|
||||||
|
right,
|
||||||
|
position: node.range().into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
self.left.expected_type(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||||
|
self.left.validate(_source, _context)?;
|
||||||
|
self.right.validate(_source, _context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
let left = self.left.run(source, context)?;
|
||||||
|
let right = self.right.run(source, context)?;
|
||||||
|
let value = match self.operator {
|
||||||
|
MathOperator::Add => left.add(right, self.position)?,
|
||||||
|
MathOperator::Subtract => left.subtract(right, self.position)?,
|
||||||
|
MathOperator::Multiply => left.multiply(right, self.position)?,
|
||||||
|
MathOperator::Divide => left.divide(right, self.position)?,
|
||||||
|
MathOperator::Modulo => left.modulo(right, self.position)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for Math {
|
||||||
|
fn format(&self, output: &mut String, indent_level: u8) {
|
||||||
|
self.left.format(output, indent_level);
|
||||||
|
output.push(' ');
|
||||||
|
self.operator.format(output, indent_level);
|
||||||
|
output.push(' ');
|
||||||
|
self.right.format(output, indent_level);
|
||||||
|
}
|
||||||
|
}
|
69
src/abstract_tree/math_operator.rs
Normal file
69
src/abstract_tree/math_operator.rs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Context, Format, SyntaxNode, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub enum MathOperator {
|
||||||
|
Add,
|
||||||
|
Subtract,
|
||||||
|
Multiply,
|
||||||
|
Divide,
|
||||||
|
Modulo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for MathOperator {
|
||||||
|
fn from_syntax(
|
||||||
|
node: SyntaxNode,
|
||||||
|
_source: &str,
|
||||||
|
_context: &Context,
|
||||||
|
) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("math_operator", node)?;
|
||||||
|
|
||||||
|
let operator_node = node.child(0).unwrap();
|
||||||
|
let operator = match operator_node.kind() {
|
||||||
|
"+" => MathOperator::Add,
|
||||||
|
"-" => MathOperator::Subtract,
|
||||||
|
"*" => MathOperator::Multiply,
|
||||||
|
"/" => MathOperator::Divide,
|
||||||
|
"%" => MathOperator::Modulo,
|
||||||
|
_ => {
|
||||||
|
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||||
|
expected: "+, -, *, / or %".to_string(),
|
||||||
|
actual: operator_node.kind().to_string(),
|
||||||
|
position: node.range().into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(operator)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
Ok(Type::None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
Ok(Value::none())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for MathOperator {
|
||||||
|
fn format(&self, output: &mut String, _indent_level: u8) {
|
||||||
|
let char = match self {
|
||||||
|
MathOperator::Add => '+',
|
||||||
|
MathOperator::Subtract => '-',
|
||||||
|
MathOperator::Multiply => '*',
|
||||||
|
MathOperator::Divide => '/',
|
||||||
|
MathOperator::Modulo => '%',
|
||||||
|
};
|
||||||
|
|
||||||
|
output.push(char);
|
||||||
|
}
|
||||||
|
}
|
178
src/abstract_tree/mod.rs
Normal file
178
src/abstract_tree/mod.rs
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
//! Abstract, executable representations of corresponding items found in Dust
|
||||||
|
//! source code. The types that implement [AbstractTree] are inteded to be
|
||||||
|
//! created by an [Interpreter].
|
||||||
|
pub mod r#as;
|
||||||
|
pub mod assignment;
|
||||||
|
pub mod assignment_operator;
|
||||||
|
pub mod block;
|
||||||
|
pub mod command;
|
||||||
|
pub mod enum_defintion;
|
||||||
|
pub mod enum_pattern;
|
||||||
|
pub mod expression;
|
||||||
|
pub mod r#for;
|
||||||
|
pub mod function_call;
|
||||||
|
pub mod function_expression;
|
||||||
|
pub mod function_node;
|
||||||
|
pub mod identifier;
|
||||||
|
pub mod if_else;
|
||||||
|
pub mod index;
|
||||||
|
pub mod index_assignment;
|
||||||
|
pub mod index_expression;
|
||||||
|
pub mod logic;
|
||||||
|
pub mod logic_operator;
|
||||||
|
pub mod map_node;
|
||||||
|
pub mod r#match;
|
||||||
|
pub mod match_pattern;
|
||||||
|
pub mod math;
|
||||||
|
pub mod math_operator;
|
||||||
|
pub mod statement;
|
||||||
|
pub mod struct_definition;
|
||||||
|
pub mod r#type;
|
||||||
|
pub mod type_definition;
|
||||||
|
pub mod type_specification;
|
||||||
|
pub mod value_node;
|
||||||
|
pub mod r#while;
|
||||||
|
|
||||||
|
pub use {
|
||||||
|
assignment::*, assignment_operator::*, block::*, command::*, enum_defintion::*,
|
||||||
|
enum_pattern::*, expression::*, function_call::*, function_expression::*, function_node::*,
|
||||||
|
identifier::*, if_else::*, index::*, index_assignment::IndexAssignment, index_expression::*,
|
||||||
|
logic::*, logic_operator::*, map_node::*, match_pattern::*, math::*, math_operator::*, r#as::*,
|
||||||
|
r#for::*, r#match::*, r#type::*, r#while::*, statement::*, struct_definition::*,
|
||||||
|
type_definition::*, type_specification::*, value_node::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
context::Context,
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
SyntaxNode, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A detailed report of a position in the source code string.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
|
pub struct SourcePosition {
|
||||||
|
pub start_byte: usize,
|
||||||
|
pub end_byte: usize,
|
||||||
|
pub start_row: usize,
|
||||||
|
pub start_column: usize,
|
||||||
|
pub end_row: usize,
|
||||||
|
pub end_column: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<tree_sitter::Range> for SourcePosition {
|
||||||
|
fn from(range: tree_sitter::Range) -> Self {
|
||||||
|
SourcePosition {
|
||||||
|
start_byte: range.start_byte,
|
||||||
|
end_byte: range.end_byte,
|
||||||
|
start_row: range.start_point.row + 1,
|
||||||
|
start_column: range.start_point.column,
|
||||||
|
end_row: range.end_point.row + 1,
|
||||||
|
end_column: range.end_point.column,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Abstraction that represents a whole, executable unit of dust code.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
|
pub struct Root {
|
||||||
|
statements: Vec<Statement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Change Root to use tree sitter's cursor to traverse the statements
|
||||||
|
// instead of indexes. This will be more performant when there are a lot of
|
||||||
|
// top-level statements in the tree.
|
||||||
|
impl AbstractTree for Root {
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("root", node)?;
|
||||||
|
|
||||||
|
let statement_count = node.child_count();
|
||||||
|
let mut statements = Vec::with_capacity(statement_count);
|
||||||
|
|
||||||
|
for index in 0..statement_count {
|
||||||
|
let statement_node = node.child(index).unwrap();
|
||||||
|
let statement = Statement::from_syntax(statement_node, source, context)?;
|
||||||
|
|
||||||
|
statements.push(statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Root { statements })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||||
|
for statement in &self.statements {
|
||||||
|
statement.validate(_source, _context)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
let mut value = Value::none();
|
||||||
|
|
||||||
|
for statement in &self.statements {
|
||||||
|
value = statement.run(source, context)?;
|
||||||
|
|
||||||
|
if statement.is_return() {
|
||||||
|
return Ok(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
self.statements.last().unwrap().expected_type(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for Root {
|
||||||
|
fn format(&self, output: &mut String, indent_level: u8) {
|
||||||
|
for (index, statement) in self.statements.iter().enumerate() {
|
||||||
|
if index > 0 {
|
||||||
|
output.push('\n');
|
||||||
|
}
|
||||||
|
statement.format(output, indent_level);
|
||||||
|
output.push('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This trait is implemented by the Evaluator's internal types to form an
|
||||||
|
/// executable tree that resolves to a single value.
|
||||||
|
pub trait AbstractTree: Sized + Format {
|
||||||
|
/// Interpret the syntax tree at the given node and return the abstraction.
|
||||||
|
/// Returns a syntax error if the source is invalid.
|
||||||
|
///
|
||||||
|
/// This function is used to convert nodes in the Tree Sitter concrete
|
||||||
|
/// syntax tree into executable nodes in an abstract tree. This function is
|
||||||
|
/// where the tree should be traversed by accessing sibling and child nodes.
|
||||||
|
/// Each node in the CST should be traversed only once.
|
||||||
|
///
|
||||||
|
/// If necessary, the source code can be accessed directly by getting the
|
||||||
|
/// node's byte range.
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError>;
|
||||||
|
|
||||||
|
/// Return the type of the value that this abstract node will create when
|
||||||
|
/// run. Returns a validation error if the tree is invalid.
|
||||||
|
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError>;
|
||||||
|
|
||||||
|
/// Verify the type integrity of the node. Returns a validation error if the
|
||||||
|
/// tree is invalid.
|
||||||
|
fn validate(&self, source: &str, context: &Context) -> Result<(), ValidationError>;
|
||||||
|
|
||||||
|
/// Execute this node's logic and return a value. Returns a runtime error if
|
||||||
|
/// the node cannot resolve to a value.
|
||||||
|
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Format {
|
||||||
|
fn format(&self, output: &mut String, indent_level: u8);
|
||||||
|
|
||||||
|
fn indent(output: &mut String, indent_level: u8) {
|
||||||
|
for _ in 0..indent_level {
|
||||||
|
output.push_str(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
src/abstract_tree/new.rs
Normal file
45
src/abstract_tree/new.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tree_sitter::Node;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Context, Format, Identifier, Type, TypeSpecification, Value, ValueNode,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct New {
|
||||||
|
identifier: Identifier,
|
||||||
|
properties: Vec<(Identifier, ValueNode, Option<TypeSpecification>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for New {
|
||||||
|
fn from_syntax(node: Node, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
let identifier_node = node.child(1).unwrap();
|
||||||
|
let identifier = Identifier::from_syntax(identifier_node, source, context)?;
|
||||||
|
|
||||||
|
let properties = Vec::new();
|
||||||
|
|
||||||
|
Ok(New {
|
||||||
|
identifier,
|
||||||
|
properties,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for New {
|
||||||
|
fn format(&self, _output: &mut String, _indent_level: u8) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
202
src/abstract_tree/statement.rs
Normal file
202
src/abstract_tree/statement.rs
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Assignment, Block, Context, Expression, For, Format, IfElse, IndexAssignment,
|
||||||
|
Match, SyntaxNode, Type, TypeDefinition, Value, While,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Abstract representation of a statement.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct Statement {
|
||||||
|
is_return: bool,
|
||||||
|
statement_kind: StatementKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Statement {
|
||||||
|
pub fn is_return(&self) -> bool {
|
||||||
|
self.is_return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for Statement {
|
||||||
|
fn from_syntax(
|
||||||
|
node: SyntaxNode,
|
||||||
|
source: &str,
|
||||||
|
_context: &Context,
|
||||||
|
) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("statement", node)?;
|
||||||
|
|
||||||
|
let first_child = node.child(0).unwrap();
|
||||||
|
let mut is_return = first_child.kind() == "return" || first_child.kind() == "break";
|
||||||
|
let child = if is_return {
|
||||||
|
node.child(1).unwrap()
|
||||||
|
} else {
|
||||||
|
first_child
|
||||||
|
};
|
||||||
|
|
||||||
|
let statement_kind = StatementKind::from_syntax(child, source, _context)?;
|
||||||
|
|
||||||
|
if let StatementKind::Block(block) = &statement_kind {
|
||||||
|
if block.contains_return() {
|
||||||
|
is_return = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Statement {
|
||||||
|
is_return,
|
||||||
|
statement_kind,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
self.statement_kind.expected_type(_context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||||
|
self.statement_kind.validate(_source, _context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
self.statement_kind.run(_source, _context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for Statement {
|
||||||
|
fn format(&self, _output: &mut String, _indent_level: u8) {
|
||||||
|
self.statement_kind.format(_output, _indent_level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
enum StatementKind {
|
||||||
|
Assignment(Box<Assignment>),
|
||||||
|
Expression(Expression),
|
||||||
|
IfElse(Box<IfElse>),
|
||||||
|
Match(Match),
|
||||||
|
While(Box<While>),
|
||||||
|
Block(Box<Block>),
|
||||||
|
For(Box<For>),
|
||||||
|
IndexAssignment(Box<IndexAssignment>),
|
||||||
|
TypeDefinition(TypeDefinition),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for StatementKind {
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("statement_kind", node)?;
|
||||||
|
|
||||||
|
let child = node.child(0).unwrap();
|
||||||
|
|
||||||
|
match child.kind() {
|
||||||
|
"assignment" => Ok(StatementKind::Assignment(Box::new(
|
||||||
|
Assignment::from_syntax(child, source, context)?,
|
||||||
|
))),
|
||||||
|
"expression" => Ok(StatementKind::Expression(Expression::from_syntax(
|
||||||
|
child, source, context,
|
||||||
|
)?)),
|
||||||
|
"if_else" => Ok(StatementKind::IfElse(Box::new(IfElse::from_syntax(
|
||||||
|
child, source, context,
|
||||||
|
)?))),
|
||||||
|
"while" => Ok(StatementKind::While(Box::new(While::from_syntax(
|
||||||
|
child, source, context,
|
||||||
|
)?))),
|
||||||
|
"block" => Ok(StatementKind::Block(Box::new(Block::from_syntax(
|
||||||
|
child, source, context,
|
||||||
|
)?))),
|
||||||
|
"for" => Ok(StatementKind::For(Box::new(For::from_syntax(
|
||||||
|
child, source, context,
|
||||||
|
)?))),
|
||||||
|
"index_assignment" => Ok(StatementKind::IndexAssignment(Box::new(
|
||||||
|
IndexAssignment::from_syntax(child, source, context)?,
|
||||||
|
))),
|
||||||
|
"match" => Ok(StatementKind::Match(Match::from_syntax(
|
||||||
|
child, source, context,
|
||||||
|
)?)),
|
||||||
|
"type_definition" => Ok(StatementKind::TypeDefinition(TypeDefinition::from_syntax(
|
||||||
|
child, source, context
|
||||||
|
)?)),
|
||||||
|
_ => Err(SyntaxError::UnexpectedSyntaxNode {
|
||||||
|
expected:
|
||||||
|
"assignment, index assignment, expression, type_definition, block, return, if...else, while, for or match".to_string(),
|
||||||
|
actual: child.kind().to_string(),
|
||||||
|
position: node.range().into(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
match self {
|
||||||
|
StatementKind::Assignment(assignment) => assignment.expected_type(_context),
|
||||||
|
StatementKind::Expression(expression) => expression.expected_type(_context),
|
||||||
|
StatementKind::IfElse(if_else) => if_else.expected_type(_context),
|
||||||
|
StatementKind::Match(r#match) => r#match.expected_type(_context),
|
||||||
|
StatementKind::While(r#while) => r#while.expected_type(_context),
|
||||||
|
StatementKind::Block(block) => block.expected_type(_context),
|
||||||
|
StatementKind::For(r#for) => r#for.expected_type(_context),
|
||||||
|
StatementKind::IndexAssignment(index_assignment) => {
|
||||||
|
index_assignment.expected_type(_context)
|
||||||
|
}
|
||||||
|
StatementKind::TypeDefinition(type_definition) => {
|
||||||
|
type_definition.expected_type(_context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||||
|
match self {
|
||||||
|
StatementKind::Assignment(assignment) => assignment.validate(_source, _context),
|
||||||
|
StatementKind::Expression(expression) => expression.validate(_source, _context),
|
||||||
|
StatementKind::IfElse(if_else) => if_else.validate(_source, _context),
|
||||||
|
StatementKind::Match(r#match) => r#match.validate(_source, _context),
|
||||||
|
StatementKind::While(r#while) => r#while.validate(_source, _context),
|
||||||
|
StatementKind::Block(block) => block.validate(_source, _context),
|
||||||
|
StatementKind::For(r#for) => r#for.validate(_source, _context),
|
||||||
|
StatementKind::IndexAssignment(index_assignment) => {
|
||||||
|
index_assignment.validate(_source, _context)
|
||||||
|
}
|
||||||
|
StatementKind::TypeDefinition(type_definition) => {
|
||||||
|
type_definition.validate(_source, _context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
match self {
|
||||||
|
StatementKind::Assignment(assignment) => assignment.run(_source, _context),
|
||||||
|
StatementKind::Expression(expression) => expression.run(_source, _context),
|
||||||
|
StatementKind::IfElse(if_else) => if_else.run(_source, _context),
|
||||||
|
StatementKind::Match(r#match) => r#match.run(_source, _context),
|
||||||
|
StatementKind::While(r#while) => r#while.run(_source, _context),
|
||||||
|
StatementKind::Block(block) => block.run(_source, _context),
|
||||||
|
StatementKind::For(r#for) => r#for.run(_source, _context),
|
||||||
|
StatementKind::IndexAssignment(index_assignment) => {
|
||||||
|
index_assignment.run(_source, _context)
|
||||||
|
}
|
||||||
|
StatementKind::TypeDefinition(type_definition) => {
|
||||||
|
type_definition.run(_source, _context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for StatementKind {
|
||||||
|
fn format(&self, output: &mut String, indent_level: u8) {
|
||||||
|
StatementKind::indent(output, indent_level);
|
||||||
|
|
||||||
|
match self {
|
||||||
|
StatementKind::Assignment(assignment) => assignment.format(output, indent_level),
|
||||||
|
StatementKind::Expression(expression) => expression.format(output, indent_level),
|
||||||
|
StatementKind::IfElse(if_else) => if_else.format(output, indent_level),
|
||||||
|
StatementKind::Match(r#match) => r#match.format(output, indent_level),
|
||||||
|
StatementKind::While(r#while) => r#while.format(output, indent_level),
|
||||||
|
StatementKind::Block(block) => block.format(output, indent_level),
|
||||||
|
StatementKind::For(r#for) => r#for.format(output, indent_level),
|
||||||
|
StatementKind::IndexAssignment(index_assignment) => {
|
||||||
|
index_assignment.format(output, indent_level)
|
||||||
|
}
|
||||||
|
StatementKind::TypeDefinition(type_definition) => {
|
||||||
|
type_definition.format(output, indent_level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
120
src/abstract_tree/struct_definition.rs
Normal file
120
src/abstract_tree/struct_definition.rs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tree_sitter::Node as SyntaxNode;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Context, Format, Identifier, Map, MapNode, Statement, StructInstance, Type,
|
||||||
|
TypeDefinition, TypeSpecification, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct StructDefinition {
|
||||||
|
name: Identifier,
|
||||||
|
properties: BTreeMap<Identifier, (Option<Statement>, Type)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StructDefinition {
|
||||||
|
pub fn instantiate(
|
||||||
|
&self,
|
||||||
|
new_properties: &MapNode,
|
||||||
|
source: &str,
|
||||||
|
context: &Context,
|
||||||
|
) -> Result<StructInstance, RuntimeError> {
|
||||||
|
let mut all_properties = Map::new();
|
||||||
|
|
||||||
|
for (key, (statement_option, _)) in &self.properties {
|
||||||
|
if let Some(statement) = statement_option {
|
||||||
|
let value = statement.run(source, context)?;
|
||||||
|
|
||||||
|
all_properties.set(key.clone(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (key, (statement, _)) in new_properties.properties() {
|
||||||
|
let value = statement.run(source, context)?;
|
||||||
|
|
||||||
|
all_properties.set(key.clone(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(StructInstance::new(self.name.clone(), all_properties))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for StructDefinition {
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("struct_definition", node)?;
|
||||||
|
|
||||||
|
let name_node = node.child(1).unwrap();
|
||||||
|
let name = Identifier::from_syntax(name_node, source, context)?;
|
||||||
|
|
||||||
|
let mut properties = BTreeMap::new();
|
||||||
|
let mut current_identifier: Option<Identifier> = None;
|
||||||
|
let mut current_type: Option<Type> = None;
|
||||||
|
let mut current_statement = None;
|
||||||
|
|
||||||
|
for index in 2..node.child_count() - 1 {
|
||||||
|
let child_syntax_node = node.child(index).unwrap();
|
||||||
|
|
||||||
|
if child_syntax_node.kind() == "identifier" {
|
||||||
|
if current_statement.is_none() {
|
||||||
|
if let (Some(identifier), Some(r#type)) = (¤t_identifier, ¤t_type) {
|
||||||
|
properties.insert(identifier.clone(), (None, r#type.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
current_type = None;
|
||||||
|
current_identifier =
|
||||||
|
Some(Identifier::from_syntax(child_syntax_node, source, context)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
if child_syntax_node.kind() == "type_specification" {
|
||||||
|
current_type = Some(
|
||||||
|
TypeSpecification::from_syntax(child_syntax_node, source, context)?
|
||||||
|
.take_inner(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if child_syntax_node.kind() == "statement" {
|
||||||
|
current_statement =
|
||||||
|
Some(Statement::from_syntax(child_syntax_node, source, context)?);
|
||||||
|
|
||||||
|
if let Some(identifier) = ¤t_identifier {
|
||||||
|
let r#type = if let Some(r#type) = ¤t_type {
|
||||||
|
r#type.clone()
|
||||||
|
} else {
|
||||||
|
Type::None
|
||||||
|
};
|
||||||
|
|
||||||
|
properties.insert(
|
||||||
|
identifier.clone(),
|
||||||
|
(current_statement.clone(), r#type.clone()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(StructDefinition { name, properties })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
Ok(Type::None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
context.set_definition(self.name.clone(), TypeDefinition::Struct(self.clone()))?;
|
||||||
|
|
||||||
|
Ok(Value::none())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for StructDefinition {
|
||||||
|
fn format(&self, _output: &mut String, _indent_level: u8) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
394
src/abstract_tree/type.rs
Normal file
394
src/abstract_tree/type.rs
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
|
fmt::{self, Display, Formatter},
|
||||||
|
};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tree_sitter::Node as SyntaxNode;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
built_in_types::BuiltInType,
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Context, Format, Identifier, TypeSpecification, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub enum Type {
|
||||||
|
Any,
|
||||||
|
Boolean,
|
||||||
|
Collection,
|
||||||
|
Custom {
|
||||||
|
name: Identifier,
|
||||||
|
arguments: Vec<Type>,
|
||||||
|
},
|
||||||
|
Float,
|
||||||
|
Function {
|
||||||
|
parameter_types: Vec<Type>,
|
||||||
|
return_type: Box<Type>,
|
||||||
|
},
|
||||||
|
Integer,
|
||||||
|
List,
|
||||||
|
ListOf(Box<Type>),
|
||||||
|
ListExact(Vec<Type>),
|
||||||
|
Map(Option<BTreeMap<Identifier, Type>>),
|
||||||
|
None,
|
||||||
|
Number,
|
||||||
|
String,
|
||||||
|
Range,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Type {
|
||||||
|
pub fn custom(name: Identifier, arguments: Vec<Type>) -> Self {
|
||||||
|
Type::Custom { name, arguments }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn option(inner_type: Option<Type>) -> Self {
|
||||||
|
BuiltInType::Option(inner_type).get().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list(item_type: Type) -> Self {
|
||||||
|
Type::ListOf(Box::new(item_type))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn function(parameter_types: Vec<Type>, return_type: Type) -> Self {
|
||||||
|
Type::Function {
|
||||||
|
parameter_types,
|
||||||
|
return_type: Box::new(return_type),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a boolean indicating whether is type is accepting of the other.
|
||||||
|
///
|
||||||
|
/// The types do not need to match exactly. For example, the Any variant matches all of the
|
||||||
|
/// others and the Number variant accepts Number, Integer and Float.
|
||||||
|
pub fn accepts(&self, other: &Type) -> bool {
|
||||||
|
log::info!("Checking type {self} against {other}.");
|
||||||
|
|
||||||
|
match (self, other) {
|
||||||
|
(Type::Any, _)
|
||||||
|
| (_, Type::Any)
|
||||||
|
| (Type::Boolean, Type::Boolean)
|
||||||
|
| (Type::Collection, Type::Collection)
|
||||||
|
| (Type::Collection, Type::String)
|
||||||
|
| (Type::Collection, Type::List)
|
||||||
|
| (Type::List, Type::Collection)
|
||||||
|
| (Type::Collection, Type::ListExact(_))
|
||||||
|
| (Type::ListExact(_), Type::Collection)
|
||||||
|
| (Type::Collection, Type::ListOf(_))
|
||||||
|
| (Type::ListOf(_), Type::Collection)
|
||||||
|
| (Type::Collection, Type::Map(_))
|
||||||
|
| (Type::Map(_), Type::Collection)
|
||||||
|
| (Type::String, Type::Collection)
|
||||||
|
| (Type::Float, Type::Float)
|
||||||
|
| (Type::Integer, Type::Integer)
|
||||||
|
| (Type::List, Type::List)
|
||||||
|
| (Type::Map(None), Type::Map(None))
|
||||||
|
| (Type::Number, Type::Number)
|
||||||
|
| (Type::Number, Type::Integer)
|
||||||
|
| (Type::Number, Type::Float)
|
||||||
|
| (Type::Integer, Type::Number)
|
||||||
|
| (Type::Float, Type::Number)
|
||||||
|
| (Type::String, Type::String)
|
||||||
|
| (Type::None, Type::None) => true,
|
||||||
|
(Type::Map(left_types), Type::Map(right_types)) => left_types == right_types,
|
||||||
|
(
|
||||||
|
Type::Custom {
|
||||||
|
name: left_name,
|
||||||
|
arguments: left_arguments,
|
||||||
|
},
|
||||||
|
Type::Custom {
|
||||||
|
name: right_name,
|
||||||
|
arguments: right_arguments,
|
||||||
|
},
|
||||||
|
) => left_name == right_name && left_arguments == right_arguments,
|
||||||
|
(Type::ListOf(self_item_type), Type::ListOf(other_item_type)) => {
|
||||||
|
self_item_type.accepts(&other_item_type)
|
||||||
|
}
|
||||||
|
(Type::ListExact(self_types), Type::ListExact(other_types)) => {
|
||||||
|
for (left, right) in self_types.iter().zip(other_types.iter()) {
|
||||||
|
if !left.accepts(right) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
(Type::ListExact(exact_types), Type::ListOf(of_type))
|
||||||
|
| (Type::ListOf(of_type), Type::ListExact(exact_types)) => {
|
||||||
|
exact_types.iter().all(|r#type| r#type == of_type.as_ref())
|
||||||
|
}
|
||||||
|
(
|
||||||
|
Type::Function {
|
||||||
|
parameter_types: self_parameter_types,
|
||||||
|
return_type: self_return_type,
|
||||||
|
},
|
||||||
|
Type::Function {
|
||||||
|
parameter_types: other_parameter_types,
|
||||||
|
return_type: other_return_type,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
let parameter_type_pairs = self_parameter_types
|
||||||
|
.iter()
|
||||||
|
.zip(other_parameter_types.iter());
|
||||||
|
|
||||||
|
for (self_parameter_type, other_parameter_type) in parameter_type_pairs {
|
||||||
|
if self_parameter_type == other_parameter_type {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self_return_type == other_return_type
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_function(&self) -> bool {
|
||||||
|
matches!(self, Type::Function { .. })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_list(&self) -> bool {
|
||||||
|
matches!(self, Type::ListOf(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_map(&self) -> bool {
|
||||||
|
matches!(self, Type::Map(_))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for Type {
|
||||||
|
fn from_syntax(
|
||||||
|
node: SyntaxNode,
|
||||||
|
_source: &str,
|
||||||
|
context: &Context,
|
||||||
|
) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("type", node)?;
|
||||||
|
|
||||||
|
let type_node = node.child(0).unwrap();
|
||||||
|
|
||||||
|
let r#type = match type_node.kind() {
|
||||||
|
"identifier" => {
|
||||||
|
let name = Identifier::from_syntax(type_node, _source, context)?;
|
||||||
|
let mut arguments = Vec::new();
|
||||||
|
|
||||||
|
for index in 2..node.child_count() - 1 {
|
||||||
|
let child = node.child(index).unwrap();
|
||||||
|
|
||||||
|
if child.is_named() {
|
||||||
|
let r#type = Type::from_syntax(child, _source, context)?;
|
||||||
|
|
||||||
|
arguments.push(r#type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Type::custom(name, arguments)
|
||||||
|
}
|
||||||
|
"{" => {
|
||||||
|
let mut type_map = BTreeMap::new();
|
||||||
|
let mut previous_identifier = None;
|
||||||
|
|
||||||
|
for index in 1..node.child_count() - 1 {
|
||||||
|
let child = node.child(index).unwrap();
|
||||||
|
|
||||||
|
if let Some(identifier) = previous_identifier {
|
||||||
|
let type_specification =
|
||||||
|
TypeSpecification::from_syntax(child, _source, context)?;
|
||||||
|
|
||||||
|
type_map.insert(identifier, type_specification.take_inner());
|
||||||
|
previous_identifier = None;
|
||||||
|
} else {
|
||||||
|
previous_identifier =
|
||||||
|
Some(Identifier::from_syntax(child, _source, context)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Type::Map(Some(type_map))
|
||||||
|
}
|
||||||
|
"[" => {
|
||||||
|
let item_type_node = node.child(1).unwrap();
|
||||||
|
let item_type = Type::from_syntax(item_type_node, _source, context)?;
|
||||||
|
|
||||||
|
Type::ListOf(Box::new(item_type))
|
||||||
|
}
|
||||||
|
"list" => {
|
||||||
|
let item_type_node = node.child(1);
|
||||||
|
|
||||||
|
if let Some(child) = item_type_node {
|
||||||
|
Type::ListOf(Box::new(Type::from_syntax(child, _source, context)?))
|
||||||
|
} else {
|
||||||
|
Type::List
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"any" => Type::Any,
|
||||||
|
"bool" => Type::Boolean,
|
||||||
|
"collection" => Type::Collection,
|
||||||
|
"float" => Type::Float,
|
||||||
|
"(" => {
|
||||||
|
let child_count = node.child_count();
|
||||||
|
let mut parameter_types = Vec::new();
|
||||||
|
|
||||||
|
for index in 1..child_count - 2 {
|
||||||
|
let child = node.child(index).unwrap();
|
||||||
|
|
||||||
|
if child.is_named() {
|
||||||
|
let parameter_type = Type::from_syntax(child, _source, context)?;
|
||||||
|
|
||||||
|
parameter_types.push(parameter_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let final_node = node.child(child_count - 1).unwrap();
|
||||||
|
let return_type = if final_node.is_named() {
|
||||||
|
Type::from_syntax(final_node, _source, context)?
|
||||||
|
} else {
|
||||||
|
Type::option(None)
|
||||||
|
};
|
||||||
|
|
||||||
|
Type::Function {
|
||||||
|
parameter_types,
|
||||||
|
return_type: Box::new(return_type),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"int" => Type::Integer,
|
||||||
|
"map" => Type::Map(None),
|
||||||
|
"num" => Type::Number,
|
||||||
|
"none" => Type::None,
|
||||||
|
"str" => Type::String,
|
||||||
|
_ => {
|
||||||
|
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||||
|
expected: "any, bool, float, int, num, str, list, map, custom type, (, [ or {"
|
||||||
|
.to_string(),
|
||||||
|
actual: type_node.kind().to_string(),
|
||||||
|
position: node.range().into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(r#type)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
Ok(Type::None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
Ok(Value::none())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for Type {
|
||||||
|
fn format(&self, output: &mut String, indent_level: u8) {
|
||||||
|
match self {
|
||||||
|
Type::Any => output.push_str("any"),
|
||||||
|
Type::Boolean => output.push_str("bool"),
|
||||||
|
Type::Collection => output.push_str("collection"),
|
||||||
|
Type::Custom {
|
||||||
|
name: _,
|
||||||
|
arguments: _,
|
||||||
|
} => todo!(),
|
||||||
|
Type::Float => output.push_str("float"),
|
||||||
|
Type::Function {
|
||||||
|
parameter_types,
|
||||||
|
return_type,
|
||||||
|
} => {
|
||||||
|
output.push('(');
|
||||||
|
|
||||||
|
for (index, parameter_type) in parameter_types.iter().enumerate() {
|
||||||
|
parameter_type.format(output, indent_level);
|
||||||
|
|
||||||
|
if index != parameter_types.len() - 1 {
|
||||||
|
output.push(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push_str(") -> ");
|
||||||
|
return_type.format(output, indent_level);
|
||||||
|
}
|
||||||
|
Type::Integer => output.push_str("int"),
|
||||||
|
Type::List => todo!(),
|
||||||
|
Type::ListOf(item_type) => {
|
||||||
|
output.push('[');
|
||||||
|
item_type.format(output, indent_level);
|
||||||
|
output.push(']');
|
||||||
|
}
|
||||||
|
Type::ListExact(_) => todo!(),
|
||||||
|
Type::Map(_) => {
|
||||||
|
output.push_str("map");
|
||||||
|
}
|
||||||
|
Type::None => output.push_str("Option::None"),
|
||||||
|
Type::Number => output.push_str("num"),
|
||||||
|
Type::String => output.push_str("str"),
|
||||||
|
Type::Range => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Type {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Type::Any => write!(f, "any"),
|
||||||
|
Type::Boolean => write!(f, "bool"),
|
||||||
|
Type::Collection => write!(f, "collection"),
|
||||||
|
Type::Custom { name, arguments } => {
|
||||||
|
if !arguments.is_empty() {
|
||||||
|
write!(f, "<")?;
|
||||||
|
|
||||||
|
for (index, r#type) in arguments.into_iter().enumerate() {
|
||||||
|
if index == arguments.len() - 1 {
|
||||||
|
write!(f, "{}", r#type)?;
|
||||||
|
} else {
|
||||||
|
write!(f, "{}, ", r#type)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(f, ">")
|
||||||
|
} else {
|
||||||
|
write!(f, "{name}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Type::Float => write!(f, "float"),
|
||||||
|
Type::Function {
|
||||||
|
parameter_types,
|
||||||
|
return_type,
|
||||||
|
} => {
|
||||||
|
write!(f, "(")?;
|
||||||
|
|
||||||
|
for (index, parameter_type) in parameter_types.iter().enumerate() {
|
||||||
|
write!(f, "{parameter_type}")?;
|
||||||
|
|
||||||
|
if index != parameter_types.len() - 1 {
|
||||||
|
write!(f, " ")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(f, ")")?;
|
||||||
|
write!(f, " -> {return_type}")
|
||||||
|
}
|
||||||
|
Type::Integer => write!(f, "int"),
|
||||||
|
Type::List => write!(f, "list"),
|
||||||
|
Type::ListOf(item_type) => write!(f, "[{item_type}]"),
|
||||||
|
Type::ListExact(types) => {
|
||||||
|
write!(f, "[")?;
|
||||||
|
|
||||||
|
for (index, r#type) in types.into_iter().enumerate() {
|
||||||
|
if index == types.len() - 1 {
|
||||||
|
write!(f, "{}", r#type)?;
|
||||||
|
} else {
|
||||||
|
write!(f, "{}, ", r#type)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(f, "]")
|
||||||
|
}
|
||||||
|
Type::Map(_) => write!(f, "map"),
|
||||||
|
Type::Number => write!(f, "num"),
|
||||||
|
Type::None => write!(f, "none"),
|
||||||
|
Type::String => write!(f, "str"),
|
||||||
|
Type::Range => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
73
src/abstract_tree/type_definition.rs
Normal file
73
src/abstract_tree/type_definition.rs
Normal 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!()
|
||||||
|
}
|
||||||
|
}
|
56
src/abstract_tree/type_specification.rs
Normal file
56
src/abstract_tree/type_specification.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Context, Format, SyntaxNode, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct TypeSpecification {
|
||||||
|
r#type: Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeSpecification {
|
||||||
|
pub fn new(r#type: Type) -> Self {
|
||||||
|
Self { r#type }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner(&self) -> &Type {
|
||||||
|
&self.r#type
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn take_inner(self) -> Type {
|
||||||
|
self.r#type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for TypeSpecification {
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("type_specification", node)?;
|
||||||
|
|
||||||
|
let type_node = node.child(1).unwrap();
|
||||||
|
let r#type = Type::from_syntax(type_node, source, context)?;
|
||||||
|
|
||||||
|
Ok(TypeSpecification { r#type })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
self.r#type.expected_type(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
self.r#type.run(source, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for TypeSpecification {
|
||||||
|
fn format(&self, output: &mut String, indent_level: u8) {
|
||||||
|
output.push('<');
|
||||||
|
self.r#type.format(output, indent_level);
|
||||||
|
output.push('>');
|
||||||
|
}
|
||||||
|
}
|
357
src/abstract_tree/value_node.rs
Normal file
357
src/abstract_tree/value_node.rs
Normal file
@ -0,0 +1,357 @@
|
|||||||
|
use std::{cmp::Ordering, ops::RangeInclusive};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tree_sitter::Node as SyntaxNode;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Context, Expression, Format, Function, FunctionNode,
|
||||||
|
Identifier, List, Type, Value, TypeDefinition, MapNode,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
|
pub enum ValueNode {
|
||||||
|
Boolean(String),
|
||||||
|
Float(String),
|
||||||
|
Function(Function),
|
||||||
|
Integer(String),
|
||||||
|
String(String),
|
||||||
|
List(Vec<Expression>),
|
||||||
|
Map(MapNode),
|
||||||
|
Range(RangeInclusive<i64>),
|
||||||
|
Struct {
|
||||||
|
name: Identifier,
|
||||||
|
properties: MapNode,
|
||||||
|
},
|
||||||
|
Enum {
|
||||||
|
name: Identifier,
|
||||||
|
variant: Identifier,
|
||||||
|
expression: Option<Box<Expression>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for ValueNode {
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("value", node)?;
|
||||||
|
|
||||||
|
let child = node.child(0).unwrap();
|
||||||
|
let value_node = match child.kind() {
|
||||||
|
"boolean" => ValueNode::Boolean(source[child.byte_range()].to_string()),
|
||||||
|
"float" => ValueNode::Float(source[child.byte_range()].to_string()),
|
||||||
|
"function" => {
|
||||||
|
let function_node = FunctionNode::from_syntax(child, source, context)?;
|
||||||
|
|
||||||
|
ValueNode::Function(Function::ContextDefined(function_node))
|
||||||
|
}
|
||||||
|
"integer" => ValueNode::Integer(source[child.byte_range()].to_string()),
|
||||||
|
"string" => {
|
||||||
|
let without_quotes = child.start_byte() + 1..child.end_byte() - 1;
|
||||||
|
|
||||||
|
ValueNode::String(source[without_quotes].to_string())
|
||||||
|
}
|
||||||
|
"list" => {
|
||||||
|
let mut expressions = Vec::new();
|
||||||
|
|
||||||
|
for index in 1..child.child_count() - 1 {
|
||||||
|
let current_node = child.child(index).unwrap();
|
||||||
|
|
||||||
|
if current_node.is_named() {
|
||||||
|
let expression = Expression::from_syntax(current_node, source, context)?;
|
||||||
|
|
||||||
|
expressions.push(expression);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ValueNode::List(expressions)
|
||||||
|
}
|
||||||
|
"map" => {
|
||||||
|
ValueNode::Map(MapNode::from_syntax(child, source, context)?)
|
||||||
|
}
|
||||||
|
"range" => {
|
||||||
|
let mut split = source[child.byte_range()].split("..");
|
||||||
|
let start = split.next().unwrap().parse().unwrap();
|
||||||
|
let end = split.next().unwrap().parse().unwrap();
|
||||||
|
|
||||||
|
ValueNode::Range(start..=end)
|
||||||
|
}
|
||||||
|
"enum_instance" => {
|
||||||
|
let name_node = child.child(0).unwrap();
|
||||||
|
let name = Identifier::from_syntax(name_node, source, context)?;
|
||||||
|
|
||||||
|
let variant_node = child.child(2).unwrap();
|
||||||
|
let variant = Identifier::from_syntax(variant_node, source, context)?;
|
||||||
|
|
||||||
|
let expression = if let Some(expression_node) = child.child(4) {
|
||||||
|
Some(Box::new(Expression::from_syntax(expression_node, source, context)?))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
ValueNode::Enum { name, variant , expression }
|
||||||
|
}
|
||||||
|
"struct_instance" => {
|
||||||
|
let name_node = child.child(0).unwrap();
|
||||||
|
let name = Identifier::from_syntax(name_node, source, context)?;
|
||||||
|
|
||||||
|
let properties_node = child.child(2).unwrap();
|
||||||
|
let properties = MapNode::from_syntax(properties_node, source, context)?;
|
||||||
|
|
||||||
|
ValueNode::Struct
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
properties
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||||
|
expected:
|
||||||
|
"string, integer, float, boolean, range, list, map, option, function, struct or enum"
|
||||||
|
.to_string(),
|
||||||
|
actual: child.kind().to_string(),
|
||||||
|
position: node.range().into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(value_node)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
let r#type = match self {
|
||||||
|
ValueNode::Boolean(_) => Type::Boolean,
|
||||||
|
ValueNode::Float(_) => Type::Float,
|
||||||
|
ValueNode::Function(function) => function.r#type(),
|
||||||
|
ValueNode::Integer(_) => Type::Integer,
|
||||||
|
ValueNode::String(_) => Type::String,
|
||||||
|
ValueNode::List(expressions) => {
|
||||||
|
let mut item_types = Vec::new();
|
||||||
|
|
||||||
|
for expression in expressions {
|
||||||
|
let expression_type = expression.expected_type(context)?;
|
||||||
|
|
||||||
|
item_types.push(expression_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
Type::ListExact(item_types)
|
||||||
|
}
|
||||||
|
ValueNode::Map(map_node) => map_node.expected_type(context)?,
|
||||||
|
ValueNode::Struct { name, .. } => {
|
||||||
|
Type::custom(name.clone(), Vec::with_capacity(0))
|
||||||
|
}
|
||||||
|
ValueNode::Range(_) => Type::Range,
|
||||||
|
ValueNode::Enum { name, variant, expression: _ } => {
|
||||||
|
let types: Vec<Type> = if let Some(type_definition) = context.get_definition(name)? {
|
||||||
|
if let TypeDefinition::Enum(enum_definition) = type_definition {
|
||||||
|
let types = enum_definition.variants().into_iter().find_map(|(identifier, types)| {
|
||||||
|
if identifier == variant {
|
||||||
|
Some(types.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(types) = types {
|
||||||
|
types
|
||||||
|
} else {
|
||||||
|
return Err(ValidationError::VariableIdentifierNotFound(variant.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return Err(ValidationError::ExpectedEnumDefintion { actual: type_definition.clone() });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(ValidationError::VariableIdentifierNotFound(name.clone()));
|
||||||
|
};
|
||||||
|
|
||||||
|
Type::custom(name.clone(), types.clone())
|
||||||
|
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(r#type)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
|
||||||
|
match self {
|
||||||
|
ValueNode::Function(function) => {
|
||||||
|
if let Function::ContextDefined(function_node) = function {
|
||||||
|
function_node.validate(_source, context)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ValueNode::Map(map_node) => map_node.validate(_source, context)?,
|
||||||
|
ValueNode::Enum { name, expression, .. } => {
|
||||||
|
name.validate(_source, context)?;
|
||||||
|
|
||||||
|
if let Some(expression) = expression {
|
||||||
|
expression.validate(_source, context)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
let value = match self {
|
||||||
|
ValueNode::Boolean(value_source) => Value::Boolean(value_source.parse().unwrap()),
|
||||||
|
ValueNode::Float(value_source) => {
|
||||||
|
let float = value_source.parse()?;
|
||||||
|
|
||||||
|
Value::Float(float)
|
||||||
|
}
|
||||||
|
ValueNode::Function(function) => Value::Function(function.clone()),
|
||||||
|
ValueNode::Integer(value_source) => Value::Integer(value_source.parse().unwrap()),
|
||||||
|
ValueNode::String(value_source) => Value::string(value_source.clone()),
|
||||||
|
ValueNode::List(expressions) => {
|
||||||
|
let mut values = Vec::with_capacity(expressions.len());
|
||||||
|
|
||||||
|
for node in expressions {
|
||||||
|
let value = node.run(source, context)?;
|
||||||
|
|
||||||
|
values.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::List(List::with_items(values))
|
||||||
|
}
|
||||||
|
ValueNode::Map(map_node) => map_node.run(source, context)?,
|
||||||
|
ValueNode::Range(range) => Value::Range(range.clone()),
|
||||||
|
ValueNode::Struct { name, properties } => {
|
||||||
|
let instance = if let Some(definition) = context.get_definition(name)? {
|
||||||
|
if let TypeDefinition::Struct(struct_definition) = definition {
|
||||||
|
struct_definition.instantiate(properties, source, context)?
|
||||||
|
} else {
|
||||||
|
return Err(RuntimeError::ValidationFailure(ValidationError::ExpectedStructDefintion { actual: definition.clone() }))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(RuntimeError::ValidationFailure(
|
||||||
|
ValidationError::TypeDefinitionNotFound(name.clone())
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
Value::Struct(instance)
|
||||||
|
|
||||||
|
}
|
||||||
|
ValueNode::Enum { name, variant, expression } => {
|
||||||
|
let value = if let Some(expression) = expression {
|
||||||
|
expression.run(source, context)?
|
||||||
|
} else {
|
||||||
|
Value::none()
|
||||||
|
};
|
||||||
|
let instance = if let Some(definition) = context.get_definition(name)? {
|
||||||
|
if let TypeDefinition::Enum(enum_defintion) = definition {
|
||||||
|
enum_defintion.instantiate(variant.clone(), Some(value))
|
||||||
|
} else {
|
||||||
|
return Err(RuntimeError::ValidationFailure(
|
||||||
|
ValidationError::ExpectedEnumDefintion {
|
||||||
|
actual: definition.clone()
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(RuntimeError::ValidationFailure(
|
||||||
|
ValidationError::TypeDefinitionNotFound(name.clone())
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
Value::Enum(instance)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for ValueNode {
|
||||||
|
fn format(&self, output: &mut String, indent_level: u8) {
|
||||||
|
match self {
|
||||||
|
ValueNode::Boolean(source) | ValueNode::Float(source) | ValueNode::Integer(source) => {
|
||||||
|
output.push_str(source)
|
||||||
|
}
|
||||||
|
ValueNode::String(source) => {
|
||||||
|
output.push('\'');
|
||||||
|
output.push_str(source);
|
||||||
|
output.push('\'');
|
||||||
|
}
|
||||||
|
ValueNode::Function(function) => function.format(output, indent_level),
|
||||||
|
ValueNode::List(expressions) => {
|
||||||
|
output.push('[');
|
||||||
|
|
||||||
|
for expression in expressions {
|
||||||
|
expression.format(output, indent_level);
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push(']');
|
||||||
|
}
|
||||||
|
ValueNode::Map(map_node) => map_node.format(output, indent_level),
|
||||||
|
ValueNode::Struct { name, properties } => {
|
||||||
|
name.format(output, indent_level);
|
||||||
|
properties.format(output, indent_level);
|
||||||
|
}
|
||||||
|
ValueNode::Range(_) => todo!(),
|
||||||
|
ValueNode::Enum { .. } => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for ValueNode {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
match (self, other) {
|
||||||
|
(ValueNode::Boolean(left), ValueNode::Boolean(right)) => left.cmp(right),
|
||||||
|
(ValueNode::Boolean(_), _) => Ordering::Greater,
|
||||||
|
(ValueNode::Float(left), ValueNode::Float(right)) => left.cmp(right),
|
||||||
|
(ValueNode::Float(_), _) => Ordering::Greater,
|
||||||
|
(ValueNode::Function(left), ValueNode::Function(right)) => left.cmp(right),
|
||||||
|
(ValueNode::Function(_), _) => Ordering::Greater,
|
||||||
|
(ValueNode::Integer(left), ValueNode::Integer(right)) => left.cmp(right),
|
||||||
|
(ValueNode::Integer(_), _) => Ordering::Greater,
|
||||||
|
(ValueNode::String(left), ValueNode::String(right)) => left.cmp(right),
|
||||||
|
(ValueNode::String(_), _) => Ordering::Greater,
|
||||||
|
(ValueNode::List(left), ValueNode::List(right)) => left.cmp(right),
|
||||||
|
(ValueNode::List(_), _) => Ordering::Greater,
|
||||||
|
(ValueNode::Map(left), ValueNode::Map(right)) => left.cmp(right),
|
||||||
|
(ValueNode::Map(_), _) => Ordering::Greater,
|
||||||
|
(ValueNode::Struct{ name: left_name, properties: left_properties }, ValueNode::Struct {name: right_name, properties: right_properties} ) => {
|
||||||
|
let name_cmp = left_name.cmp(right_name);
|
||||||
|
|
||||||
|
if name_cmp.is_eq() {
|
||||||
|
left_properties.cmp(right_properties)
|
||||||
|
} else {
|
||||||
|
name_cmp
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(ValueNode::Struct {..}, _) => Ordering::Greater,
|
||||||
|
(
|
||||||
|
ValueNode::Enum {
|
||||||
|
name: left_name, variant: left_variant, expression: left_expression
|
||||||
|
},
|
||||||
|
ValueNode::Enum {
|
||||||
|
name: right_name, variant: right_variant, expression: right_expression
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
let name_cmp = left_name.cmp(right_name);
|
||||||
|
|
||||||
|
if name_cmp.is_eq() {
|
||||||
|
let variant_cmp = left_variant.cmp(right_variant);
|
||||||
|
|
||||||
|
if variant_cmp.is_eq() {
|
||||||
|
left_expression.cmp(right_expression)
|
||||||
|
} else {
|
||||||
|
variant_cmp
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
name_cmp
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(ValueNode::Enum { .. }, _) => Ordering::Greater,
|
||||||
|
(ValueNode::Range(left), ValueNode::Range(right)) => left.clone().cmp(right.clone()),
|
||||||
|
(ValueNode::Range(_), _) => Ordering::Less,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for ValueNode {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
64
src/abstract_tree/while.rs
Normal file
64
src/abstract_tree/while.rs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, SyntaxError, ValidationError},
|
||||||
|
AbstractTree, Block, Context, Expression, Format, SyntaxNode, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Abstract representation of a while loop.
|
||||||
|
///
|
||||||
|
/// While executes its block repeatedly until its expression evaluates to true.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct While {
|
||||||
|
expression: Expression,
|
||||||
|
block: Block,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractTree for While {
|
||||||
|
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("while", node)?;
|
||||||
|
|
||||||
|
let expression_node = node.child(1).unwrap();
|
||||||
|
let expression = Expression::from_syntax(expression_node, source, context)?;
|
||||||
|
|
||||||
|
let block_node = node.child(2).unwrap();
|
||||||
|
let block = Block::from_syntax(block_node, source, context)?;
|
||||||
|
|
||||||
|
Ok(While { expression, block })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
|
||||||
|
self.block.expected_type(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
|
||||||
|
log::info!("VALIDATE while loop");
|
||||||
|
|
||||||
|
self.expression.validate(_source, context)?;
|
||||||
|
self.block.validate(_source, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
log::info!("RUN while loop start");
|
||||||
|
|
||||||
|
while self.expression.run(source, context)?.as_boolean()? {
|
||||||
|
self.block.run(source, context)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("RUN while loop end");
|
||||||
|
|
||||||
|
Ok(Value::none())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for While {
|
||||||
|
fn format(&self, output: &mut String, indent_level: u8) {
|
||||||
|
output.push('\n');
|
||||||
|
While::indent(output, indent_level);
|
||||||
|
output.push_str("while ");
|
||||||
|
self.expression.format(output, indent_level);
|
||||||
|
output.push(' ');
|
||||||
|
self.block.format(output, indent_level);
|
||||||
|
output.push('\n');
|
||||||
|
}
|
||||||
|
}
|
55
src/built_in_functions/fs.rs
Normal file
55
src/built_in_functions/fs.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
use std::fs::read_to_string;
|
||||||
|
|
||||||
|
use enum_iterator::{all, Sequence};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{error::RuntimeError, Context, Type, Value};
|
||||||
|
|
||||||
|
use super::Callable;
|
||||||
|
|
||||||
|
pub fn fs_functions() -> impl Iterator<Item = Fs> {
|
||||||
|
all()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Sequence, Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum Fs {
|
||||||
|
ReadFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Callable for Fs {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Fs::ReadFile => "read_file",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Fs::ReadFile => "Read the contents of a file to a string.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn r#type(&self) -> Type {
|
||||||
|
match self {
|
||||||
|
Fs::ReadFile => Type::function(vec![Type::String], Type::String),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(
|
||||||
|
&self,
|
||||||
|
arguments: &[Value],
|
||||||
|
_source: &str,
|
||||||
|
_outer_context: &Context,
|
||||||
|
) -> Result<Value, RuntimeError> {
|
||||||
|
match self {
|
||||||
|
Fs::ReadFile => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||||
|
|
||||||
|
let path = arguments.first().unwrap().as_string()?;
|
||||||
|
let file_content = read_to_string(path.as_str())?;
|
||||||
|
|
||||||
|
Ok(Value::string(file_content))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
src/built_in_functions/json.rs
Normal file
77
src/built_in_functions/json.rs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
use enum_iterator::Sequence;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{error::RuntimeError, Context, Type, Value};
|
||||||
|
|
||||||
|
use super::Callable;
|
||||||
|
|
||||||
|
pub fn json_functions() -> impl Iterator<Item = Json> {
|
||||||
|
enum_iterator::all()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Sequence, Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum Json {
|
||||||
|
Create,
|
||||||
|
CreatePretty,
|
||||||
|
Parse,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Callable for Json {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Json::Create => "create",
|
||||||
|
Json::CreatePretty => "create_pretty",
|
||||||
|
Json::Parse => "parse",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Json::Create => "Convert a value to a JSON string.",
|
||||||
|
Json::CreatePretty => "Convert a value to a formatted JSON string.",
|
||||||
|
Json::Parse => "Convert JSON to a value",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn r#type(&self) -> Type {
|
||||||
|
match self {
|
||||||
|
Json::Create => Type::function(vec![Type::Any], Type::String),
|
||||||
|
Json::CreatePretty => Type::function(vec![Type::Any], Type::String),
|
||||||
|
Json::Parse => Type::function(vec![Type::String], Type::Any),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(
|
||||||
|
&self,
|
||||||
|
arguments: &[Value],
|
||||||
|
_source: &str,
|
||||||
|
_outer_context: &Context,
|
||||||
|
) -> Result<Value, RuntimeError> {
|
||||||
|
match self {
|
||||||
|
Json::Create => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||||
|
|
||||||
|
let value = arguments.first().unwrap();
|
||||||
|
let json_string = serde_json::to_string(value)?;
|
||||||
|
|
||||||
|
Ok(Value::String(json_string))
|
||||||
|
}
|
||||||
|
Json::CreatePretty => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||||
|
|
||||||
|
let value = arguments.first().unwrap();
|
||||||
|
let json_string = serde_json::to_string_pretty(value)?;
|
||||||
|
|
||||||
|
Ok(Value::String(json_string))
|
||||||
|
}
|
||||||
|
Json::Parse => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||||
|
|
||||||
|
let json_string = arguments.first().unwrap().as_string()?;
|
||||||
|
let value = serde_json::from_str(json_string)?;
|
||||||
|
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
198
src/built_in_functions/mod.rs
Normal file
198
src/built_in_functions/mod.rs
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
pub mod fs;
|
||||||
|
pub mod json;
|
||||||
|
pub mod str;
|
||||||
|
|
||||||
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
|
||||||
|
use rand::{random, thread_rng, Rng};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{RuntimeError, ValidationError},
|
||||||
|
Context, EnumInstance, Format, Identifier, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
use self::{fs::Fs, json::Json, str::StrFunction};
|
||||||
|
|
||||||
|
pub trait Callable {
|
||||||
|
fn name(&self) -> &'static str;
|
||||||
|
fn description(&self) -> &'static str;
|
||||||
|
fn r#type(&self) -> Type;
|
||||||
|
fn call(
|
||||||
|
&self,
|
||||||
|
arguments: &[Value],
|
||||||
|
source: &str,
|
||||||
|
context: &Context,
|
||||||
|
) -> Result<Value, RuntimeError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum BuiltInFunction {
|
||||||
|
AssertEqual,
|
||||||
|
Fs(Fs),
|
||||||
|
Json(Json),
|
||||||
|
Length,
|
||||||
|
Output,
|
||||||
|
RandomBoolean,
|
||||||
|
RandomFloat,
|
||||||
|
RandomFrom,
|
||||||
|
RandomInteger,
|
||||||
|
String(StrFunction),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Callable for BuiltInFunction {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
BuiltInFunction::AssertEqual => "assert_equal",
|
||||||
|
BuiltInFunction::Fs(fs_function) => fs_function.name(),
|
||||||
|
BuiltInFunction::Json(json_function) => json_function.name(),
|
||||||
|
BuiltInFunction::Length => "length",
|
||||||
|
BuiltInFunction::Output => "output",
|
||||||
|
BuiltInFunction::RandomBoolean => "boolean",
|
||||||
|
BuiltInFunction::RandomFloat => "float",
|
||||||
|
BuiltInFunction::RandomFrom => "from",
|
||||||
|
BuiltInFunction::RandomInteger => "integer",
|
||||||
|
BuiltInFunction::String(string_function) => string_function.name(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
BuiltInFunction::AssertEqual => "assert_equal",
|
||||||
|
BuiltInFunction::Fs(fs_function) => fs_function.description(),
|
||||||
|
BuiltInFunction::Json(json_function) => json_function.description(),
|
||||||
|
BuiltInFunction::Length => "length",
|
||||||
|
BuiltInFunction::Output => "output",
|
||||||
|
BuiltInFunction::RandomBoolean => "boolean",
|
||||||
|
BuiltInFunction::RandomFloat => "float",
|
||||||
|
BuiltInFunction::RandomFrom => "from",
|
||||||
|
BuiltInFunction::RandomInteger => "integer",
|
||||||
|
BuiltInFunction::String(string_function) => string_function.description(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn r#type(&self) -> Type {
|
||||||
|
match self {
|
||||||
|
BuiltInFunction::AssertEqual => Type::function(vec![Type::Any, Type::Any], Type::None),
|
||||||
|
BuiltInFunction::Fs(fs_function) => fs_function.r#type(),
|
||||||
|
BuiltInFunction::Json(json_function) => json_function.r#type(),
|
||||||
|
BuiltInFunction::Length => Type::function(vec![Type::Collection], Type::Integer),
|
||||||
|
BuiltInFunction::Output => Type::function(vec![Type::Any], Type::None),
|
||||||
|
BuiltInFunction::RandomBoolean => Type::function(vec![], Type::Boolean),
|
||||||
|
BuiltInFunction::RandomFloat => Type::function(vec![], Type::Float),
|
||||||
|
BuiltInFunction::RandomFrom => Type::function(vec![Type::Collection], Type::Any),
|
||||||
|
BuiltInFunction::RandomInteger => Type::function(vec![], Type::Integer),
|
||||||
|
BuiltInFunction::String(string_function) => string_function.r#type(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(
|
||||||
|
&self,
|
||||||
|
arguments: &[Value],
|
||||||
|
_source: &str,
|
||||||
|
context: &Context,
|
||||||
|
) -> Result<Value, RuntimeError> {
|
||||||
|
match self {
|
||||||
|
BuiltInFunction::AssertEqual => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||||
|
|
||||||
|
let left = arguments.get(0).unwrap();
|
||||||
|
let right = arguments.get(1).unwrap();
|
||||||
|
|
||||||
|
if left == right {
|
||||||
|
Ok(Value::Enum(EnumInstance::new(
|
||||||
|
Identifier::new("Result"),
|
||||||
|
Identifier::new("Ok"),
|
||||||
|
Some(Value::none()),
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Err(RuntimeError::AssertEqualFailed {
|
||||||
|
left: left.clone(),
|
||||||
|
right: right.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BuiltInFunction::Fs(fs_function) => fs_function.call(arguments, _source, context),
|
||||||
|
BuiltInFunction::Json(json_function) => json_function.call(arguments, _source, context),
|
||||||
|
BuiltInFunction::Length => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||||
|
|
||||||
|
let value = arguments.first().unwrap();
|
||||||
|
let length = if let Ok(list) = value.as_list() {
|
||||||
|
list.items()?.len()
|
||||||
|
} else if let Ok(map) = value.as_map() {
|
||||||
|
map.inner().len()
|
||||||
|
} else if let Ok(str) = value.as_string() {
|
||||||
|
str.chars().count()
|
||||||
|
} else {
|
||||||
|
return Err(RuntimeError::ValidationFailure(
|
||||||
|
ValidationError::ExpectedCollection {
|
||||||
|
actual: value.clone(),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Value::Integer(length as i64))
|
||||||
|
}
|
||||||
|
BuiltInFunction::Output => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||||
|
|
||||||
|
let value = arguments.first().unwrap();
|
||||||
|
|
||||||
|
println!("{value}");
|
||||||
|
|
||||||
|
Ok(Value::none())
|
||||||
|
}
|
||||||
|
BuiltInFunction::RandomBoolean => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 0, arguments.len())?;
|
||||||
|
|
||||||
|
Ok(Value::Boolean(random()))
|
||||||
|
}
|
||||||
|
BuiltInFunction::RandomFloat => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 0, arguments.len())?;
|
||||||
|
|
||||||
|
Ok(Value::Float(random()))
|
||||||
|
}
|
||||||
|
BuiltInFunction::RandomFrom => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||||
|
|
||||||
|
let value = arguments.first().unwrap();
|
||||||
|
|
||||||
|
if let Ok(list) = value.as_list() {
|
||||||
|
let items = list.items()?;
|
||||||
|
|
||||||
|
if items.len() == 0 {
|
||||||
|
Ok(Value::none())
|
||||||
|
} else {
|
||||||
|
let random_index = thread_rng().gen_range(0..items.len());
|
||||||
|
let random_value = items.get(random_index).cloned().unwrap_or_default();
|
||||||
|
|
||||||
|
Ok(random_value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BuiltInFunction::RandomInteger => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 0, arguments.len())?;
|
||||||
|
|
||||||
|
Ok(Value::Integer(random()))
|
||||||
|
}
|
||||||
|
BuiltInFunction::String(string_function) => {
|
||||||
|
string_function.call(arguments, _source, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format for BuiltInFunction {
|
||||||
|
fn format(&self, output: &mut String, _indent_level: u8) {
|
||||||
|
output.push_str(self.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for BuiltInFunction {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.name())
|
||||||
|
}
|
||||||
|
}
|
591
src/built_in_functions/str.rs
Normal file
591
src/built_in_functions/str.rs
Normal file
@ -0,0 +1,591 @@
|
|||||||
|
use enum_iterator::Sequence;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{error::RuntimeError, Context, EnumInstance, Identifier, List, Type, Value};
|
||||||
|
|
||||||
|
use super::Callable;
|
||||||
|
|
||||||
|
pub fn string_functions() -> impl Iterator<Item = StrFunction> {
|
||||||
|
enum_iterator::all()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Sequence, Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum StrFunction {
|
||||||
|
AsBytes,
|
||||||
|
EndsWith,
|
||||||
|
Find,
|
||||||
|
Insert,
|
||||||
|
IsAscii,
|
||||||
|
IsEmpty,
|
||||||
|
Lines,
|
||||||
|
Matches,
|
||||||
|
Parse,
|
||||||
|
Remove,
|
||||||
|
ReplaceRange,
|
||||||
|
Retain,
|
||||||
|
Split,
|
||||||
|
SplitAt,
|
||||||
|
SplitInclusive,
|
||||||
|
SplitN,
|
||||||
|
SplitOnce,
|
||||||
|
SplitTerminator,
|
||||||
|
SplitWhitespace,
|
||||||
|
StartsWith,
|
||||||
|
StripPrefix,
|
||||||
|
ToLowercase,
|
||||||
|
ToUppercase,
|
||||||
|
Trim,
|
||||||
|
TrimEnd,
|
||||||
|
TrimEndMatches,
|
||||||
|
TrimMatches,
|
||||||
|
TrimStart,
|
||||||
|
TrimStartMatches,
|
||||||
|
Truncate,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Callable for StrFunction {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
StrFunction::AsBytes => "as_bytes",
|
||||||
|
StrFunction::EndsWith => "ends_with",
|
||||||
|
StrFunction::Find => "find",
|
||||||
|
StrFunction::Insert => "insert",
|
||||||
|
StrFunction::IsAscii => "is_ascii",
|
||||||
|
StrFunction::IsEmpty => "is_empty",
|
||||||
|
StrFunction::Lines => "lines",
|
||||||
|
StrFunction::Matches => "matches",
|
||||||
|
StrFunction::Parse => "parse",
|
||||||
|
StrFunction::Remove => "remove",
|
||||||
|
StrFunction::ReplaceRange => "replace_range",
|
||||||
|
StrFunction::Retain => "retain",
|
||||||
|
StrFunction::Split => "split",
|
||||||
|
StrFunction::SplitAt => "split_at",
|
||||||
|
StrFunction::SplitInclusive => "split_inclusive",
|
||||||
|
StrFunction::SplitN => "split_n",
|
||||||
|
StrFunction::SplitOnce => "split_once",
|
||||||
|
StrFunction::SplitTerminator => "split_terminator",
|
||||||
|
StrFunction::SplitWhitespace => "split_whitespace",
|
||||||
|
StrFunction::StartsWith => "starts_with",
|
||||||
|
StrFunction::StripPrefix => "strip_prefix",
|
||||||
|
StrFunction::ToLowercase => "to_lowercase",
|
||||||
|
StrFunction::ToUppercase => "to_uppercase",
|
||||||
|
StrFunction::Trim => "trim",
|
||||||
|
StrFunction::TrimEnd => "trim_end",
|
||||||
|
StrFunction::TrimEndMatches => "trim_end_matches",
|
||||||
|
StrFunction::TrimMatches => "trim_matches",
|
||||||
|
StrFunction::TrimStart => "trim_start",
|
||||||
|
StrFunction::TrimStartMatches => "trim_start_matches",
|
||||||
|
StrFunction::Truncate => "truncate",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
StrFunction::AsBytes => "TODO",
|
||||||
|
StrFunction::EndsWith => "TODO",
|
||||||
|
StrFunction::Find => "TODO",
|
||||||
|
StrFunction::Insert => "TODO",
|
||||||
|
StrFunction::IsAscii => "TODO",
|
||||||
|
StrFunction::IsEmpty => "TODO",
|
||||||
|
StrFunction::Lines => "TODO",
|
||||||
|
StrFunction::Matches => "TODO",
|
||||||
|
StrFunction::Parse => "TODO",
|
||||||
|
StrFunction::Remove => "TODO",
|
||||||
|
StrFunction::ReplaceRange => "TODO",
|
||||||
|
StrFunction::Retain => "TODO",
|
||||||
|
StrFunction::Split => "TODO",
|
||||||
|
StrFunction::SplitAt => "TODO",
|
||||||
|
StrFunction::SplitInclusive => "TODO",
|
||||||
|
StrFunction::SplitN => "TODO",
|
||||||
|
StrFunction::SplitOnce => "TODO",
|
||||||
|
StrFunction::SplitTerminator => "TODO",
|
||||||
|
StrFunction::SplitWhitespace => "TODO",
|
||||||
|
StrFunction::StartsWith => "TODO",
|
||||||
|
StrFunction::StripPrefix => "TODO",
|
||||||
|
StrFunction::ToLowercase => "TODO",
|
||||||
|
StrFunction::ToUppercase => "TODO",
|
||||||
|
StrFunction::Trim => "TODO",
|
||||||
|
StrFunction::TrimEnd => "TODO",
|
||||||
|
StrFunction::TrimEndMatches => "TODO",
|
||||||
|
StrFunction::TrimMatches => "TODO",
|
||||||
|
StrFunction::TrimStart => "TODO",
|
||||||
|
StrFunction::TrimStartMatches => "TODO",
|
||||||
|
StrFunction::Truncate => "TODO",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn r#type(&self) -> Type {
|
||||||
|
match self {
|
||||||
|
StrFunction::AsBytes => Type::function(vec![Type::String], Type::list(Type::Integer)),
|
||||||
|
StrFunction::EndsWith => {
|
||||||
|
Type::function(vec![Type::String, Type::String], Type::Boolean)
|
||||||
|
}
|
||||||
|
StrFunction::Find => Type::function(
|
||||||
|
vec![Type::String, Type::String],
|
||||||
|
Type::option(Some(Type::Integer)),
|
||||||
|
),
|
||||||
|
StrFunction::Insert => Type::function(
|
||||||
|
vec![Type::String, Type::Integer, Type::String],
|
||||||
|
Type::String,
|
||||||
|
),
|
||||||
|
StrFunction::IsAscii => Type::function(vec![Type::String], Type::Boolean),
|
||||||
|
StrFunction::IsEmpty => Type::function(vec![Type::String], Type::Boolean),
|
||||||
|
StrFunction::Lines => Type::function(vec![Type::String], Type::list(Type::String)),
|
||||||
|
StrFunction::Matches => {
|
||||||
|
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
|
||||||
|
}
|
||||||
|
StrFunction::Parse => Type::function(vec![Type::String], Type::Any),
|
||||||
|
StrFunction::Remove => Type::function(
|
||||||
|
vec![Type::String, Type::Integer],
|
||||||
|
Type::option(Some(Type::String)),
|
||||||
|
),
|
||||||
|
StrFunction::ReplaceRange => Type::function(
|
||||||
|
vec![Type::String, Type::list(Type::Integer), Type::String],
|
||||||
|
Type::String,
|
||||||
|
),
|
||||||
|
StrFunction::Retain => Type::function(
|
||||||
|
vec![
|
||||||
|
Type::String,
|
||||||
|
Type::function(vec![Type::String], Type::Boolean),
|
||||||
|
],
|
||||||
|
Type::String,
|
||||||
|
),
|
||||||
|
StrFunction::Split => {
|
||||||
|
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
|
||||||
|
}
|
||||||
|
StrFunction::SplitAt => {
|
||||||
|
Type::function(vec![Type::String, Type::Integer], Type::list(Type::String))
|
||||||
|
}
|
||||||
|
StrFunction::SplitInclusive => {
|
||||||
|
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
|
||||||
|
}
|
||||||
|
StrFunction::SplitN => Type::function(
|
||||||
|
vec![Type::String, Type::Integer, Type::String],
|
||||||
|
Type::list(Type::String),
|
||||||
|
),
|
||||||
|
StrFunction::SplitOnce => {
|
||||||
|
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
|
||||||
|
}
|
||||||
|
StrFunction::SplitTerminator => {
|
||||||
|
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
|
||||||
|
}
|
||||||
|
StrFunction::SplitWhitespace => {
|
||||||
|
Type::function(vec![Type::String], Type::list(Type::String))
|
||||||
|
}
|
||||||
|
StrFunction::StartsWith => {
|
||||||
|
Type::function(vec![Type::String, Type::String], Type::Boolean)
|
||||||
|
}
|
||||||
|
StrFunction::StripPrefix => Type::function(
|
||||||
|
vec![Type::String, Type::String],
|
||||||
|
Type::option(Some(Type::String)),
|
||||||
|
),
|
||||||
|
StrFunction::ToLowercase => Type::function(vec![Type::String], Type::String),
|
||||||
|
StrFunction::ToUppercase => Type::function(vec![Type::String], Type::String),
|
||||||
|
StrFunction::Truncate => {
|
||||||
|
Type::function(vec![Type::String, Type::Integer], Type::String)
|
||||||
|
}
|
||||||
|
StrFunction::Trim => Type::function(vec![Type::String], Type::String),
|
||||||
|
StrFunction::TrimEnd => Type::function(vec![Type::String], Type::String),
|
||||||
|
StrFunction::TrimEndMatches => {
|
||||||
|
Type::function(vec![Type::String, Type::String], Type::String)
|
||||||
|
}
|
||||||
|
StrFunction::TrimMatches => {
|
||||||
|
Type::function(vec![Type::String, Type::String], Type::String)
|
||||||
|
}
|
||||||
|
StrFunction::TrimStart => Type::function(vec![Type::String], Type::String),
|
||||||
|
StrFunction::TrimStartMatches => {
|
||||||
|
Type::function(vec![Type::String, Type::String], Type::String)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(
|
||||||
|
&self,
|
||||||
|
arguments: &[Value],
|
||||||
|
_source: &str,
|
||||||
|
_context: &Context,
|
||||||
|
) -> Result<Value, RuntimeError> {
|
||||||
|
let value = match self {
|
||||||
|
StrFunction::AsBytes => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
let bytes = string
|
||||||
|
.bytes()
|
||||||
|
.map(|byte| Value::Integer(byte as i64))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Value::List(List::with_items(bytes))
|
||||||
|
}
|
||||||
|
StrFunction::EndsWith => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
let pattern_string = arguments.get(1).unwrap().as_string()?;
|
||||||
|
let pattern = pattern_string.as_str();
|
||||||
|
|
||||||
|
Value::Boolean(string.ends_with(pattern))
|
||||||
|
}
|
||||||
|
StrFunction::Find => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
let pattern_string = arguments.get(1).unwrap().as_string()?;
|
||||||
|
let pattern = pattern_string.as_str();
|
||||||
|
let find = string
|
||||||
|
.find(pattern)
|
||||||
|
.map(|index| Value::Integer(index as i64));
|
||||||
|
|
||||||
|
if let Some(index) = find {
|
||||||
|
Value::Enum(EnumInstance::new(
|
||||||
|
Identifier::new("Option"),
|
||||||
|
Identifier::new("Some"),
|
||||||
|
Some(index),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Value::Enum(EnumInstance::new(
|
||||||
|
Identifier::new("Option"),
|
||||||
|
Identifier::new("None"),
|
||||||
|
Some(Value::none()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StrFunction::IsAscii => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
|
||||||
|
Value::Boolean(string.is_ascii())
|
||||||
|
}
|
||||||
|
StrFunction::IsEmpty => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
|
||||||
|
Value::Boolean(string.is_empty())
|
||||||
|
}
|
||||||
|
StrFunction::Insert => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 3, arguments.len())?;
|
||||||
|
|
||||||
|
let mut string = arguments.first().unwrap().as_string()?.clone();
|
||||||
|
let index = arguments.get(1).unwrap().as_integer()? as usize;
|
||||||
|
let insertion = arguments.get(2).unwrap().as_string()?;
|
||||||
|
|
||||||
|
string.insert_str(index, insertion);
|
||||||
|
|
||||||
|
Value::String(string)
|
||||||
|
}
|
||||||
|
StrFunction::Lines => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
let lines = string
|
||||||
|
.lines()
|
||||||
|
.map(|line| Value::string(line.to_string()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Value::List(List::with_items(lines))
|
||||||
|
}
|
||||||
|
StrFunction::Matches => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
let pattern_string = arguments.get(1).unwrap().as_string()?;
|
||||||
|
let pattern = pattern_string.as_str();
|
||||||
|
let matches = string
|
||||||
|
.matches(pattern)
|
||||||
|
.map(|pattern| Value::string(pattern.to_string()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Value::List(List::with_items(matches))
|
||||||
|
}
|
||||||
|
StrFunction::Parse => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
|
||||||
|
if let Ok(integer) = string.parse::<i64>() {
|
||||||
|
Value::Integer(integer)
|
||||||
|
} else if let Ok(float) = string.parse::<f64>() {
|
||||||
|
Value::Float(float)
|
||||||
|
} else {
|
||||||
|
Value::none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StrFunction::Remove => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
let index = arguments.get(1).unwrap().as_integer()? as usize;
|
||||||
|
let chars = string.chars().collect::<Vec<char>>();
|
||||||
|
|
||||||
|
if index < chars.len() {
|
||||||
|
let new_string = chars
|
||||||
|
.iter()
|
||||||
|
.map(|char| char.to_string())
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
Value::some(Value::string(new_string))
|
||||||
|
} else {
|
||||||
|
Value::none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StrFunction::ReplaceRange => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 3, arguments.len())?;
|
||||||
|
|
||||||
|
let mut string = arguments.first().unwrap().as_string()?.clone();
|
||||||
|
let range = arguments.get(1).unwrap().as_list()?.items()?;
|
||||||
|
let start = range[0].as_integer()? as usize;
|
||||||
|
let end = range[1].as_integer()? as usize;
|
||||||
|
let pattern = arguments.get(2).unwrap().as_string()?;
|
||||||
|
|
||||||
|
string.replace_range(start..end, pattern);
|
||||||
|
|
||||||
|
Value::String(string)
|
||||||
|
}
|
||||||
|
StrFunction::Retain => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||||
|
|
||||||
|
todo!();
|
||||||
|
|
||||||
|
// let mut string = arguments.first().unwrap().as_string()?.clone();
|
||||||
|
// let predicate = arguments.get(1).unwrap().as_function()?;
|
||||||
|
|
||||||
|
// string.retain(|char| {
|
||||||
|
// predicate
|
||||||
|
// .call(&[Value::string(char)], _source, _outer_context)
|
||||||
|
// .unwrap()
|
||||||
|
// .as_boolean()
|
||||||
|
// .unwrap()
|
||||||
|
// });
|
||||||
|
|
||||||
|
// Value::String(string)
|
||||||
|
}
|
||||||
|
StrFunction::Split => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
let pattern_string = arguments.get(1).unwrap().as_string()?;
|
||||||
|
let pattern = pattern_string.as_str();
|
||||||
|
let sections = string
|
||||||
|
.split(pattern)
|
||||||
|
.map(|section| Value::string(section.to_string()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Value::List(List::with_items(sections))
|
||||||
|
}
|
||||||
|
StrFunction::SplitAt => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
let index = arguments.get(1).unwrap().as_integer()?;
|
||||||
|
let (left, right) = string.split_at(index as usize);
|
||||||
|
|
||||||
|
Value::List(List::with_items(vec![
|
||||||
|
Value::string(left.to_string()),
|
||||||
|
Value::string(right.to_string()),
|
||||||
|
]))
|
||||||
|
}
|
||||||
|
StrFunction::SplitInclusive => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
let pattern_string = arguments.get(1).unwrap().as_string()?;
|
||||||
|
let pattern = pattern_string.as_str();
|
||||||
|
let sections = string
|
||||||
|
.split(pattern)
|
||||||
|
.map(|pattern| Value::string(pattern.to_string()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Value::List(List::with_items(sections))
|
||||||
|
}
|
||||||
|
StrFunction::SplitN => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 3, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
let count = arguments.get(1).unwrap().as_integer()?;
|
||||||
|
let pattern_string = arguments.get(2).unwrap().as_string()?;
|
||||||
|
let pattern = pattern_string.as_str();
|
||||||
|
let sections = string
|
||||||
|
.splitn(count as usize, pattern)
|
||||||
|
.map(|pattern| Value::string(pattern.to_string()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Value::List(List::with_items(sections))
|
||||||
|
}
|
||||||
|
StrFunction::SplitOnce => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
let pattern_string = arguments.get(1).unwrap().as_string()?;
|
||||||
|
let pattern = pattern_string.as_str();
|
||||||
|
let sections = string.split_once(pattern).map(|(left, right)| {
|
||||||
|
Value::List(List::with_items(vec![
|
||||||
|
Value::string(left.to_string()),
|
||||||
|
Value::string(right.to_string()),
|
||||||
|
]))
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(sections) = sections {
|
||||||
|
Value::some(sections)
|
||||||
|
} else {
|
||||||
|
Value::none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StrFunction::SplitTerminator => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
let pattern_string = arguments.get(1).unwrap().as_string()?;
|
||||||
|
let pattern = pattern_string.as_str();
|
||||||
|
let sections = string
|
||||||
|
.split_terminator(pattern)
|
||||||
|
.map(|section| Value::string(section.to_string()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Value::List(List::with_items(sections))
|
||||||
|
}
|
||||||
|
StrFunction::SplitWhitespace => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
let sections = string
|
||||||
|
.split_whitespace()
|
||||||
|
.map(|section| Value::string(section.to_string()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Value::List(List::with_items(sections))
|
||||||
|
}
|
||||||
|
StrFunction::StartsWith => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
let pattern_string = arguments.get(1).unwrap().as_string()?;
|
||||||
|
let pattern = pattern_string.as_str();
|
||||||
|
|
||||||
|
Value::Boolean(string.starts_with(pattern))
|
||||||
|
}
|
||||||
|
StrFunction::StripPrefix => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
let prefix_string = arguments.get(1).unwrap().as_string()?;
|
||||||
|
let prefix = prefix_string.as_str();
|
||||||
|
let stripped = string
|
||||||
|
.strip_prefix(prefix)
|
||||||
|
.map(|remainder| Value::string(remainder.to_string()));
|
||||||
|
|
||||||
|
if let Some(value) = stripped {
|
||||||
|
Value::Enum(EnumInstance::new(
|
||||||
|
Identifier::new("Option"),
|
||||||
|
Identifier::new("Some"),
|
||||||
|
Some(value),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Value::none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StrFunction::ToLowercase => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
let lowercase = string.to_lowercase();
|
||||||
|
|
||||||
|
Value::string(lowercase)
|
||||||
|
}
|
||||||
|
StrFunction::ToUppercase => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
let uppercase = string.to_uppercase();
|
||||||
|
|
||||||
|
Value::string(uppercase)
|
||||||
|
}
|
||||||
|
StrFunction::Trim => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||||
|
|
||||||
|
let trimmed = arguments.first().unwrap().as_string()?.trim().to_string();
|
||||||
|
|
||||||
|
Value::string(trimmed)
|
||||||
|
}
|
||||||
|
StrFunction::TrimEnd => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||||
|
|
||||||
|
let trimmed = arguments
|
||||||
|
.first()
|
||||||
|
.unwrap()
|
||||||
|
.as_string()?
|
||||||
|
.trim_end()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
Value::string(trimmed)
|
||||||
|
}
|
||||||
|
StrFunction::TrimEndMatches => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
let pattern_string = arguments.get(1).unwrap().as_string()?;
|
||||||
|
let pattern = pattern_string.as_str();
|
||||||
|
let trimmed = string.trim_end_matches(pattern).to_string();
|
||||||
|
|
||||||
|
Value::string(trimmed)
|
||||||
|
}
|
||||||
|
StrFunction::TrimMatches => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
let pattern = arguments
|
||||||
|
.get(1)
|
||||||
|
.unwrap()
|
||||||
|
.as_string()?
|
||||||
|
.chars()
|
||||||
|
.collect::<Vec<char>>();
|
||||||
|
let trimmed = string.trim_matches(pattern.as_slice()).to_string();
|
||||||
|
|
||||||
|
Value::string(trimmed)
|
||||||
|
}
|
||||||
|
StrFunction::TrimStart => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||||
|
|
||||||
|
let trimmed = arguments
|
||||||
|
.first()
|
||||||
|
.unwrap()
|
||||||
|
.as_string()?
|
||||||
|
.trim_start()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
Value::string(trimmed)
|
||||||
|
}
|
||||||
|
StrFunction::TrimStartMatches => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||||
|
|
||||||
|
let string = arguments.first().unwrap().as_string()?;
|
||||||
|
let pattern = arguments
|
||||||
|
.get(1)
|
||||||
|
.unwrap()
|
||||||
|
.as_string()?
|
||||||
|
.chars()
|
||||||
|
.collect::<Vec<char>>();
|
||||||
|
let trimmed = string.trim_start_matches(pattern.as_slice()).to_string();
|
||||||
|
|
||||||
|
Value::string(trimmed)
|
||||||
|
}
|
||||||
|
StrFunction::Truncate => {
|
||||||
|
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||||
|
|
||||||
|
let input_string = arguments.first().unwrap().as_string()?;
|
||||||
|
let new_length = arguments.get(1).unwrap().as_integer()? as usize;
|
||||||
|
|
||||||
|
let new_string = input_string
|
||||||
|
.chars()
|
||||||
|
.take(new_length)
|
||||||
|
.map(|char| char.to_string())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Value::String(new_string)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
}
|
51
src/built_in_identifiers.rs
Normal file
51
src/built_in_identifiers.rs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
use std::sync::{Arc, OnceLock};
|
||||||
|
|
||||||
|
use enum_iterator::{all, Sequence};
|
||||||
|
|
||||||
|
use crate::Identifier;
|
||||||
|
|
||||||
|
pub fn all_built_in_identifiers() -> impl Iterator<Item = BuiltInIdentifier> {
|
||||||
|
all()
|
||||||
|
}
|
||||||
|
|
||||||
|
static OPTION: OnceLock<Identifier> = OnceLock::new();
|
||||||
|
static NONE: OnceLock<Identifier> = OnceLock::new();
|
||||||
|
static SOME: OnceLock<Identifier> = OnceLock::new();
|
||||||
|
static RESULT: OnceLock<Identifier> = OnceLock::new();
|
||||||
|
static OK: OnceLock<Identifier> = OnceLock::new();
|
||||||
|
static ERROR: OnceLock<Identifier> = OnceLock::new();
|
||||||
|
|
||||||
|
#[derive(Sequence, Debug)]
|
||||||
|
pub enum BuiltInIdentifier {
|
||||||
|
Option,
|
||||||
|
None,
|
||||||
|
Some,
|
||||||
|
Result,
|
||||||
|
Ok,
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BuiltInIdentifier {
|
||||||
|
pub fn get(&self) -> &Identifier {
|
||||||
|
match self {
|
||||||
|
BuiltInIdentifier::Option => {
|
||||||
|
OPTION.get_or_init(|| Identifier::from_raw_parts(Arc::new("Option".to_string())))
|
||||||
|
}
|
||||||
|
BuiltInIdentifier::None => {
|
||||||
|
NONE.get_or_init(|| Identifier::from_raw_parts(Arc::new("None".to_string())))
|
||||||
|
}
|
||||||
|
BuiltInIdentifier::Some => {
|
||||||
|
SOME.get_or_init(|| Identifier::from_raw_parts(Arc::new("Some".to_string())))
|
||||||
|
}
|
||||||
|
BuiltInIdentifier::Result => {
|
||||||
|
RESULT.get_or_init(|| Identifier::from_raw_parts(Arc::new("Result".to_string())))
|
||||||
|
}
|
||||||
|
BuiltInIdentifier::Ok => {
|
||||||
|
OK.get_or_init(|| Identifier::from_raw_parts(Arc::new("Ok".to_string())))
|
||||||
|
}
|
||||||
|
BuiltInIdentifier::Error => {
|
||||||
|
ERROR.get_or_init(|| Identifier::from_raw_parts(Arc::new("Error".to_string())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user