upload code
This commit is contained in:
commit
3f507064ce
111 changed files with 9258 additions and 0 deletions
37
.github/workflows/rust.yml
vendored
Normal file
37
.github/workflows/rust.yml
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
name: Rust
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
|
- name: Get Rust Version
|
||||||
|
id: rust-version
|
||||||
|
run: echo "::set-output name=version::$(cargo -V | head -n1 | awk '{print $2}')"
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/git
|
||||||
|
~/.cargo/registry
|
||||||
|
target
|
||||||
|
key: ${{runner.os}}-rust-${{steps.rust-version.outputs.version}}
|
||||||
|
|
||||||
|
- run: cargo test
|
||||||
|
env:
|
||||||
|
RUST_BACKTRACE: 1
|
||||||
|
|
||||||
|
rustfmt:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: dtolnay/rust-toolchain@nightly
|
||||||
|
with:
|
||||||
|
components: rustfmt
|
||||||
|
- run: cargo fmt --all -- --check
|
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
a.out
|
||||||
|
|
||||||
|
# rust/cargo
|
||||||
|
/target/
|
||||||
|
|
||||||
|
# git
|
||||||
|
*.orig
|
||||||
|
|
||||||
|
# emacs
|
||||||
|
*~
|
||||||
|
\#*#
|
||||||
|
.#*#
|
||||||
|
|
||||||
|
# intellij
|
||||||
|
/.idea/
|
||||||
|
*.ipr
|
||||||
|
*.iml
|
1402
Cargo.lock
generated
Normal file
1402
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
44
Cargo.toml
Normal file
44
Cargo.toml
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
# -*- eval: (cargo-minor-mode 1) -*-
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = [".", "stdlib"]
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "compiler"
|
||||||
|
version = "0.0.0"
|
||||||
|
publish = false
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["Dominic Meiser <git@msrd0.de>"]
|
||||||
|
description = "Compiler for our symbolic heap-manipulating probabilistic programming language"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "integration"
|
||||||
|
path = "integration/main.rs"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
anyhow = "1"
|
||||||
|
tar = "0.4"
|
||||||
|
vergen = { version = "7", features = ["git"], default-features = false }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1"
|
||||||
|
ariadne = "0.1"
|
||||||
|
askama = { version = "0.11", default-features = false }
|
||||||
|
bat = { version = "0.21", features = ["regex-onig"], default-features = false }
|
||||||
|
clap = { version = "3", features = ["derive"] }
|
||||||
|
indexmap = "1"
|
||||||
|
prettyplease = "0.1"
|
||||||
|
proc-macro2 = { version = "1", features = ["span-locations"], default-features = false }
|
||||||
|
quote = { version = "1", default-features = false }
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
syn = { version = "1", features = ["clone-impls", "extra-traits", "full", "parsing", "printing"], default-features = false }
|
||||||
|
tar = "0.4"
|
||||||
|
tempfile = "3"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
lazy-regex = "2"
|
||||||
|
libtest = { version = "0.4", package = "libtest-mimic" }
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
strip = true
|
116
README.md
Normal file
116
README.md
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
# Compiler
|
||||||
|
|
||||||
|
This is a compiler for a heap-manipulating symbolic probabilistic programming language
|
||||||
|
that I'm designing as part of my bachelor thesis.
|
||||||
|
|
||||||
|
## Types
|
||||||
|
|
||||||
|
- `bool` boolean value
|
||||||
|
- `int` integer value (either 32 or 64 bit, depending on the target architecture)
|
||||||
|
- `()` empty tuple (also the default return value of functions, like `void` in C)
|
||||||
|
- `(..)` tuple
|
||||||
|
- `Rc<_>` reference-counting pointer
|
||||||
|
- `Option<_>` optional value, can be `None` or `Some(_)`
|
||||||
|
- `$ident`, `$ident<..>` custom type, must have previously been typedef'ed
|
||||||
|
|
||||||
|
## Syntax
|
||||||
|
|
||||||
|
```
|
||||||
|
$program => $typedefs $functions $inputs $main $outputs
|
||||||
|
|
||||||
|
$typedefs => $typedef $typedefs
|
||||||
|
| ε
|
||||||
|
|
||||||
|
$functions => $function $functions
|
||||||
|
| ε
|
||||||
|
|
||||||
|
$inputs => $input $inputs
|
||||||
|
| ε
|
||||||
|
|
||||||
|
$main => $stmt $block
|
||||||
|
|
||||||
|
$outputs => $output $outputs
|
||||||
|
| ε
|
||||||
|
|
||||||
|
$typedef => type $ident = $type;
|
||||||
|
| type $ident<$identlist> = $type;
|
||||||
|
|
||||||
|
$type => bool
|
||||||
|
| int
|
||||||
|
| ()
|
||||||
|
| ($typelist)
|
||||||
|
| ($typelist,)
|
||||||
|
| Rc<$type>
|
||||||
|
| Option<$type>
|
||||||
|
| $ident
|
||||||
|
| $ident<$typelist>
|
||||||
|
|
||||||
|
$typelist => $type | $type, $typelist
|
||||||
|
|
||||||
|
$function => fn $ident($params) { $block }
|
||||||
|
| fn $ident($params) -> $type { $block }
|
||||||
|
| fn $ident<$identlist>($params) { $block }
|
||||||
|
| fn $ident<$identlist>($params) -> $type { $block }
|
||||||
|
|
||||||
|
$params => $paramlist | ε
|
||||||
|
|
||||||
|
$paramlist => $ident: $type, $paramlist
|
||||||
|
| $ident: $type
|
||||||
|
|
||||||
|
$input => input!($ident);
|
||||||
|
|
||||||
|
$output => output!($identlist);
|
||||||
|
|
||||||
|
$stmt => let $ident: $type = $expr;
|
||||||
|
| $ident = $expr;
|
||||||
|
| $ifstmt
|
||||||
|
| { $block } [$float] { $block }
|
||||||
|
| while $ifcond { $block }
|
||||||
|
| { $block }
|
||||||
|
| return;
|
||||||
|
| return $expr;
|
||||||
|
| continue;
|
||||||
|
| break;
|
||||||
|
|
||||||
|
$block => $stmt $block
|
||||||
|
| ε
|
||||||
|
|
||||||
|
$ifstmt => if $ifcond { $block }
|
||||||
|
| if $ifcond { $block } else $ifstmt
|
||||||
|
| if $ifcond { $block } else { $block }
|
||||||
|
|
||||||
|
$ifcond => $expr
|
||||||
|
| let Some($ident) = $expr
|
||||||
|
|
||||||
|
$expr => $expr $op $expr
|
||||||
|
| !$expr
|
||||||
|
| -$expr
|
||||||
|
| *$expr
|
||||||
|
| ($exprs)
|
||||||
|
| ($exprlist,)
|
||||||
|
| $expr.$index
|
||||||
|
| Rc($expr)
|
||||||
|
| Some($expr)
|
||||||
|
| None
|
||||||
|
| $int
|
||||||
|
| $bool
|
||||||
|
| $ident($exprs)
|
||||||
|
| $ident<$typelist>($exprs)
|
||||||
|
|
||||||
|
$exprs => $exprlist | ε
|
||||||
|
|
||||||
|
$exprlist => $expr, $exprlist
|
||||||
|
| $expr
|
||||||
|
|
||||||
|
$op => + | - | * | /
|
||||||
|
| == | != | < | <= | > | >=
|
||||||
|
| && | ||
|
||||||
|
|
||||||
|
$ident matches all valid Rust identifiers
|
||||||
|
$identlist => $ident | $ident, $identlist
|
||||||
|
|
||||||
|
$bool => true | false
|
||||||
|
$index matches all non-negative integer values
|
||||||
|
$int matches all integer values
|
||||||
|
$float matches all floating-point values
|
||||||
|
```
|
24
build.rs
Normal file
24
build.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
use std::{env, fs::File, path::PathBuf};
|
||||||
|
use vergen::{vergen, Config, ShaKind};
|
||||||
|
|
||||||
|
fn main() -> anyhow::Result<()> {
|
||||||
|
// provide version information
|
||||||
|
let mut cfg = Config::default();
|
||||||
|
let git = cfg.git_mut();
|
||||||
|
*git.branch_mut() = false;
|
||||||
|
*git.commit_count_mut() = true;
|
||||||
|
*git.sha_kind_mut() = ShaKind::Short;
|
||||||
|
vergen(cfg)?;
|
||||||
|
|
||||||
|
// include the stdlib crate with the compiler
|
||||||
|
let out_dir: PathBuf = env::var("OUT_DIR")?.parse()?;
|
||||||
|
let archive_path = out_dir.join("stdlib.tar");
|
||||||
|
println!("cargo:rustc-env=ARCHIVE_PATH={}", archive_path.display());
|
||||||
|
let mut archive = tar::Builder::new(File::create(&archive_path)?);
|
||||||
|
let stdlib_path = "stdlib";
|
||||||
|
println!("cargo:rerun-if-changed={stdlib_path}");
|
||||||
|
archive.append_dir_all(stdlib_path, stdlib_path)?;
|
||||||
|
archive.finish()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
3
examples/dice.p3l
Normal file
3
examples/dice.p3l
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
input!(dice);
|
||||||
|
{}
|
||||||
|
output!(dice);
|
262
examples/quicksort.p3l
Normal file
262
examples/quicksort.p3l
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
type List<T> = Option<Rc<(T, List<T>)>>;
|
||||||
|
type Partition<T> = (List<T>, T, List<T>);
|
||||||
|
|
||||||
|
// Return a random number between 0 (inclusive) and
|
||||||
|
// max (exclusive).
|
||||||
|
fn random(max: int) -> int {
|
||||||
|
while true {
|
||||||
|
let value: int = 0;
|
||||||
|
let max_value: int = 0;
|
||||||
|
while max_value < max {
|
||||||
|
max_value = max_value * 2 + 1;
|
||||||
|
value = value * 2;
|
||||||
|
{ value = value + 1; } [0.5] {}
|
||||||
|
}
|
||||||
|
if value < max {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true if the option is `None`.
|
||||||
|
fn is_none<O>(opt: O) -> bool {
|
||||||
|
if let Some(_value) = opt {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap an option.
|
||||||
|
fn unwrap<T>(opt: Option<T>) -> T {
|
||||||
|
if let Some(value) = opt {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new, empty list.
|
||||||
|
// Complexity: O(1)
|
||||||
|
fn list_new<T>() -> List<T> {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push a new value to the front of the list.
|
||||||
|
// Complexity: O(1)
|
||||||
|
fn list_push_front<T>(list: List<T>, elem: T) -> List<T> {
|
||||||
|
return Some(Rc((elem, list)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop the value from the front of the list.
|
||||||
|
// Complexity: O(1)
|
||||||
|
fn list_pop_front<T>(list: List<T>) -> (T, List<T>) {
|
||||||
|
if let Some(front) = list {
|
||||||
|
return *front;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append a list to the end of this one.
|
||||||
|
// Complexity: O(n)
|
||||||
|
fn list_append<T>(list: List<T>, list2: List<T>) -> List<T> {
|
||||||
|
if is_none::<List<T>>(list) {
|
||||||
|
return list2;
|
||||||
|
}
|
||||||
|
|
||||||
|
let back: List<T> = list;
|
||||||
|
while let Some(front) = back {
|
||||||
|
let tail: List<T> = (*front).1;
|
||||||
|
if is_none::<List<T>>(tail) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
back = tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(back) = back {
|
||||||
|
back.1 = list2;
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
// else unreachable - back is always a non-empty list
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the length of a list.
|
||||||
|
// Complexity: O(n)
|
||||||
|
fn list_len<T>(list: List<T>) -> int {
|
||||||
|
let len: int = 0;
|
||||||
|
while let Some(inner) = list {
|
||||||
|
len = len + 1;
|
||||||
|
list = (*inner).1;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap the value of the two list elements.
|
||||||
|
fn list_swap<T>(list: List<T>, list2: List<T>) {
|
||||||
|
let list: Rc<(T, List<T>)> = unwrap::<Rc<(T, List<T>)>>(list);
|
||||||
|
let list2: Rc<(T, List<T>)> = unwrap::<Rc<(T, List<T>)>>(list2);
|
||||||
|
let tmp: T = (*list).0;
|
||||||
|
list.0 = (*list2).0;
|
||||||
|
list2.0 = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap the n-th and last elements, and return its value.
|
||||||
|
fn list_swap_nth_last<T>(list: List<T>, n: int) -> T {
|
||||||
|
let i: int = 0;
|
||||||
|
let nth: List<T> = None;
|
||||||
|
while let Some(front) = list {
|
||||||
|
if i == n {
|
||||||
|
nth = list;
|
||||||
|
}
|
||||||
|
let tail: List<T> = (*front).1;
|
||||||
|
if is_none::<List<T>>(tail) {
|
||||||
|
let _unit: () = list_swap::<T>(nth, list);
|
||||||
|
return (*front).0;
|
||||||
|
}
|
||||||
|
list = tail;
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the next list element.
|
||||||
|
fn list_next<T>(list: List<T>) -> List<T> {
|
||||||
|
return (*unwrap::<Rc<(T, List<T>)>>(list)).1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split the list at index n.
|
||||||
|
fn list_split<T>(list: List<T>, n: int) -> (List<T>, List<T>) {
|
||||||
|
if n == 0 {
|
||||||
|
return (None, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
let list2: List<T> = list;
|
||||||
|
let i: int = 0;
|
||||||
|
while i < n - 1 {
|
||||||
|
list2 = list_next::<T>(list2);
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(front) = list2 {
|
||||||
|
list2 = (*front).1;
|
||||||
|
front.1 = None;
|
||||||
|
}
|
||||||
|
return (list, list2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increase swap count
|
||||||
|
fn inc_swaps(swaps_and_comps: Rc<(int, int)>) {
|
||||||
|
swaps_and_comps.0 = (*swaps_and_comps).0 + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increase comparison count
|
||||||
|
fn inc_comps(swaps_and_comps: Rc<(int, int)>) {
|
||||||
|
swaps_and_comps.1 = (*swaps_and_comps).1 + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Randomised quicksort.
|
||||||
|
// Expected complexity: O(n log n)
|
||||||
|
fn quicksort(list: List<int>, swaps_and_comps: Rc<(int, int)>) -> List<int> {
|
||||||
|
if list_len::<int>(list) < 2 {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
let partition: Partition<int> = partition(list, swaps_and_comps);
|
||||||
|
let list: List<int> = quicksort(partition.0, swaps_and_comps);
|
||||||
|
let pivot: int = partition.1;
|
||||||
|
let list2: List<int> = quicksort(partition.2, swaps_and_comps);
|
||||||
|
|
||||||
|
list2 = list_push_front::<int>(list2, pivot);
|
||||||
|
return list_append::<int>(list, list2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Partition a list into smaller, pivot, and larger elements
|
||||||
|
fn partition(list: List<int>, swaps_and_comps: Rc<(int, int)>) -> Partition<int> {
|
||||||
|
// choose some random element as pivot and move it to the last position
|
||||||
|
let list_len: int = list_len::<int>(list);
|
||||||
|
let pivot: int = list_swap_nth_last::<int>(list, random(list_len));
|
||||||
|
let _unit: () = inc_swaps(swaps_and_comps);
|
||||||
|
|
||||||
|
// this is our pivot position
|
||||||
|
let iter: List<int> = list;
|
||||||
|
let iter_idx: int = -1;
|
||||||
|
|
||||||
|
// move all elements smaller than our pivot to its left
|
||||||
|
let i: int = 0;
|
||||||
|
let iter2: List<int> = list;
|
||||||
|
while let Some(front) = iter2 {
|
||||||
|
// don't swap out the pivot already
|
||||||
|
if i != list_len - 1 {
|
||||||
|
|
||||||
|
let _unit: () = inc_comps(swaps_and_comps);
|
||||||
|
if (*front).0 <= pivot {
|
||||||
|
// move pivot index forward
|
||||||
|
if iter_idx >= 0 {
|
||||||
|
iter = list_next::<int>(iter);
|
||||||
|
}
|
||||||
|
iter_idx = iter_idx + 1;
|
||||||
|
|
||||||
|
// swap current element with pivot position
|
||||||
|
if iter_idx != i {
|
||||||
|
let _unit: () = list_swap::<int>(iter, iter2);
|
||||||
|
let _unit: () = inc_swaps(swaps_and_comps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
iter2 = (*front).1;
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// move pivot to the correct pivot position
|
||||||
|
iter_idx = iter_idx + 1;
|
||||||
|
let _pivot: int = list_swap_nth_last::<int>(list, iter_idx);
|
||||||
|
let _unit: () = inc_swaps(swaps_and_comps);
|
||||||
|
|
||||||
|
// split the list into two
|
||||||
|
let lists: (List<int>, List<int>) = list_split::<int>(list, iter_idx);
|
||||||
|
let pop: (int, List<int>) = list_pop_front::<int>(lists.1);
|
||||||
|
return (lists.0, pop.0, pop.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the list is sorted.
|
||||||
|
fn assert_sorted(list: List<int>) -> () {
|
||||||
|
if let Some(first) = list {
|
||||||
|
if assert_sorted_impl((*first).1, (*first).0) {
|
||||||
|
return ();
|
||||||
|
}
|
||||||
|
// else there's no path to a return statement - assert failed
|
||||||
|
} else {
|
||||||
|
return ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn assert_sorted_impl(list: List<int>, prev: int) -> bool {
|
||||||
|
while let Some(front) = list {
|
||||||
|
let curr: int = (*front).0;
|
||||||
|
if curr < prev {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
prev = curr;
|
||||||
|
list = (*front).1;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
input!(len);
|
||||||
|
|
||||||
|
// Create a random list.
|
||||||
|
let list: List<int> = list_new::<int>();
|
||||||
|
let i: int = 0;
|
||||||
|
while i < len {
|
||||||
|
list = list_push_front::<int>(list, random(len*2));
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run quicksort.
|
||||||
|
let swaps_and_comps: Rc<(int, int)> = Rc((0, 0));
|
||||||
|
list = quicksort(list, swaps_and_comps);
|
||||||
|
let swaps: int = (*swaps_and_comps).0;
|
||||||
|
let comps: int = (*swaps_and_comps).1;
|
||||||
|
|
||||||
|
// Ensure the list is now sorted.
|
||||||
|
let _unit: () = assert_sorted(list);
|
||||||
|
|
||||||
|
output!(swaps);
|
||||||
|
output!(comps);
|
382
examples/skiplist-1-4.p3l
Normal file
382
examples/skiplist-1-4.p3l
Normal file
|
@ -0,0 +1,382 @@
|
||||||
|
// 0: own level (lowest level is 0)
|
||||||
|
// 1: entry below this one (exists iff level is greater than 0)
|
||||||
|
// 2: entry to the right of this one
|
||||||
|
type SkipList<V> = (int, SkipListLink<V>, Option<SkipListNode<V>>);
|
||||||
|
|
||||||
|
type SkipListLink<V> = Option<Rc<SkipList<V>>>;
|
||||||
|
|
||||||
|
// 0: own level (lowest level is 0)
|
||||||
|
// 1: entry below this one (exists iff level is greater than 0)
|
||||||
|
// 2: entry to the right of this one
|
||||||
|
// 3: key of the column this entry belongs to
|
||||||
|
// 4: value of the column (only on the lowest level)
|
||||||
|
type SkipListNode<V> = Rc<(int, Option<SkipListNode<V>>, Option<SkipListNode<V>>, int, Option<V>)>;
|
||||||
|
|
||||||
|
type Stack<T> = Rc<(Option<(T, Stack<T>)>,)>;
|
||||||
|
|
||||||
|
// Unwrap an option.
|
||||||
|
fn unwrap<T>(opt: Option<T>) -> T {
|
||||||
|
if let Some(value) = opt {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new, empty stack
|
||||||
|
fn stack_new<T>() -> Stack<T> {
|
||||||
|
return Rc((None,));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push a new value to the stack.
|
||||||
|
fn stack_push<T>(stack: Stack<T>, value: T) {
|
||||||
|
stack.0 = Some((value, Rc(*stack)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop a value from the stack.
|
||||||
|
fn stack_pop<T>(stack: Stack<T>) -> Option<T> {
|
||||||
|
if let Some(top) = (*stack).0 {
|
||||||
|
stack.0 = (*top.1).0;
|
||||||
|
return Some(top.0);
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a new, empty skiplist.
|
||||||
|
fn skiplist_new<V>() -> SkipList<V> {
|
||||||
|
return (0, None, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the current maximum level of the skiplist.
|
||||||
|
fn skiplist_level<V>(list: SkipList<V>) -> int {
|
||||||
|
return list.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOT PUBLIC API
|
||||||
|
// Insert a new value into the skiplist at the first position.
|
||||||
|
fn skiplist_insert_first<V>(
|
||||||
|
list: SkipList<V>,
|
||||||
|
new_lvl: int,
|
||||||
|
key: int,
|
||||||
|
value: V
|
||||||
|
) -> SkipList<V> {
|
||||||
|
// decompose list into levels
|
||||||
|
let levels: Stack<Option<SkipListNode<V>>> =
|
||||||
|
stack_new::<Option<SkipListNode<V>>>();
|
||||||
|
let _unit: () = stack_push::<Option<SkipListNode<V>>>(levels, list.2);
|
||||||
|
while let Some(next) = list.1 {
|
||||||
|
list = *next;
|
||||||
|
let _unit: () = stack_push::<Option<SkipListNode<V>>>(levels, list.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new list with just the lowest level
|
||||||
|
let lvl: int = 0;
|
||||||
|
let top: Option<Option<SkipListNode<V>>> =
|
||||||
|
stack_pop::<Option<SkipListNode<V>>>(levels);
|
||||||
|
let new_node: SkipListNode<V> = Rc((
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
unwrap::<Option<SkipListNode<V>>>(top),
|
||||||
|
key,
|
||||||
|
Some(value)
|
||||||
|
));
|
||||||
|
list = (0, None, Some(new_node));
|
||||||
|
|
||||||
|
// rebuild list up to its original level
|
||||||
|
while let Some(top) = stack_pop::<Option<SkipListNode<V>>>(levels) {
|
||||||
|
lvl = lvl + 1;
|
||||||
|
if new_lvl >= lvl {
|
||||||
|
new_node = Rc((lvl, Some(new_node), top, key, None));
|
||||||
|
} else {
|
||||||
|
let top: SkipListNode<V> = unwrap::<SkipListNode<V>>(top);
|
||||||
|
new_node = Rc((lvl, Some(new_node), (*top).2, (*top).3, None));
|
||||||
|
}
|
||||||
|
list = (lvl, Some(Rc(list)), Some(new_node));
|
||||||
|
}
|
||||||
|
|
||||||
|
// add new levels if necessary
|
||||||
|
while new_lvl > lvl {
|
||||||
|
lvl = lvl + 1;
|
||||||
|
new_node = Rc((lvl, Some(new_node), None, key, None));
|
||||||
|
list = (lvl, Some(Rc(list)), Some(new_node));
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOT PUBLIC API
|
||||||
|
// Insert a new value into the skiplist that needs to be inserted after
|
||||||
|
// the current node. Will be called when entering a new level while
|
||||||
|
// searching for the insert position. Returns the level of the inserted
|
||||||
|
// node.
|
||||||
|
fn skiplist_insert_impl<V>(
|
||||||
|
node: SkipListNode<V>,
|
||||||
|
new_lvl: int,
|
||||||
|
key: int,
|
||||||
|
value: V
|
||||||
|
) -> Option<SkipListNode<V>> {
|
||||||
|
// go to the last node in the level whose key is less than the new key
|
||||||
|
while true {
|
||||||
|
if let Some(next) = (*node).2 {
|
||||||
|
if (*next).3 < key {
|
||||||
|
node = next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// when not at the lowest level, descend
|
||||||
|
if let Some(below) = (*node).1 {
|
||||||
|
let new_node: Option<SkipListNode<V>> =
|
||||||
|
skiplist_insert_impl::<V>(below, new_lvl, key, value);
|
||||||
|
if let Some(new_node) = new_node {
|
||||||
|
let node_lvl: int = (*node).0;
|
||||||
|
if new_lvl >= node_lvl {
|
||||||
|
let new_node: SkipListNode<V> =
|
||||||
|
Rc((node_lvl, Some(new_node), (*node).2, key, None));
|
||||||
|
node.2 = Some(new_node);
|
||||||
|
return Some(new_node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we found a node with the exact key, just update the value
|
||||||
|
if (*node).3 == key {
|
||||||
|
node.4 = Some(value);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, we need to create a new node immediately following the
|
||||||
|
// current one
|
||||||
|
let new_node: SkipListNode<V> =
|
||||||
|
Rc((0, None, (*node).2, key, Some(value)));
|
||||||
|
node.2 = Some(new_node);
|
||||||
|
return Some(new_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOT PUBLIC API
|
||||||
|
// Insert a new value with a set level.
|
||||||
|
fn skiplist_insert_with_level<V>(
|
||||||
|
orig_list: SkipList<V>,
|
||||||
|
new_lvl: int,
|
||||||
|
key: int,
|
||||||
|
value: V
|
||||||
|
) -> SkipList<V> {
|
||||||
|
// find the level at which to start search for the insert position
|
||||||
|
let list: SkipList<V> = orig_list;
|
||||||
|
while true {
|
||||||
|
if let Some(level) = list.2 {
|
||||||
|
if (*level).3 <= key {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(below) = list.1 {
|
||||||
|
list = *below;
|
||||||
|
} else {
|
||||||
|
return skiplist_insert_first::<V>(list, new_lvl, key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(node) = list.2 {
|
||||||
|
let new_node: Option<SkipListNode<V>> =
|
||||||
|
skiplist_insert_impl::<V>(node, new_lvl, key, value);
|
||||||
|
if let Some(new_node) = new_node {
|
||||||
|
let node_lvl: int = (*node).0;
|
||||||
|
|
||||||
|
// update levels above node_lvl if necessary
|
||||||
|
list = orig_list;
|
||||||
|
if new_lvl >= list.0 {
|
||||||
|
let new_node: SkipListNode<V> =
|
||||||
|
Rc((list.0, None, list.2, key, None));
|
||||||
|
list.2 = Some(new_node);
|
||||||
|
}
|
||||||
|
while let Some(below) = list.1 {
|
||||||
|
list = *below;
|
||||||
|
if list.0 <= node_lvl || list.0 < new_lvl {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let new_node: SkipListNode<V> =
|
||||||
|
Rc((list.0, None, list.2, key, None));
|
||||||
|
list.2 = Some(new_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add levels above the list level if necessary
|
||||||
|
list = orig_list;
|
||||||
|
while new_lvl > list.0 {
|
||||||
|
new_node = Rc((list.0, Some(new_node), None, key, None));
|
||||||
|
list = (list.0 + 1, Some(Rc(list)), Some(new_node));
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
} else {
|
||||||
|
return orig_list;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// we are inserting into an empty list
|
||||||
|
return skiplist_insert_first::<V>(list, new_lvl, key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOT PUBLIC API
|
||||||
|
// Return a random level.
|
||||||
|
fn skiplist_random_level() -> int {
|
||||||
|
let lvl: int = 0;
|
||||||
|
while true {
|
||||||
|
{ lvl = lvl + 1; } [0.25] { return lvl; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take the skiplist, insert a new value, and return the skiplist.
|
||||||
|
fn skiplist_insert<V>(
|
||||||
|
list: SkipList<V>,
|
||||||
|
key: int,
|
||||||
|
value: V
|
||||||
|
) -> SkipList<V> {
|
||||||
|
let lvl: int = skiplist_random_level();
|
||||||
|
return skiplist_insert_with_level::<V>(list, lvl, key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOT PUBLIC API
|
||||||
|
// Find a value in the skiplist, starting the search at a specific node.
|
||||||
|
fn skiplist_get_impl<V>(
|
||||||
|
node: SkipListNode<V>,
|
||||||
|
key: int
|
||||||
|
) -> (Option<V>, int) {
|
||||||
|
let steps: int = 1;
|
||||||
|
|
||||||
|
if (*node).3 < key {
|
||||||
|
while true {
|
||||||
|
if let Some(next) = (*node).2 {
|
||||||
|
let next_key: int = (*next).3;
|
||||||
|
steps = steps + 1;
|
||||||
|
if next_key <= key {
|
||||||
|
node = next;
|
||||||
|
if next_key == key {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(below) = (*node).1 {
|
||||||
|
node = below;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*node).3 != key {
|
||||||
|
return (None, steps);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the value is only stored at the lowest level
|
||||||
|
while let Some(below) = (*node).1 {
|
||||||
|
node = below;
|
||||||
|
}
|
||||||
|
return (Some(unwrap::<V>((*node).4)), steps);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find a value in the skiplist.
|
||||||
|
fn skiplist_get<V>(list: SkipList<V>, key: int) -> (Option<V>, int) {
|
||||||
|
let steps: int = 0;
|
||||||
|
|
||||||
|
while true {
|
||||||
|
if let Some(node) = list.2 {
|
||||||
|
steps = steps + 1;
|
||||||
|
if (*node).3 <= key {
|
||||||
|
let tmp: (Option<V>, int) = skiplist_get_impl::<V>(node, key);
|
||||||
|
return (tmp.0, steps + tmp.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(below) = list.1 {
|
||||||
|
list = *below;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (None, steps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the skiplist contains a certain key.
|
||||||
|
fn assert_contains<V>(list: SkipList<V>, key: int) -> () {
|
||||||
|
if let Some(_value) = skiplist_get::<V>(list, key).0 {
|
||||||
|
return ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the length of the skiplist.
|
||||||
|
fn skiplist_len<V>(list: SkipList<V>) -> int {
|
||||||
|
// descend to the lowest level
|
||||||
|
while let Some(next) = list.1 {
|
||||||
|
list = *next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// count the lowest level
|
||||||
|
let node: Option<SkipListNode<V>> = list.2;
|
||||||
|
let len: int = 0;
|
||||||
|
while let Some(next) = node {
|
||||||
|
len = len + 1;
|
||||||
|
node = (*next).2;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the skiplist has the correct length.
|
||||||
|
fn assert_len<V>(list: SkipList<V>, len: int) -> () {
|
||||||
|
if skiplist_len::<V>(list) == len {
|
||||||
|
return ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that a value is in a certain range.
|
||||||
|
fn assert_in_range(value: int, min_incl: int, max_excl: int) -> () {
|
||||||
|
if value >= min_incl && value < max_excl {
|
||||||
|
return ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a random number between 0 (inclusive) and
|
||||||
|
// max (exclusive).
|
||||||
|
fn random(max: int) -> int {
|
||||||
|
while true {
|
||||||
|
let value: int = 0;
|
||||||
|
let max_value: int = 0;
|
||||||
|
while max_value < max {
|
||||||
|
max_value = max_value * 2 + 1;
|
||||||
|
value = value * 2;
|
||||||
|
{ value = value + 1; } [0.25] {}
|
||||||
|
}
|
||||||
|
if value < max {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input!(len);
|
||||||
|
|
||||||
|
// create a new list with entries 0..len
|
||||||
|
let list: SkipList<()> = skiplist_new::<()>();
|
||||||
|
let i: int = 0;
|
||||||
|
while i < len {
|
||||||
|
list = skiplist_insert::<()>(list, i, ());
|
||||||
|
let _unit: () = assert_contains::<()>(list, i);
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
let _unit: () = assert_len::<()>(list, len);
|
||||||
|
|
||||||
|
// lookup a random value in the list
|
||||||
|
let key: int = random(len);
|
||||||
|
let _unit: () = assert_in_range(key, 0, len);
|
||||||
|
let lookup: (Option<()>, int) = skiplist_get::<()>(list, key);
|
||||||
|
let _unit: () = unwrap::<()>(lookup.0);
|
||||||
|
|
||||||
|
let steps: int = lookup.1;
|
||||||
|
let lvl: int = skiplist_level::<()>(list);
|
||||||
|
|
||||||
|
output!(len);
|
||||||
|
output!(lvl, steps);
|
382
examples/skiplist-3-8.p3l
Normal file
382
examples/skiplist-3-8.p3l
Normal file
|
@ -0,0 +1,382 @@
|
||||||
|
// 0: own level (lowest level is 0)
|
||||||
|
// 1: entry below this one (exists iff level is greater than 0)
|
||||||
|
// 2: entry to the right of this one
|
||||||
|
type SkipList<V> = (int, SkipListLink<V>, Option<SkipListNode<V>>);
|
||||||
|
|
||||||
|
type SkipListLink<V> = Option<Rc<SkipList<V>>>;
|
||||||
|
|
||||||
|
// 0: own level (lowest level is 0)
|
||||||
|
// 1: entry below this one (exists iff level is greater than 0)
|
||||||
|
// 2: entry to the right of this one
|
||||||
|
// 3: key of the column this entry belongs to
|
||||||
|
// 4: value of the column (only on the lowest level)
|
||||||
|
type SkipListNode<V> = Rc<(int, Option<SkipListNode<V>>, Option<SkipListNode<V>>, int, Option<V>)>;
|
||||||
|
|
||||||
|
type Stack<T> = Rc<(Option<(T, Stack<T>)>,)>;
|
||||||
|
|
||||||
|
// Unwrap an option.
|
||||||
|
fn unwrap<T>(opt: Option<T>) -> T {
|
||||||
|
if let Some(value) = opt {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new, empty stack
|
||||||
|
fn stack_new<T>() -> Stack<T> {
|
||||||
|
return Rc((None,));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push a new value to the stack.
|
||||||
|
fn stack_push<T>(stack: Stack<T>, value: T) {
|
||||||
|
stack.0 = Some((value, Rc(*stack)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop a value from the stack.
|
||||||
|
fn stack_pop<T>(stack: Stack<T>) -> Option<T> {
|
||||||
|
if let Some(top) = (*stack).0 {
|
||||||
|
stack.0 = (*top.1).0;
|
||||||
|
return Some(top.0);
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a new, empty skiplist.
|
||||||
|
fn skiplist_new<V>() -> SkipList<V> {
|
||||||
|
return (0, None, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the current maximum level of the skiplist.
|
||||||
|
fn skiplist_level<V>(list: SkipList<V>) -> int {
|
||||||
|
return list.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOT PUBLIC API
|
||||||
|
// Insert a new value into the skiplist at the first position.
|
||||||
|
fn skiplist_insert_first<V>(
|
||||||
|
list: SkipList<V>,
|
||||||
|
new_lvl: int,
|
||||||
|
key: int,
|
||||||
|
value: V
|
||||||
|
) -> SkipList<V> {
|
||||||
|
// decompose list into levels
|
||||||
|
let levels: Stack<Option<SkipListNode<V>>> =
|
||||||
|
stack_new::<Option<SkipListNode<V>>>();
|
||||||
|
let _unit: () = stack_push::<Option<SkipListNode<V>>>(levels, list.2);
|
||||||
|
while let Some(next) = list.1 {
|
||||||
|
list = *next;
|
||||||
|
let _unit: () = stack_push::<Option<SkipListNode<V>>>(levels, list.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new list with just the lowest level
|
||||||
|
let lvl: int = 0;
|
||||||
|
let top: Option<Option<SkipListNode<V>>> =
|
||||||
|
stack_pop::<Option<SkipListNode<V>>>(levels);
|
||||||
|
let new_node: SkipListNode<V> = Rc((
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
unwrap::<Option<SkipListNode<V>>>(top),
|
||||||
|
key,
|
||||||
|
Some(value)
|
||||||
|
));
|
||||||
|
list = (0, None, Some(new_node));
|
||||||
|
|
||||||
|
// rebuild list up to its original level
|
||||||
|
while let Some(top) = stack_pop::<Option<SkipListNode<V>>>(levels) {
|
||||||
|
lvl = lvl + 1;
|
||||||
|
if new_lvl >= lvl {
|
||||||
|
new_node = Rc((lvl, Some(new_node), top, key, None));
|
||||||
|
} else {
|
||||||
|
let top: SkipListNode<V> = unwrap::<SkipListNode<V>>(top);
|
||||||
|
new_node = Rc((lvl, Some(new_node), (*top).2, (*top).3, None));
|
||||||
|
}
|
||||||
|
list = (lvl, Some(Rc(list)), Some(new_node));
|
||||||
|
}
|
||||||
|
|
||||||
|
// add new levels if necessary
|
||||||
|
while new_lvl > lvl {
|
||||||
|
lvl = lvl + 1;
|
||||||
|
new_node = Rc((lvl, Some(new_node), None, key, None));
|
||||||
|
list = (lvl, Some(Rc(list)), Some(new_node));
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOT PUBLIC API
|
||||||
|
// Insert a new value into the skiplist that needs to be inserted after
|
||||||
|
// the current node. Will be called when entering a new level while
|
||||||
|
// searching for the insert position. Returns the level of the inserted
|
||||||
|
// node.
|
||||||
|
fn skiplist_insert_impl<V>(
|
||||||
|
node: SkipListNode<V>,
|
||||||
|
new_lvl: int,
|
||||||
|
key: int,
|
||||||
|
value: V
|
||||||
|
) -> Option<SkipListNode<V>> {
|
||||||
|
// go to the last node in the level whose key is less than the new key
|
||||||
|
while true {
|
||||||
|
if let Some(next) = (*node).2 {
|
||||||
|
if (*next).3 < key {
|
||||||
|
node = next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// when not at the lowest level, descend
|
||||||
|
if let Some(below) = (*node).1 {
|
||||||
|
let new_node: Option<SkipListNode<V>> =
|
||||||
|
skiplist_insert_impl::<V>(below, new_lvl, key, value);
|
||||||
|
if let Some(new_node) = new_node {
|
||||||
|
let node_lvl: int = (*node).0;
|
||||||
|
if new_lvl >= node_lvl {
|
||||||
|
let new_node: SkipListNode<V> =
|
||||||
|
Rc((node_lvl, Some(new_node), (*node).2, key, None));
|
||||||
|
node.2 = Some(new_node);
|
||||||
|
return Some(new_node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we found a node with the exact key, just update the value
|
||||||
|
if (*node).3 == key {
|
||||||
|
node.4 = Some(value);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, we need to create a new node immediately following the
|
||||||
|
// current one
|
||||||
|
let new_node: SkipListNode<V> =
|
||||||
|
Rc((0, None, (*node).2, key, Some(value)));
|
||||||
|
node.2 = Some(new_node);
|
||||||
|
return Some(new_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOT PUBLIC API
|
||||||
|
// Insert a new value with a set level.
|
||||||
|
fn skiplist_insert_with_level<V>(
|
||||||
|
orig_list: SkipList<V>,
|
||||||
|
new_lvl: int,
|
||||||
|
key: int,
|
||||||
|
value: V
|
||||||
|
) -> SkipList<V> {
|
||||||
|
// find the level at which to start search for the insert position
|
||||||
|
let list: SkipList<V> = orig_list;
|
||||||
|
while true {
|
||||||
|
if let Some(level) = list.2 {
|
||||||
|
if (*level).3 <= key {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(below) = list.1 {
|
||||||
|
list = *below;
|
||||||
|
} else {
|
||||||
|
return skiplist_insert_first::<V>(list, new_lvl, key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(node) = list.2 {
|
||||||
|
let new_node: Option<SkipListNode<V>> =
|
||||||
|
skiplist_insert_impl::<V>(node, new_lvl, key, value);
|
||||||
|
if let Some(new_node) = new_node {
|
||||||
|
let node_lvl: int = (*node).0;
|
||||||
|
|
||||||
|
// update levels above node_lvl if necessary
|
||||||
|
list = orig_list;
|
||||||
|
if new_lvl >= list.0 {
|
||||||
|
let new_node: SkipListNode<V> =
|
||||||
|
Rc((list.0, None, list.2, key, None));
|
||||||
|
list.2 = Some(new_node);
|
||||||
|
}
|
||||||
|
while let Some(below) = list.1 {
|
||||||
|
list = *below;
|
||||||
|
if list.0 <= node_lvl || list.0 < new_lvl {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let new_node: SkipListNode<V> =
|
||||||
|
Rc((list.0, None, list.2, key, None));
|
||||||
|
list.2 = Some(new_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add levels above the list level if necessary
|
||||||
|
list = orig_list;
|
||||||
|
while new_lvl > list.0 {
|
||||||
|
new_node = Rc((list.0, Some(new_node), None, key, None));
|
||||||
|
list = (list.0 + 1, Some(Rc(list)), Some(new_node));
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
} else {
|
||||||
|
return orig_list;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// we are inserting into an empty list
|
||||||
|
return skiplist_insert_first::<V>(list, new_lvl, key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOT PUBLIC API
|
||||||
|
// Return a random level.
|
||||||
|
fn skiplist_random_level() -> int {
|
||||||
|
let lvl: int = 0;
|
||||||
|
while true {
|
||||||
|
{ lvl = lvl + 1; } [0.375] { return lvl; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take the skiplist, insert a new value, and return the skiplist.
|
||||||
|
fn skiplist_insert<V>(
|
||||||
|
list: SkipList<V>,
|
||||||
|
key: int,
|
||||||
|
value: V
|
||||||
|
) -> SkipList<V> {
|
||||||
|
let lvl: int = skiplist_random_level();
|
||||||
|
return skiplist_insert_with_level::<V>(list, lvl, key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOT PUBLIC API
|
||||||
|
// Find a value in the skiplist, starting the search at a specific node.
|
||||||
|
fn skiplist_get_impl<V>(
|
||||||
|
node: SkipListNode<V>,
|
||||||
|
key: int
|
||||||
|
) -> (Option<V>, int) {
|
||||||
|
let steps: int = 1;
|
||||||
|
|
||||||
|
if (*node).3 < key {
|
||||||
|
while true {
|
||||||
|
if let Some(next) = (*node).2 {
|
||||||
|
let next_key: int = (*next).3;
|
||||||
|
steps = steps + 1;
|
||||||
|
if next_key <= key {
|
||||||
|
node = next;
|
||||||
|
if next_key == key {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(below) = (*node).1 {
|
||||||
|
node = below;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*node).3 != key {
|
||||||
|
return (None, steps);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the value is only stored at the lowest level
|
||||||
|
while let Some(below) = (*node).1 {
|
||||||
|
node = below;
|
||||||
|
}
|
||||||
|
return (Some(unwrap::<V>((*node).4)), steps);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find a value in the skiplist.
|
||||||
|
fn skiplist_get<V>(list: SkipList<V>, key: int) -> (Option<V>, int) {
|
||||||
|
let steps: int = 0;
|
||||||
|
|
||||||
|
while true {
|
||||||
|
if let Some(node) = list.2 {
|
||||||
|
steps = steps + 1;
|
||||||
|
if (*node).3 <= key {
|
||||||
|
let tmp: (Option<V>, int) = skiplist_get_impl::<V>(node, key);
|
||||||
|
return (tmp.0, steps + tmp.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(below) = list.1 {
|
||||||
|
list = *below;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (None, steps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the skiplist contains a certain key.
|
||||||
|
fn assert_contains<V>(list: SkipList<V>, key: int) -> () {
|
||||||
|
if let Some(_value) = skiplist_get::<V>(list, key).0 {
|
||||||
|
return ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the length of the skiplist.
|
||||||
|
fn skiplist_len<V>(list: SkipList<V>) -> int {
|
||||||
|
// descend to the lowest level
|
||||||
|
while let Some(next) = list.1 {
|
||||||
|
list = *next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// count the lowest level
|
||||||
|
let node: Option<SkipListNode<V>> = list.2;
|
||||||
|
let len: int = 0;
|
||||||
|
while let Some(next) = node {
|
||||||
|
len = len + 1;
|
||||||
|
node = (*next).2;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the skiplist has the correct length.
|
||||||
|
fn assert_len<V>(list: SkipList<V>, len: int) -> () {
|
||||||
|
if skiplist_len::<V>(list) == len {
|
||||||
|
return ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that a value is in a certain range.
|
||||||
|
fn assert_in_range(value: int, min_incl: int, max_excl: int) -> () {
|
||||||
|
if value >= min_incl && value < max_excl {
|
||||||
|
return ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a random number between 0 (inclusive) and
|
||||||
|
// max (exclusive).
|
||||||
|
fn random(max: int) -> int {
|
||||||
|
while true {
|
||||||
|
let value: int = 0;
|
||||||
|
let max_value: int = 0;
|
||||||
|
while max_value < max {
|
||||||
|
max_value = max_value * 2 + 1;
|
||||||
|
value = value * 2;
|
||||||
|
{ value = value + 1; } [0.375] {}
|
||||||
|
}
|
||||||
|
if value < max {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input!(len);
|
||||||
|
|
||||||
|
// create a new list with entries 0..len
|
||||||
|
let list: SkipList<()> = skiplist_new::<()>();
|
||||||
|
let i: int = 0;
|
||||||
|
while i < len {
|
||||||
|
list = skiplist_insert::<()>(list, i, ());
|
||||||
|
let _unit: () = assert_contains::<()>(list, i);
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
let _unit: () = assert_len::<()>(list, len);
|
||||||
|
|
||||||
|
// lookup a random value in the list
|
||||||
|
let key: int = random(len);
|
||||||
|
let _unit: () = assert_in_range(key, 0, len);
|
||||||
|
let lookup: (Option<()>, int) = skiplist_get::<()>(list, key);
|
||||||
|
let _unit: () = unwrap::<()>(lookup.0);
|
||||||
|
|
||||||
|
let steps: int = lookup.1;
|
||||||
|
let lvl: int = skiplist_level::<()>(list);
|
||||||
|
|
||||||
|
output!(len);
|
||||||
|
output!(lvl, steps);
|
382
examples/skiplist.p3l
Normal file
382
examples/skiplist.p3l
Normal file
|
@ -0,0 +1,382 @@
|
||||||
|
// 0: own level (lowest level is 0)
|
||||||
|
// 1: entry below this one (exists iff level is greater than 0)
|
||||||
|
// 2: entry to the right of this one
|
||||||
|
type SkipList<V> = (int, SkipListLink<V>, Option<SkipListNode<V>>);
|
||||||
|
|
||||||
|
type SkipListLink<V> = Option<Rc<SkipList<V>>>;
|
||||||
|
|
||||||
|
// 0: own level (lowest level is 0)
|
||||||
|
// 1: entry below this one (exists iff level is greater than 0)
|
||||||
|
// 2: entry to the right of this one
|
||||||
|
// 3: key of the column this entry belongs to
|
||||||
|
// 4: value of the column (only on the lowest level)
|
||||||
|
type SkipListNode<V> = Rc<(int, Option<SkipListNode<V>>, Option<SkipListNode<V>>, int, Option<V>)>;
|
||||||
|
|
||||||
|
type Stack<T> = Rc<(Option<(T, Stack<T>)>,)>;
|
||||||
|
|
||||||
|
// Unwrap an option.
|
||||||
|
fn unwrap<T>(opt: Option<T>) -> T {
|
||||||
|
if let Some(value) = opt {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new, empty stack
|
||||||
|
fn stack_new<T>() -> Stack<T> {
|
||||||
|
return Rc((None,));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push a new value to the stack.
|
||||||
|
fn stack_push<T>(stack: Stack<T>, value: T) {
|
||||||
|
stack.0 = Some((value, Rc(*stack)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop a value from the stack.
|
||||||
|
fn stack_pop<T>(stack: Stack<T>) -> Option<T> {
|
||||||
|
if let Some(top) = (*stack).0 {
|
||||||
|
stack.0 = (*top.1).0;
|
||||||
|
return Some(top.0);
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a new, empty skiplist.
|
||||||
|
fn skiplist_new<V>() -> SkipList<V> {
|
||||||
|
return (0, None, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the current maximum level of the skiplist.
|
||||||
|
fn skiplist_level<V>(list: SkipList<V>) -> int {
|
||||||
|
return list.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOT PUBLIC API
|
||||||
|
// Insert a new value into the skiplist at the first position.
|
||||||
|
fn skiplist_insert_first<V>(
|
||||||
|
list: SkipList<V>,
|
||||||
|
new_lvl: int,
|
||||||
|
key: int,
|
||||||
|
value: V
|
||||||
|
) -> SkipList<V> {
|
||||||
|
// decompose list into levels
|
||||||
|
let levels: Stack<Option<SkipListNode<V>>> =
|
||||||
|
stack_new::<Option<SkipListNode<V>>>();
|
||||||
|
let _unit: () = stack_push::<Option<SkipListNode<V>>>(levels, list.2);
|
||||||
|
while let Some(next) = list.1 {
|
||||||
|
list = *next;
|
||||||
|
let _unit: () = stack_push::<Option<SkipListNode<V>>>(levels, list.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new list with just the lowest level
|
||||||
|
let lvl: int = 0;
|
||||||
|
let top: Option<Option<SkipListNode<V>>> =
|
||||||
|
stack_pop::<Option<SkipListNode<V>>>(levels);
|
||||||
|
let new_node: SkipListNode<V> = Rc((
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
unwrap::<Option<SkipListNode<V>>>(top),
|
||||||
|
key,
|
||||||
|
Some(value)
|
||||||
|
));
|
||||||
|
list = (0, None, Some(new_node));
|
||||||
|
|
||||||
|
// rebuild list up to its original level
|
||||||
|
while let Some(top) = stack_pop::<Option<SkipListNode<V>>>(levels) {
|
||||||
|
lvl = lvl + 1;
|
||||||
|
if new_lvl >= lvl {
|
||||||
|
new_node = Rc((lvl, Some(new_node), top, key, None));
|
||||||
|
} else {
|
||||||
|
let top: SkipListNode<V> = unwrap::<SkipListNode<V>>(top);
|
||||||
|
new_node = Rc((lvl, Some(new_node), (*top).2, (*top).3, None));
|
||||||
|
}
|
||||||
|
list = (lvl, Some(Rc(list)), Some(new_node));
|
||||||
|
}
|
||||||
|
|
||||||
|
// add new levels if necessary
|
||||||
|
while new_lvl > lvl {
|
||||||
|
lvl = lvl + 1;
|
||||||
|
new_node = Rc((lvl, Some(new_node), None, key, None));
|
||||||
|
list = (lvl, Some(Rc(list)), Some(new_node));
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOT PUBLIC API
|
||||||
|
// Insert a new value into the skiplist that needs to be inserted after
|
||||||
|
// the current node. Will be called when entering a new level while
|
||||||
|
// searching for the insert position. Returns the level of the inserted
|
||||||
|
// node.
|
||||||
|
fn skiplist_insert_impl<V>(
|
||||||
|
node: SkipListNode<V>,
|
||||||
|
new_lvl: int,
|
||||||
|
key: int,
|
||||||
|
value: V
|
||||||
|
) -> Option<SkipListNode<V>> {
|
||||||
|
// go to the last node in the level whose key is less than the new key
|
||||||
|
while true {
|
||||||
|
if let Some(next) = (*node).2 {
|
||||||
|
if (*next).3 < key {
|
||||||
|
node = next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// when not at the lowest level, descend
|
||||||
|
if let Some(below) = (*node).1 {
|
||||||
|
let new_node: Option<SkipListNode<V>> =
|
||||||
|
skiplist_insert_impl::<V>(below, new_lvl, key, value);
|
||||||
|
if let Some(new_node) = new_node {
|
||||||
|
let node_lvl: int = (*node).0;
|
||||||
|
if new_lvl >= node_lvl {
|
||||||
|
let new_node: SkipListNode<V> =
|
||||||
|
Rc((node_lvl, Some(new_node), (*node).2, key, None));
|
||||||
|
node.2 = Some(new_node);
|
||||||
|
return Some(new_node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we found a node with the exact key, just update the value
|
||||||
|
if (*node).3 == key {
|
||||||
|
node.4 = Some(value);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, we need to create a new node immediately following the
|
||||||
|
// current one
|
||||||
|
let new_node: SkipListNode<V> =
|
||||||
|
Rc((0, None, (*node).2, key, Some(value)));
|
||||||
|
node.2 = Some(new_node);
|
||||||
|
return Some(new_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOT PUBLIC API
|
||||||
|
// Insert a new value with a set level.
|
||||||
|
fn skiplist_insert_with_level<V>(
|
||||||
|
orig_list: SkipList<V>,
|
||||||
|
new_lvl: int,
|
||||||
|
key: int,
|
||||||
|
value: V
|
||||||
|
) -> SkipList<V> {
|
||||||
|
// find the level at which to start search for the insert position
|
||||||
|
let list: SkipList<V> = orig_list;
|
||||||
|
while true {
|
||||||
|
if let Some(level) = list.2 {
|
||||||
|
if (*level).3 <= key {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(below) = list.1 {
|
||||||
|
list = *below;
|
||||||
|
} else {
|
||||||
|
return skiplist_insert_first::<V>(list, new_lvl, key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(node) = list.2 {
|
||||||
|
let new_node: Option<SkipListNode<V>> =
|
||||||
|
skiplist_insert_impl::<V>(node, new_lvl, key, value);
|
||||||
|
if let Some(new_node) = new_node {
|
||||||
|
let node_lvl: int = (*node).0;
|
||||||
|
|
||||||
|
// update levels above node_lvl if necessary
|
||||||
|
list = orig_list;
|
||||||
|
if new_lvl >= list.0 {
|
||||||
|
let new_node: SkipListNode<V> =
|
||||||
|
Rc((list.0, None, list.2, key, None));
|
||||||
|
list.2 = Some(new_node);
|
||||||
|
}
|
||||||
|
while let Some(below) = list.1 {
|
||||||
|
list = *below;
|
||||||
|
if list.0 <= node_lvl || list.0 < new_lvl {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let new_node: SkipListNode<V> =
|
||||||
|
Rc((list.0, None, list.2, key, None));
|
||||||
|
list.2 = Some(new_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add levels above the list level if necessary
|
||||||
|
list = orig_list;
|
||||||
|
while new_lvl > list.0 {
|
||||||
|
new_node = Rc((list.0, Some(new_node), None, key, None));
|
||||||
|
list = (list.0 + 1, Some(Rc(list)), Some(new_node));
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
} else {
|
||||||
|
return orig_list;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// we are inserting into an empty list
|
||||||
|
return skiplist_insert_first::<V>(list, new_lvl, key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOT PUBLIC API
|
||||||
|
// Return a random level.
|
||||||
|
fn skiplist_random_level() -> int {
|
||||||
|
let lvl: int = 0;
|
||||||
|
while true {
|
||||||
|
{ lvl = lvl + 1; } [0.5] { return lvl; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take the skiplist, insert a new value, and return the skiplist.
|
||||||
|
fn skiplist_insert<V>(
|
||||||
|
list: SkipList<V>,
|
||||||
|
key: int,
|
||||||
|
value: V
|
||||||
|
) -> SkipList<V> {
|
||||||
|
let lvl: int = skiplist_random_level();
|
||||||
|
return skiplist_insert_with_level::<V>(list, lvl, key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOT PUBLIC API
|
||||||
|
// Find a value in the skiplist, starting the search at a specific node.
|
||||||
|
fn skiplist_get_impl<V>(
|
||||||
|
node: SkipListNode<V>,
|
||||||
|
key: int
|
||||||
|
) -> (Option<V>, int) {
|
||||||
|
let steps: int = 1;
|
||||||
|
|
||||||
|
if (*node).3 < key {
|
||||||
|
while true {
|
||||||
|
if let Some(next) = (*node).2 {
|
||||||
|
let next_key: int = (*next).3;
|
||||||
|
steps = steps + 1;
|
||||||
|
if next_key <= key {
|
||||||
|
node = next;
|
||||||
|
if next_key == key {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(below) = (*node).1 {
|
||||||
|
node = below;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*node).3 != key {
|
||||||
|
return (None, steps);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the value is only stored at the lowest level
|
||||||
|
while let Some(below) = (*node).1 {
|
||||||
|
node = below;
|
||||||
|
}
|
||||||
|
return (Some(unwrap::<V>((*node).4)), steps);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find a value in the skiplist.
|
||||||
|
fn skiplist_get<V>(list: SkipList<V>, key: int) -> (Option<V>, int) {
|
||||||
|
let steps: int = 0;
|
||||||
|
|
||||||
|
while true {
|
||||||
|
if let Some(node) = list.2 {
|
||||||
|
steps = steps + 1;
|
||||||
|
if (*node).3 <= key {
|
||||||
|
let tmp: (Option<V>, int) = skiplist_get_impl::<V>(node, key);
|
||||||
|
return (tmp.0, steps + tmp.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(below) = list.1 {
|
||||||
|
list = *below;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (None, steps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the skiplist contains a certain key.
|
||||||
|
fn assert_contains<V>(list: SkipList<V>, key: int) -> () {
|
||||||
|
if let Some(_value) = skiplist_get::<V>(list, key).0 {
|
||||||
|
return ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the length of the skiplist.
|
||||||
|
fn skiplist_len<V>(list: SkipList<V>) -> int {
|
||||||
|
// descend to the lowest level
|
||||||
|
while let Some(next) = list.1 {
|
||||||
|
list = *next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// count the lowest level
|
||||||
|
let node: Option<SkipListNode<V>> = list.2;
|
||||||
|
let len: int = 0;
|
||||||
|
while let Some(next) = node {
|
||||||
|
len = len + 1;
|
||||||
|
node = (*next).2;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the skiplist has the correct length.
|
||||||
|
fn assert_len<V>(list: SkipList<V>, len: int) -> () {
|
||||||
|
if skiplist_len::<V>(list) == len {
|
||||||
|
return ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that a value is in a certain range.
|
||||||
|
fn assert_in_range(value: int, min_incl: int, max_excl: int) -> () {
|
||||||
|
if value >= min_incl && value < max_excl {
|
||||||
|
return ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a random number between 0 (inclusive) and
|
||||||
|
// max (exclusive).
|
||||||
|
fn random(max: int) -> int {
|
||||||
|
while true {
|
||||||
|
let value: int = 0;
|
||||||
|
let max_value: int = 0;
|
||||||
|
while max_value < max {
|
||||||
|
max_value = max_value * 2 + 1;
|
||||||
|
value = value * 2;
|
||||||
|
{ value = value + 1; } [0.5] {}
|
||||||
|
}
|
||||||
|
if value < max {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input!(len);
|
||||||
|
|
||||||
|
// create a new list with entries 0..len
|
||||||
|
let list: SkipList<()> = skiplist_new::<()>();
|
||||||
|
let i: int = 0;
|
||||||
|
while i < len {
|
||||||
|
list = skiplist_insert::<()>(list, i, ());
|
||||||
|
let _unit: () = assert_contains::<()>(list, i);
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
let _unit: () = assert_len::<()>(list, len);
|
||||||
|
|
||||||
|
// lookup a random value in the list
|
||||||
|
let key: int = random(len);
|
||||||
|
let _unit: () = assert_in_range(key, 0, len);
|
||||||
|
let lookup: (Option<()>, int) = skiplist_get::<()>(list, key);
|
||||||
|
let _unit: () = unwrap::<()>(lookup.0);
|
||||||
|
|
||||||
|
let steps: int = lookup.1;
|
||||||
|
let lvl: int = skiplist_level::<()>(list);
|
||||||
|
|
||||||
|
output!(len);
|
||||||
|
output!(lvl, steps);
|
7
integration/fail/no-such-field/assignment-bool-field.out
Normal file
7
integration/fail/no-such-field/assignment-bool-field.out
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Error: No such field
|
||||||
|
╭─[assignment-bool-field.txt:2:5]
|
||||||
|
│
|
||||||
|
2 │ foo.bar = true;
|
||||||
|
· ─┬─
|
||||||
|
· ╰─── bool is a primitive type and does not have any fields
|
||||||
|
───╯
|
2
integration/fail/no-such-field/assignment-bool-field.txt
Normal file
2
integration/fail/no-such-field/assignment-bool-field.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
let foo: bool = true;
|
||||||
|
foo.bar = true;
|
7
integration/fail/no-such-field/assignment-bool-index.out
Normal file
7
integration/fail/no-such-field/assignment-bool-index.out
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Error: No such field
|
||||||
|
╭─[assignment-bool-index.txt:2:5]
|
||||||
|
│
|
||||||
|
2 │ foo.0 = true;
|
||||||
|
· ┬
|
||||||
|
· ╰── bool is a primitive type and does not have any fields
|
||||||
|
───╯
|
2
integration/fail/no-such-field/assignment-bool-index.txt
Normal file
2
integration/fail/no-such-field/assignment-bool-index.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
let foo: bool = true;
|
||||||
|
foo.0 = true;
|
7
integration/fail/no-such-field/assignment-int-field.out
Normal file
7
integration/fail/no-such-field/assignment-int-field.out
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Error: No such field
|
||||||
|
╭─[assignment-int-field.txt:2:5]
|
||||||
|
│
|
||||||
|
2 │ foo.bar = 1;
|
||||||
|
· ─┬─
|
||||||
|
· ╰─── int is a primitive type and does not have any fields
|
||||||
|
───╯
|
2
integration/fail/no-such-field/assignment-int-field.txt
Normal file
2
integration/fail/no-such-field/assignment-int-field.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
let foo: int = 1;
|
||||||
|
foo.bar = 1;
|
7
integration/fail/no-such-field/assignment-int-index.out
Normal file
7
integration/fail/no-such-field/assignment-int-index.out
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Error: No such field
|
||||||
|
╭─[assignment-int-index.txt:2:5]
|
||||||
|
│
|
||||||
|
2 │ foo.0 = 1;
|
||||||
|
· ┬
|
||||||
|
· ╰── int is a primitive type and does not have any fields
|
||||||
|
───╯
|
2
integration/fail/no-such-field/assignment-int-index.txt
Normal file
2
integration/fail/no-such-field/assignment-int-index.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
let foo: int = 1;
|
||||||
|
foo.0 = 1;
|
|
@ -0,0 +1,9 @@
|
||||||
|
Error: No such field
|
||||||
|
╭─[assignment-tuple-field.txt:2:5]
|
||||||
|
│
|
||||||
|
2 │ foo.first = 0;
|
||||||
|
· ──┬──
|
||||||
|
· ╰──── tuples don't have named fields
|
||||||
|
·
|
||||||
|
· Help: To access the first element in a tuple, use `.0`
|
||||||
|
───╯
|
|
@ -0,0 +1,2 @@
|
||||||
|
let foo: (int,) = (1,);
|
||||||
|
foo.first = 0;
|
|
@ -0,0 +1,9 @@
|
||||||
|
Error: No such field
|
||||||
|
╭─[assignment-tuple-out-of-bounds.txt:2:5]
|
||||||
|
│
|
||||||
|
2 │ foo.2 = 0;
|
||||||
|
· ┬
|
||||||
|
· ╰── this tuple does not have enough elements
|
||||||
|
·
|
||||||
|
· Note: The first element in a tuple has index 0
|
||||||
|
───╯
|
|
@ -0,0 +1,2 @@
|
||||||
|
let foo: (int,) = (1,);
|
||||||
|
foo.2 = 0;
|
|
@ -0,0 +1,11 @@
|
||||||
|
Error: Too few arguments
|
||||||
|
╭─[expected-2-found-1-generic.txt:4:17]
|
||||||
|
│
|
||||||
|
2 │ fn foo<V>(foo: V, bar: V) {}
|
||||||
|
· ─┬─
|
||||||
|
· ╰─── This function takes 2 arguments,
|
||||||
|
·
|
||||||
|
4 │ let _unit: () = foo::<int>(1);
|
||||||
|
· ─┬─
|
||||||
|
· ╰─── but only 1 arguments were supplied
|
||||||
|
───╯
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This function does nothing.
|
||||||
|
fn foo<V>(foo: V, bar: V) {}
|
||||||
|
|
||||||
|
let _unit: () = foo::<int>(1);
|
11
integration/fail/too-xxx-arguments/expected-2-found-1.out
Normal file
11
integration/fail/too-xxx-arguments/expected-2-found-1.out
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
Error: Too few arguments
|
||||||
|
╭─[expected-2-found-1.txt:4:17]
|
||||||
|
│
|
||||||
|
2 │ fn foo(foo: int, bar: int) {}
|
||||||
|
· ─┬─
|
||||||
|
· ╰─── This function takes 2 arguments,
|
||||||
|
·
|
||||||
|
4 │ let _unit: () = foo(1);
|
||||||
|
· ─┬─
|
||||||
|
· ╰─── but only 1 arguments were supplied
|
||||||
|
───╯
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This function does nothing.
|
||||||
|
fn foo(foo: int, bar: int) {}
|
||||||
|
|
||||||
|
let _unit: () = foo(1);
|
10
integration/fail/type-mismatch/assignment-bool-int.out
Normal file
10
integration/fail/type-mismatch/assignment-bool-int.out
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
Error: Type Mismatch
|
||||||
|
╭─[assignment-bool-int.txt:2:7]
|
||||||
|
│
|
||||||
|
1 │ let foo: bool = true;
|
||||||
|
· ──┬─
|
||||||
|
· ╰─── Expected type `bool`,
|
||||||
|
2 │ foo = 0;
|
||||||
|
· ┬
|
||||||
|
· ╰── but expression is of type `int`
|
||||||
|
───╯
|
2
integration/fail/type-mismatch/assignment-bool-int.txt
Normal file
2
integration/fail/type-mismatch/assignment-bool-int.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
let foo: bool = true;
|
||||||
|
foo = 0;
|
|
@ -0,0 +1,10 @@
|
||||||
|
Error: Type Mismatch
|
||||||
|
╭─[assignment-rc-tuple-bool-int.txt:2:9]
|
||||||
|
│
|
||||||
|
1 │ let foo: Rc<(bool,)> = Rc((true,));
|
||||||
|
· ──┬─
|
||||||
|
· ╰─── Expected type `bool`,
|
||||||
|
2 │ foo.0 = 0;
|
||||||
|
· ┬
|
||||||
|
· ╰── but expression is of type `int`
|
||||||
|
───╯
|
|
@ -0,0 +1,2 @@
|
||||||
|
let foo: Rc<(bool,)> = Rc((true,));
|
||||||
|
foo.0 = 0;
|
|
@ -0,0 +1,10 @@
|
||||||
|
Error: Type Mismatch
|
||||||
|
╭─[assignment-rc-tuple-option-rc.txt:2:9]
|
||||||
|
│
|
||||||
|
1 │ let foo: Rc<(Option<bool>,)> = Rc((None,));
|
||||||
|
· ───┬──
|
||||||
|
· ╰──── Expected type `Option<bool>`,
|
||||||
|
2 │ foo.0 = Rc(true);
|
||||||
|
· ─┬
|
||||||
|
· ╰── but expression is of type `Rc<_>`
|
||||||
|
───╯
|
|
@ -0,0 +1,2 @@
|
||||||
|
let foo: Rc<(Option<bool>,)> = Rc((None,));
|
||||||
|
foo.0 = Rc(true);
|
|
@ -0,0 +1,9 @@
|
||||||
|
Error: Type Mismatch
|
||||||
|
╭─[declaration-bool-int-arithmetic.txt:1:17]
|
||||||
|
│
|
||||||
|
1 │ let foo: bool = 1 + 2;
|
||||||
|
· ──┬─ ──┬──
|
||||||
|
· ╰─────────── Expected type `bool`,
|
||||||
|
· │
|
||||||
|
· ╰──── but expression is of type `int`
|
||||||
|
───╯
|
|
@ -0,0 +1 @@
|
||||||
|
let foo: bool = 1 + 2;
|
|
@ -0,0 +1,9 @@
|
||||||
|
Error: Type Mismatch
|
||||||
|
╭─[declaration-bool-int-negation.txt:2:17]
|
||||||
|
│
|
||||||
|
2 │ let foo: bool = -one;
|
||||||
|
· ──┬─ ┬
|
||||||
|
· ╰─────── Expected type `bool`,
|
||||||
|
· │
|
||||||
|
· ╰── but expression is of type `int`
|
||||||
|
───╯
|
|
@ -0,0 +1,2 @@
|
||||||
|
let one: int = 1;
|
||||||
|
let foo: bool = -one;
|
|
@ -0,0 +1,9 @@
|
||||||
|
Error: Type Mismatch
|
||||||
|
╭─[declaration-bool-int-tuple-bool-bool-tuple.txt:1:31]
|
||||||
|
│
|
||||||
|
1 │ let foo: (bool, int) = (true, true);
|
||||||
|
· ─┬─ ──┬─
|
||||||
|
· ╰────────────────── Expected type `int`,
|
||||||
|
· │
|
||||||
|
· ╰─── but expression is of type `bool`
|
||||||
|
───╯
|
|
@ -0,0 +1 @@
|
||||||
|
let foo: (bool, int) = (true, true);
|
9
integration/fail/type-mismatch/declaration-bool-int.out
Normal file
9
integration/fail/type-mismatch/declaration-bool-int.out
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Error: Type Mismatch
|
||||||
|
╭─[declaration-bool-int.txt:1:17]
|
||||||
|
│
|
||||||
|
1 │ let foo: bool = 0;
|
||||||
|
· ──┬─ ┬
|
||||||
|
· ╰─────── Expected type `bool`,
|
||||||
|
· │
|
||||||
|
· ╰── but expression is of type `int`
|
||||||
|
───╯
|
1
integration/fail/type-mismatch/declaration-bool-int.txt
Normal file
1
integration/fail/type-mismatch/declaration-bool-int.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
let foo: bool = 0;
|
|
@ -0,0 +1,9 @@
|
||||||
|
Error: Type Mismatch
|
||||||
|
╭─[declaration-int-bool-conjunction.txt:1:16]
|
||||||
|
│
|
||||||
|
1 │ let foo: int = true && true;
|
||||||
|
· ─┬─ ──────┬─────
|
||||||
|
· ╰────────────────── Expected type `int`,
|
||||||
|
· │
|
||||||
|
· ╰─────── but expression is of type `bool`
|
||||||
|
───╯
|
|
@ -0,0 +1 @@
|
||||||
|
let foo: int = true && true;
|
|
@ -0,0 +1,9 @@
|
||||||
|
Error: Type Mismatch
|
||||||
|
╭─[declaration-int-bool-negation.txt:2:16]
|
||||||
|
│
|
||||||
|
2 │ let foo: int = !yes;
|
||||||
|
· ─┬─ ┬
|
||||||
|
· ╰─────── Expected type `int`,
|
||||||
|
· │
|
||||||
|
· ╰── but expression is of type `bool`
|
||||||
|
───╯
|
|
@ -0,0 +1,2 @@
|
||||||
|
let yes: bool = true;
|
||||||
|
let foo: int = !yes;
|
9
integration/fail/type-mismatch/declaration-int-bool.out
Normal file
9
integration/fail/type-mismatch/declaration-int-bool.out
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Error: Type Mismatch
|
||||||
|
╭─[declaration-int-bool.txt:1:16]
|
||||||
|
│
|
||||||
|
1 │ let foo: int = true;
|
||||||
|
· ─┬─ ──┬─
|
||||||
|
· ╰────────── Expected type `int`,
|
||||||
|
· │
|
||||||
|
· ╰─── but expression is of type `bool`
|
||||||
|
───╯
|
1
integration/fail/type-mismatch/declaration-int-bool.txt
Normal file
1
integration/fail/type-mismatch/declaration-int-bool.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
let foo: int = true;
|
|
@ -0,0 +1,9 @@
|
||||||
|
Error: Type Mismatch
|
||||||
|
╭─[declaration-int-int-comparison.txt:1:16]
|
||||||
|
│
|
||||||
|
1 │ let foo: int = 1 == 2;
|
||||||
|
· ─┬─ ───┬──
|
||||||
|
· ╰──────────── Expected type `int`,
|
||||||
|
· │
|
||||||
|
· ╰──── but expression is of type `bool`
|
||||||
|
───╯
|
|
@ -0,0 +1 @@
|
||||||
|
let foo: int = 1 == 2;
|
|
@ -0,0 +1,9 @@
|
||||||
|
Error: Type Mismatch
|
||||||
|
╭─[declaration-int-tuple-bool-tuple.txt:1:20]
|
||||||
|
│
|
||||||
|
1 │ let foo: (int,) = (true,);
|
||||||
|
· ─┬─ ──┬─
|
||||||
|
· ╰───────────── Expected type `int`,
|
||||||
|
· │
|
||||||
|
· ╰─── but expression is of type `bool`
|
||||||
|
───╯
|
|
@ -0,0 +1 @@
|
||||||
|
let foo: (int,) = (true,);
|
|
@ -0,0 +1,9 @@
|
||||||
|
Error: Type Mismatch
|
||||||
|
╭─[declaration-unit-one-tuple.txt:1:15]
|
||||||
|
│
|
||||||
|
1 │ let foo: () = (1,);
|
||||||
|
· ─┬ ──┬─
|
||||||
|
· ╰───────── Expected tuple with 0 elements,
|
||||||
|
· │
|
||||||
|
· ╰─── but expression has 1 elements
|
||||||
|
───╯
|
|
@ -0,0 +1 @@
|
||||||
|
let foo: () = (1,);
|
|
@ -0,0 +1,9 @@
|
||||||
|
Error: Type Mismatch
|
||||||
|
╭─[expression-arithmetic-int-bool.txt:1:20]
|
||||||
|
│
|
||||||
|
1 │ let foo: int = 1 + true;
|
||||||
|
· ┬ ──┬─
|
||||||
|
· ╰─────── Expected type `int`,
|
||||||
|
· │
|
||||||
|
· ╰─── but expression is of type `bool`
|
||||||
|
───╯
|
|
@ -0,0 +1 @@
|
||||||
|
let foo: int = 1 + true;
|
|
@ -0,0 +1,9 @@
|
||||||
|
Error: Type Mismatch
|
||||||
|
╭─[expression-comparison-int-bool.txt:1:22]
|
||||||
|
│
|
||||||
|
1 │ let foo: bool = 1 == true;
|
||||||
|
· ─┬ ──┬─
|
||||||
|
· ╰─────── Expected type `int`,
|
||||||
|
· │
|
||||||
|
· ╰─── but expression is of type `bool`
|
||||||
|
───╯
|
|
@ -0,0 +1 @@
|
||||||
|
let foo: bool = 1 == true;
|
|
@ -0,0 +1,9 @@
|
||||||
|
Error: Type Mismatch
|
||||||
|
╭─[expression-conjunction-bool-int.txt:1:25]
|
||||||
|
│
|
||||||
|
1 │ let foo: bool = true || 1;
|
||||||
|
· ─┬ ┬
|
||||||
|
· ╰──── Expected type `bool`,
|
||||||
|
· │
|
||||||
|
· ╰── but expression is of type `int`
|
||||||
|
───╯
|
|
@ -0,0 +1 @@
|
||||||
|
let foo: bool = true || 1;
|
9
integration/fail/type-mismatch/expression-int-0.out
Normal file
9
integration/fail/type-mismatch/expression-int-0.out
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Error: Type Mismatch
|
||||||
|
╭─[expression-int-0.txt:2:16]
|
||||||
|
│
|
||||||
|
2 │ let bar: int = foo.0;
|
||||||
|
· ──┬─┬
|
||||||
|
· ╰──── Expression is of type `int`,
|
||||||
|
· │
|
||||||
|
· ╰── but expected type `(_, ..)`
|
||||||
|
───╯
|
2
integration/fail/type-mismatch/expression-int-0.txt
Normal file
2
integration/fail/type-mismatch/expression-int-0.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
let foo: int = 1;
|
||||||
|
let bar: int = foo.0;
|
9
integration/fail/type-mismatch/expression-minus-bool.out
Normal file
9
integration/fail/type-mismatch/expression-minus-bool.out
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Error: Type Mismatch
|
||||||
|
╭─[expression-minus-bool.txt:1:17]
|
||||||
|
│
|
||||||
|
1 │ let foo: int = -true;
|
||||||
|
· ┬──┬─
|
||||||
|
· ╰────── Expected type `int`,
|
||||||
|
· │
|
||||||
|
· ╰─── but expression is of type `bool`
|
||||||
|
───╯
|
1
integration/fail/type-mismatch/expression-minus-bool.txt
Normal file
1
integration/fail/type-mismatch/expression-minus-bool.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
let foo: int = -true;
|
9
integration/fail/type-mismatch/expression-not-int.out
Normal file
9
integration/fail/type-mismatch/expression-not-int.out
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Error: Type Mismatch
|
||||||
|
╭─[expression-not-int.txt:1:18]
|
||||||
|
│
|
||||||
|
1 │ let foo: bool = !0;
|
||||||
|
· ┬┬
|
||||||
|
· ╰─── Expected type `bool`,
|
||||||
|
· │
|
||||||
|
· ╰── but expression is of type `int`
|
||||||
|
───╯
|
1
integration/fail/type-mismatch/expression-not-int.txt
Normal file
1
integration/fail/type-mismatch/expression-not-int.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
let foo: bool = !0;
|
9
integration/fail/type-mismatch/expression-unit-0.out
Normal file
9
integration/fail/type-mismatch/expression-unit-0.out
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Error: Type Mismatch
|
||||||
|
╭─[expression-unit-0.txt:1:16]
|
||||||
|
│
|
||||||
|
1 │ let foo: int = ().0;
|
||||||
|
· ─┬┬─
|
||||||
|
· ╰──── Found tuple with 0 elements,
|
||||||
|
· │
|
||||||
|
· ╰─── but expression requires at least 1 elements
|
||||||
|
───╯
|
1
integration/fail/type-mismatch/expression-unit-0.txt
Normal file
1
integration/fail/type-mismatch/expression-unit-0.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
let foo: int = ().0;
|
9
integration/fail/type-mismatch/if-condition-int.out
Normal file
9
integration/fail/type-mismatch/if-condition-int.out
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Error: Type Mismatch
|
||||||
|
╭─[if-condition-int.txt:1:4]
|
||||||
|
│
|
||||||
|
1 │ if 0 {}
|
||||||
|
· ─┬ ┬
|
||||||
|
· ╰──── Expected type `bool`,
|
||||||
|
· │
|
||||||
|
· ╰── but expression is of type `int`
|
||||||
|
───╯
|
1
integration/fail/type-mismatch/if-condition-int.txt
Normal file
1
integration/fail/type-mismatch/if-condition-int.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
if 0 {}
|
9
integration/fail/type-mismatch/while-condition-int.out
Normal file
9
integration/fail/type-mismatch/while-condition-int.out
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Error: Type Mismatch
|
||||||
|
╭─[while-condition-int.txt:1:7]
|
||||||
|
│
|
||||||
|
1 │ while 0 {}
|
||||||
|
· ──┬── ┬
|
||||||
|
· ╰────── Expected type `bool`,
|
||||||
|
· │
|
||||||
|
· ╰── but expression is of type `int`
|
||||||
|
───╯
|
1
integration/fail/type-mismatch/while-condition-int.txt
Normal file
1
integration/fail/type-mismatch/while-condition-int.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
while 0 {}
|
7
integration/fail/unknown-type/declaration.out
Normal file
7
integration/fail/unknown-type/declaration.out
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Error: Unknown type
|
||||||
|
╭─[declaration.txt:1:10]
|
||||||
|
│
|
||||||
|
1 │ let foo: Foo = 0;
|
||||||
|
· ─┬─
|
||||||
|
· ╰─── This type has not been defined
|
||||||
|
───╯
|
1
integration/fail/unknown-type/declaration.txt
Normal file
1
integration/fail/unknown-type/declaration.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
let foo: Foo = 0;
|
116
integration/main.rs
Normal file
116
integration/main.rs
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
#![warn(rust_2018_idioms)]
|
||||||
|
#![deny(elided_lifetimes_in_paths)]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
|
use compiler::{
|
||||||
|
compile::{compile, CompileResult},
|
||||||
|
diagnostic::{Diagnostic, Source}
|
||||||
|
};
|
||||||
|
use lazy_regex::regex_replace_all;
|
||||||
|
use libtest::{run_tests, Arguments, Outcome, Test};
|
||||||
|
use std::{
|
||||||
|
fs::{self, File},
|
||||||
|
io::{Read as _, Write as _},
|
||||||
|
panic::catch_unwind,
|
||||||
|
path::{Path, PathBuf}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TestData {
|
||||||
|
path: PathBuf
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_test(data: &TestData) -> anyhow::Result<()> {
|
||||||
|
let mut file = File::open(&data.path)?;
|
||||||
|
let mut input = String::new();
|
||||||
|
file.read_to_string(&mut input)?;
|
||||||
|
drop(file);
|
||||||
|
|
||||||
|
let compile_result = match catch_unwind(|| compile(&input)) {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(e) => match e.downcast_ref::<String>() {
|
||||||
|
Some(str) => anyhow::bail!("The compiler paniced: {str}"),
|
||||||
|
None => match e.downcast_ref::<&str>() {
|
||||||
|
Some(str) => anyhow::bail!("The compiler paniced: {str}"),
|
||||||
|
None => anyhow::bail!("The compiler paniced")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let msg = match compile_result {
|
||||||
|
Ok(CompileResult::Ok { .. }) => anyhow::bail!("Code compiled successfully"),
|
||||||
|
Ok(CompileResult::InternalErr(code, err)) => {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
Diagnostic::ice(err).write(Source::new(&code), &mut buf)?;
|
||||||
|
String::from_utf8(buf).unwrap()
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
let filename = data.path.file_name().unwrap().to_str().unwrap();
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
err.write(Source::new(&input).with_filename(filename), &mut buf)?;
|
||||||
|
String::from_utf8(buf).unwrap()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// remove colors from the msg
|
||||||
|
let msg = regex_replace_all!("\x1B\\[[^m]+m", &msg, |_| "");
|
||||||
|
|
||||||
|
let mut path = data.path.clone();
|
||||||
|
path.set_extension("out");
|
||||||
|
if !path.exists() {
|
||||||
|
let mut file = File::create(&path)?;
|
||||||
|
write!(file, "{msg}")?;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
let mut file = File::open(&path)?;
|
||||||
|
let mut buf = String::new();
|
||||||
|
file.read_to_string(&mut buf)?;
|
||||||
|
drop(file);
|
||||||
|
|
||||||
|
if msg == buf {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(anyhow::anyhow!(
|
||||||
|
"Error: Expected output\n\n{buf}\n\nActual output\n\n{msg}"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_tests_from_dir<P>(tests: &mut Vec<Test<TestData>>, path: P) -> anyhow::Result<()>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>
|
||||||
|
{
|
||||||
|
for file in fs::read_dir(path)? {
|
||||||
|
let file = file?;
|
||||||
|
let path = file.path();
|
||||||
|
let ty = file.file_type()?;
|
||||||
|
if ty.is_dir() {
|
||||||
|
add_tests_from_dir(tests, &path)?;
|
||||||
|
} else if ty.is_file()
|
||||||
|
&& path.extension().map(|ext| ext == "txt").unwrap_or(false)
|
||||||
|
{
|
||||||
|
tests.push(Test {
|
||||||
|
name: path.display().to_string(),
|
||||||
|
kind: "".into(),
|
||||||
|
is_ignored: false,
|
||||||
|
is_bench: false,
|
||||||
|
data: TestData { path }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> anyhow::Result<()> {
|
||||||
|
let args = Arguments::from_args();
|
||||||
|
|
||||||
|
let mut tests = Vec::new();
|
||||||
|
add_tests_from_dir(&mut tests, "integration/fail")?;
|
||||||
|
|
||||||
|
run_tests(&args, tests, |test| match run_test(&test.data) {
|
||||||
|
Ok(()) => Outcome::Passed,
|
||||||
|
Err(err) => Outcome::Failed {
|
||||||
|
msg: Some(format!("{err:?}"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exit();
|
||||||
|
}
|
35
rustfmt.toml
Normal file
35
rustfmt.toml
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
edition = "2021"
|
||||||
|
max_width = 90
|
||||||
|
newline_style = "Unix"
|
||||||
|
unstable_features = true
|
||||||
|
|
||||||
|
# always use tabs.
|
||||||
|
hard_tabs = true
|
||||||
|
tab_spaces = 4
|
||||||
|
|
||||||
|
# commas inbetween but not after
|
||||||
|
match_block_trailing_comma = true
|
||||||
|
trailing_comma = "Never"
|
||||||
|
|
||||||
|
# fix my imports for me
|
||||||
|
imports_granularity = "Crate"
|
||||||
|
group_imports = "One"
|
||||||
|
|
||||||
|
# format everything
|
||||||
|
format_code_in_doc_comments = true
|
||||||
|
format_macro_matchers = true
|
||||||
|
|
||||||
|
# don't keep outdated syntax
|
||||||
|
use_field_init_shorthand = true
|
||||||
|
use_try_shorthand = true
|
||||||
|
|
||||||
|
# condense Struct { _, _ } to Struct { .. }
|
||||||
|
condense_wildcard_suffixes = true
|
||||||
|
|
||||||
|
# prefer foo(Bar { \n }) over foo(\nBar { \n }\n)
|
||||||
|
overflow_delimited_expr = true
|
||||||
|
|
||||||
|
# I wish there was a way to allow 0..n but not a + 1..b + 2
|
||||||
|
# However, the later looks so terible that I use spaces everywhere
|
||||||
|
# https://github.com/rust-lang/rustfmt/issues/3367
|
||||||
|
spaces_around_ranges = true
|
30
src/ast/assignment.rs
Normal file
30
src/ast/assignment.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
use super::Expression;
|
||||||
|
use syn::{
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
punctuated::Punctuated,
|
||||||
|
spanned::Spanned,
|
||||||
|
Member, Token
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Assignment {
|
||||||
|
pub lhs: Punctuated<Member, Token![.]>,
|
||||||
|
pub eq_token: Token![=],
|
||||||
|
pub rhs: Expression,
|
||||||
|
pub semi_token: Token![;]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Assignment {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
let lhs = Punctuated::parse_separated_nonempty(input)?;
|
||||||
|
if lhs.trailing_punct() {
|
||||||
|
bail!(lhs.span() => "Expected ident after fullstop");
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
lhs,
|
||||||
|
eq_token: input.parse()?,
|
||||||
|
rhs: input.parse()?,
|
||||||
|
semi_token: input.parse()?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
84
src/ast/conditional.rs
Normal file
84
src/ast/conditional.rs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
use super::{Block, Expression};
|
||||||
|
use proc_macro2::{Ident, Span};
|
||||||
|
use syn::{
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
token::{Brace, Paren},
|
||||||
|
Token
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Condition {
|
||||||
|
Bool(Expression),
|
||||||
|
LetSome {
|
||||||
|
let_token: Token![let],
|
||||||
|
some_span: Span,
|
||||||
|
paren_token: Paren,
|
||||||
|
ident: Ident,
|
||||||
|
eq_token: Token![=],
|
||||||
|
expr: Expression
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Condition {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
Ok(if input.peek(Token![let]) {
|
||||||
|
let content;
|
||||||
|
Self::LetSome {
|
||||||
|
let_token: input.parse()?,
|
||||||
|
some_span: {
|
||||||
|
let some: Ident = input.parse()?;
|
||||||
|
if some != "Some" {
|
||||||
|
bail!(some.span() => "Expected `Some`");
|
||||||
|
}
|
||||||
|
some.span()
|
||||||
|
},
|
||||||
|
paren_token: syn::parenthesized!(content in input),
|
||||||
|
ident: content.parse()?,
|
||||||
|
eq_token: input.parse()?,
|
||||||
|
expr: input.parse()?
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Self::Bool(input.parse()?)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum ElseBranch {
|
||||||
|
If(Box<If>),
|
||||||
|
Block(Block)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for ElseBranch {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
Ok(if input.peek(Token![if]) {
|
||||||
|
Self::If(input.parse()?)
|
||||||
|
} else if input.peek(Brace) {
|
||||||
|
Self::Block(input.parse()?)
|
||||||
|
} else {
|
||||||
|
bail!(input.span() => "Expected `if` or block");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct If {
|
||||||
|
pub if_token: Token![if],
|
||||||
|
pub condition: Condition,
|
||||||
|
pub then_branch: Block,
|
||||||
|
pub else_branch: Option<(Token![else], ElseBranch)>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for If {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
if_token: input.parse()?,
|
||||||
|
condition: input.parse()?,
|
||||||
|
then_branch: input.parse()?,
|
||||||
|
else_branch: input
|
||||||
|
.peek(Token![else])
|
||||||
|
.then(|| syn::Result::Ok((input.parse()?, input.parse()?)))
|
||||||
|
.transpose()?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
31
src/ast/declaration.rs
Normal file
31
src/ast/declaration.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
use super::{Expression, Type};
|
||||||
|
use proc_macro2::Ident;
|
||||||
|
use syn::{
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
Token
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Declaration {
|
||||||
|
pub let_token: Token![let],
|
||||||
|
pub ident: Ident,
|
||||||
|
pub colon_token: Token![:],
|
||||||
|
pub ty: Type,
|
||||||
|
pub eq_token: Token![=],
|
||||||
|
pub expr: Expression,
|
||||||
|
pub semi_token: Token![;]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Declaration {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
let_token: input.parse()?,
|
||||||
|
ident: input.parse()?,
|
||||||
|
colon_token: input.parse()?,
|
||||||
|
ty: input.parse()?,
|
||||||
|
eq_token: input.parse()?,
|
||||||
|
expr: input.parse()?,
|
||||||
|
semi_token: input.parse()?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
433
src/ast/expression/mod.rs
Normal file
433
src/ast/expression/mod.rs
Normal file
|
@ -0,0 +1,433 @@
|
||||||
|
use super::{Generics, Type};
|
||||||
|
use proc_macro2::{Ident, Span};
|
||||||
|
use std::fmt::{self, Debug, Display, Formatter};
|
||||||
|
use syn::{
|
||||||
|
punctuated::Punctuated, spanned::Spanned, token::Paren, Index, LitBool, LitInt, Token
|
||||||
|
};
|
||||||
|
|
||||||
|
mod parse;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum ArithmeticOp {
|
||||||
|
Add(Token![+]),
|
||||||
|
Sub(Token![-]),
|
||||||
|
Mul(Token![*]),
|
||||||
|
Div(Token![/])
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArithmeticOp {
|
||||||
|
pub fn is_add_or_sub_op(self) -> bool {
|
||||||
|
matches!(self, Self::Add(_) | Self::Sub(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_mul_or_div_op(self) -> bool {
|
||||||
|
matches!(self, Self::Mul(_) | Self::Div(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn char(self) -> char {
|
||||||
|
match self {
|
||||||
|
ArithmeticOp::Add(_) => '+',
|
||||||
|
ArithmeticOp::Sub(_) => '-',
|
||||||
|
ArithmeticOp::Mul(_) => '*',
|
||||||
|
ArithmeticOp::Div(_) => '/'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Spanned for ArithmeticOp {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
match self {
|
||||||
|
Self::Add(token) => token.span,
|
||||||
|
Self::Sub(token) => token.span,
|
||||||
|
Self::Mul(token) => token.span,
|
||||||
|
Self::Div(token) => token.span
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ArithmeticOp {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.char())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum ComparisonOp {
|
||||||
|
Eq(Token![==]),
|
||||||
|
Neq(Token![!=]),
|
||||||
|
Lt(Token![<]),
|
||||||
|
Le(Token![<=]),
|
||||||
|
Gt(Token![>]),
|
||||||
|
Ge(Token![>=])
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComparisonOp {
|
||||||
|
fn str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
ComparisonOp::Eq(_) => "==",
|
||||||
|
ComparisonOp::Neq(_) => "!=",
|
||||||
|
ComparisonOp::Lt(_) => "<",
|
||||||
|
ComparisonOp::Le(_) => "<=",
|
||||||
|
ComparisonOp::Gt(_) => ">",
|
||||||
|
ComparisonOp::Ge(_) => ">="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Spanned for ComparisonOp {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
match self {
|
||||||
|
Self::Eq(token) => token.span(),
|
||||||
|
Self::Neq(token) => token.span(),
|
||||||
|
Self::Lt(token) => token.span,
|
||||||
|
Self::Le(token) => token.span(),
|
||||||
|
Self::Gt(token) => token.span,
|
||||||
|
Self::Ge(token) => token.span()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ComparisonOp {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(self.str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum ConjunctionOp {
|
||||||
|
And(Token![&&]),
|
||||||
|
Or(Token![||])
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConjunctionOp {
|
||||||
|
fn str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
ConjunctionOp::And(_) => "&&",
|
||||||
|
ConjunctionOp::Or(_) => "||"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Spanned for ConjunctionOp {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
match self {
|
||||||
|
Self::And(token) => token.span(),
|
||||||
|
Self::Or(token) => token.span()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ConjunctionOp {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(self.str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum Expression {
|
||||||
|
/// An expression enclosed in parentheses.
|
||||||
|
Term {
|
||||||
|
paren_token: Paren,
|
||||||
|
expr: Box<Expression>
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Variable access.
|
||||||
|
Ident(Ident),
|
||||||
|
|
||||||
|
/// A boolean literal.
|
||||||
|
Bool(LitBool),
|
||||||
|
|
||||||
|
/// A number literal.
|
||||||
|
Literal(LitInt),
|
||||||
|
|
||||||
|
/// A boolean negation (`!expr`).
|
||||||
|
BoolNegation {
|
||||||
|
not_token: Token![!],
|
||||||
|
expr: Box<Expression>
|
||||||
|
},
|
||||||
|
|
||||||
|
/// An integer negation (`-expr`).
|
||||||
|
IntNegation {
|
||||||
|
minus_token: Token![-],
|
||||||
|
expr: Box<Expression>
|
||||||
|
},
|
||||||
|
|
||||||
|
/// An arithmetic expression.
|
||||||
|
Arithmetic {
|
||||||
|
lhs: Box<Expression>,
|
||||||
|
op: ArithmeticOp,
|
||||||
|
rhs: Box<Expression>
|
||||||
|
},
|
||||||
|
|
||||||
|
/// A comparison.
|
||||||
|
Comparison {
|
||||||
|
lhs: Box<Expression>,
|
||||||
|
op: ComparisonOp,
|
||||||
|
rhs: Box<Expression>
|
||||||
|
},
|
||||||
|
|
||||||
|
/// A conjunction or disjunction.
|
||||||
|
Conjunction {
|
||||||
|
lhs: Box<Expression>,
|
||||||
|
op: ConjunctionOp,
|
||||||
|
rhs: Box<Expression>
|
||||||
|
},
|
||||||
|
|
||||||
|
/// A tuple constructor.
|
||||||
|
TupleCtor {
|
||||||
|
paren_token: Paren,
|
||||||
|
elems: Punctuated<Expression, Token![,]>
|
||||||
|
},
|
||||||
|
|
||||||
|
/// A tuple index expression.
|
||||||
|
TupleIndex {
|
||||||
|
expr: Box<Expression>,
|
||||||
|
dot_token: Token![.],
|
||||||
|
index: Index
|
||||||
|
},
|
||||||
|
|
||||||
|
/// An `Rc` constructor.
|
||||||
|
RcCtor {
|
||||||
|
span: Span,
|
||||||
|
paren_token: Paren,
|
||||||
|
expr: Box<Expression>
|
||||||
|
},
|
||||||
|
|
||||||
|
/// An `Option` constructor.
|
||||||
|
OptionCtor {
|
||||||
|
span: Span,
|
||||||
|
expr: Option<(Paren, Box<Expression>)>
|
||||||
|
},
|
||||||
|
|
||||||
|
/// A deref expression.
|
||||||
|
Deref {
|
||||||
|
star_token: Token![*],
|
||||||
|
expr: Box<Expression>
|
||||||
|
},
|
||||||
|
|
||||||
|
/// A function call.
|
||||||
|
FnCall {
|
||||||
|
ident: Ident,
|
||||||
|
generics: Option<Generics<Type>>,
|
||||||
|
paren_token: Paren,
|
||||||
|
inputs: Punctuated<Expression, Token![,]>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Expression {
|
||||||
|
/// Replace the rightmost part of this expression with the return value
|
||||||
|
/// of the callback. If self is not a binary expression, or if the filter
|
||||||
|
/// callback returns false, calls the callback with self.
|
||||||
|
fn replace_rightmost<F, C>(self, filter: F, callback: C) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(&Self) -> bool,
|
||||||
|
C: FnOnce(Self) -> Self
|
||||||
|
{
|
||||||
|
if !filter(&self) {
|
||||||
|
return callback(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Self::Arithmetic { lhs, op, rhs } => Self::Arithmetic {
|
||||||
|
lhs,
|
||||||
|
op,
|
||||||
|
rhs: Box::new(rhs.replace_rightmost(filter, callback))
|
||||||
|
},
|
||||||
|
Self::Comparison { lhs, op, rhs } => Self::Comparison {
|
||||||
|
lhs,
|
||||||
|
op,
|
||||||
|
rhs: Box::new(rhs.replace_rightmost(filter, callback))
|
||||||
|
},
|
||||||
|
Self::Conjunction { lhs, op, rhs } => Self::Conjunction {
|
||||||
|
lhs,
|
||||||
|
op,
|
||||||
|
rhs: Box::new(rhs.replace_rightmost(filter, callback))
|
||||||
|
},
|
||||||
|
|
||||||
|
this => callback(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Spanned for Expression {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
match self {
|
||||||
|
Self::Term { paren_token, .. } => paren_token.span,
|
||||||
|
Self::Ident(ident) => ident.span(),
|
||||||
|
Self::Bool(bool) => bool.span(),
|
||||||
|
Self::Literal(lit) => lit.span(),
|
||||||
|
Self::BoolNegation { not_token, .. } => not_token.span,
|
||||||
|
Self::IntNegation { minus_token, .. } => minus_token.span,
|
||||||
|
Self::Arithmetic { lhs, op, rhs } => lhs
|
||||||
|
.span()
|
||||||
|
.join(op.span())
|
||||||
|
.unwrap()
|
||||||
|
.join(rhs.span())
|
||||||
|
.unwrap(),
|
||||||
|
Self::Comparison { lhs, op, rhs } => lhs
|
||||||
|
.span()
|
||||||
|
.join(op.span())
|
||||||
|
.unwrap()
|
||||||
|
.join(rhs.span())
|
||||||
|
.unwrap(),
|
||||||
|
Self::Conjunction { lhs, op, rhs } => lhs
|
||||||
|
.span()
|
||||||
|
.join(op.span())
|
||||||
|
.unwrap()
|
||||||
|
.join(rhs.span())
|
||||||
|
.unwrap(),
|
||||||
|
Self::TupleCtor { paren_token, .. } => paren_token.span,
|
||||||
|
Self::TupleIndex {
|
||||||
|
expr,
|
||||||
|
dot_token,
|
||||||
|
index
|
||||||
|
} => expr
|
||||||
|
.span()
|
||||||
|
.join(dot_token.span())
|
||||||
|
.unwrap()
|
||||||
|
.join(index.span())
|
||||||
|
.unwrap(),
|
||||||
|
Self::RcCtor { span, .. } => *span,
|
||||||
|
Self::OptionCtor { span, .. } => *span,
|
||||||
|
Self::Deref { star_token, expr } => {
|
||||||
|
star_token.span.join(expr.span()).unwrap()
|
||||||
|
},
|
||||||
|
Self::FnCall { ident, .. } => ident.span()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Expression {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Term { expr, .. } => f
|
||||||
|
.debug_struct("Expression::Term")
|
||||||
|
.field("expr", &expr)
|
||||||
|
.finish_non_exhaustive(),
|
||||||
|
Self::Ident(ident) => {
|
||||||
|
f.debug_tuple("Expression::Ident").field(ident).finish()
|
||||||
|
},
|
||||||
|
Self::Bool(lit) => {
|
||||||
|
f.debug_tuple("Expression::Bool").field(&lit.value).finish()
|
||||||
|
},
|
||||||
|
Self::Literal(lit) => f
|
||||||
|
.debug_tuple("Expression::Literal")
|
||||||
|
.field(&lit.base10_parse::<isize>())
|
||||||
|
.finish(),
|
||||||
|
Self::BoolNegation { expr, .. } => f
|
||||||
|
.debug_struct("Expression::BoolNegation")
|
||||||
|
.field("expr", &expr)
|
||||||
|
.finish_non_exhaustive(),
|
||||||
|
Self::IntNegation { expr, .. } => f
|
||||||
|
.debug_struct("Expression::IntNegation")
|
||||||
|
.field("expr", &expr)
|
||||||
|
.finish_non_exhaustive(),
|
||||||
|
Self::Arithmetic { lhs, op, rhs } => f
|
||||||
|
.debug_struct("Expression::Arithmetic")
|
||||||
|
.field("lhs", &lhs)
|
||||||
|
.field("op", &op.char())
|
||||||
|
.field("rhs", &rhs)
|
||||||
|
.finish(),
|
||||||
|
Self::Comparison { lhs, op, rhs } => f
|
||||||
|
.debug_struct("Expression::Comparison")
|
||||||
|
.field("lhs", &lhs)
|
||||||
|
.field("op", &op.str())
|
||||||
|
.field("rhs", &rhs)
|
||||||
|
.finish(),
|
||||||
|
Self::Conjunction { lhs, op, rhs } => f
|
||||||
|
.debug_struct("Expression::Conjunction")
|
||||||
|
.field("lhs", &lhs)
|
||||||
|
.field("op", &op.str())
|
||||||
|
.field("rhs", &rhs)
|
||||||
|
.finish(),
|
||||||
|
Self::TupleCtor { elems, .. } => f
|
||||||
|
.debug_struct("Expression::TupleCtor")
|
||||||
|
.field("elems", &elems)
|
||||||
|
.finish_non_exhaustive(),
|
||||||
|
Self::TupleIndex { expr, index, .. } => f
|
||||||
|
.debug_struct("Expression::TupleCtor")
|
||||||
|
.field("expr", &expr)
|
||||||
|
.field("index", &index.index)
|
||||||
|
.finish_non_exhaustive(),
|
||||||
|
Self::RcCtor { expr, .. } => f
|
||||||
|
.debug_struct("Expression::RcCtor")
|
||||||
|
.field("expr", &expr)
|
||||||
|
.finish_non_exhaustive(),
|
||||||
|
Self::OptionCtor { expr, .. } => f
|
||||||
|
.debug_struct("Expression::OptionCtor")
|
||||||
|
.field("expr", &expr.as_ref().map(|(_, expr)| expr))
|
||||||
|
.finish_non_exhaustive(),
|
||||||
|
Self::Deref { expr, .. } => f
|
||||||
|
.debug_struct("Expression::Deref")
|
||||||
|
.field("expr", &expr)
|
||||||
|
.finish_non_exhaustive(),
|
||||||
|
Self::FnCall { ident, .. } => f
|
||||||
|
.debug_struct("Expression::FnCall")
|
||||||
|
.field("ident", &ident)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Expression {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Term { expr, .. } => write!(f, "({{ {expr} }})"),
|
||||||
|
Self::Ident(ident) => {
|
||||||
|
write!(f, "{ident}")
|
||||||
|
},
|
||||||
|
Self::Bool(lit) => match lit.value() {
|
||||||
|
true => f.write_str("true"),
|
||||||
|
false => f.write_str("false")
|
||||||
|
},
|
||||||
|
Self::Literal(lit) => f.write_str(lit.base10_digits()),
|
||||||
|
Self::BoolNegation { expr, .. } => write!(f, "!{{ {expr} }}"),
|
||||||
|
Self::IntNegation { expr, .. } => write!(f, "-{{ {expr} }}"),
|
||||||
|
Self::Arithmetic { lhs, op, rhs } => {
|
||||||
|
write!(f, "{{ {lhs} }} {op} {{ {rhs} }}")
|
||||||
|
},
|
||||||
|
Self::Comparison { lhs, op, rhs } => {
|
||||||
|
write!(f, "{{ {lhs} }} {op} {{ {rhs} }}")
|
||||||
|
},
|
||||||
|
Self::Conjunction { lhs, op, rhs } => {
|
||||||
|
write!(f, "{{ {lhs} }} {op} {{ {rhs} }}")
|
||||||
|
},
|
||||||
|
Self::TupleCtor { elems, .. } => {
|
||||||
|
f.write_str("(")?;
|
||||||
|
for elem in elems.iter() {
|
||||||
|
write!(f, "{{ {elem} }}, ")?;
|
||||||
|
}
|
||||||
|
f.write_str(")")
|
||||||
|
},
|
||||||
|
Self::TupleIndex { expr, index, .. } => {
|
||||||
|
write!(f, "{{ {expr} }}.{}", index.index)
|
||||||
|
},
|
||||||
|
Self::RcCtor { expr, .. } => write!(f, "Rc({{ {expr} }})"),
|
||||||
|
Self::OptionCtor { expr: None, .. } => write!(f, "None"),
|
||||||
|
Self::OptionCtor {
|
||||||
|
expr: Some((_, expr)),
|
||||||
|
..
|
||||||
|
} => write!(f, "Some({{ {expr} }})"),
|
||||||
|
Self::Deref { expr, .. } => write!(f, "*{{ {expr} }}"),
|
||||||
|
Self::FnCall {
|
||||||
|
ident,
|
||||||
|
generics,
|
||||||
|
inputs,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
write!(f, "{ident}")?;
|
||||||
|
if let Some(g) = generics {
|
||||||
|
write!(f, "::{g}")?;
|
||||||
|
}
|
||||||
|
f.write_str("(")?;
|
||||||
|
for arg in inputs.iter() {
|
||||||
|
write!(f, "{{ {arg} }}, ")?;
|
||||||
|
}
|
||||||
|
f.write_str(")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
300
src/ast/expression/parse.rs
Normal file
300
src/ast/expression/parse.rs
Normal file
|
@ -0,0 +1,300 @@
|
||||||
|
use super::{ArithmeticOp, ComparisonOp, ConjunctionOp, Expression};
|
||||||
|
use proc_macro2::Ident;
|
||||||
|
use syn::{
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
punctuated::Punctuated,
|
||||||
|
token::Paren,
|
||||||
|
Index, LitBool, LitInt, Token
|
||||||
|
};
|
||||||
|
|
||||||
|
macro_rules! parse_next_op {
|
||||||
|
($left:ident, $input:ident,Self:: $fn:ident, $op:ident:: $variant:ident) => {
|
||||||
|
(
|
||||||
|
Self::$fn(
|
||||||
|
$left,
|
||||||
|
$op::$variant($input.parse()?),
|
||||||
|
Self::parse_first($input)?
|
||||||
|
),
|
||||||
|
false
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! reorder_expr {
|
||||||
|
($lhs:ident, $op:ident, $rhs:ident => Self::$new_variant:ident {
|
||||||
|
$(Self::$variant:ident {
|
||||||
|
$lhs_lhs:ident,
|
||||||
|
$lhs_op:ident,
|
||||||
|
$lhs_rhs:ident
|
||||||
|
} $(if $condition:expr)?),*
|
||||||
|
}) => {{
|
||||||
|
// This method will not be called when there are not variants in the macro
|
||||||
|
// Also, the variables in the pattern can only be used by the if condition
|
||||||
|
#[allow(dead_code, unused_variables)]
|
||||||
|
fn filter(expr: &Expression) -> bool {
|
||||||
|
match expr {
|
||||||
|
$(Expression::$variant {
|
||||||
|
lhs: $lhs_lhs,
|
||||||
|
op: $lhs_op,
|
||||||
|
rhs: $lhs_rhs
|
||||||
|
} $(if $condition)? => true,)*
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match $lhs {
|
||||||
|
$(Self::$variant {
|
||||||
|
lhs: $lhs_lhs,
|
||||||
|
op: $lhs_op,
|
||||||
|
rhs: $lhs_rhs
|
||||||
|
} $(if $condition)? => {
|
||||||
|
Self::$variant {
|
||||||
|
lhs: $lhs_lhs,
|
||||||
|
op: $lhs_op,
|
||||||
|
rhs: $lhs_rhs
|
||||||
|
}.replace_rightmost(filter, |lhs_rhs| {
|
||||||
|
Self::$new_variant {
|
||||||
|
lhs: Box::new(lhs_rhs),
|
||||||
|
op: $op,
|
||||||
|
rhs: Box::new($rhs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},)*
|
||||||
|
lhs => Self::$new_variant {
|
||||||
|
lhs: Box::new(lhs),
|
||||||
|
op: $op,
|
||||||
|
rhs: Box::new($rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Expression {
|
||||||
|
/// Parse the first expression from the input. Might not be the full
|
||||||
|
/// expression.
|
||||||
|
fn parse_first(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
if input.peek(Paren) {
|
||||||
|
let content;
|
||||||
|
let paren_token = syn::parenthesized!(content in input);
|
||||||
|
|
||||||
|
let elems = Punctuated::parse_terminated(&content)?;
|
||||||
|
return Ok(if elems.len() == 1 && !elems.trailing_punct() {
|
||||||
|
// parenthezised expression, not a tuple
|
||||||
|
Self::Term {
|
||||||
|
paren_token,
|
||||||
|
expr: Box::new(elems.into_iter().next().unwrap())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// tuple
|
||||||
|
Self::TupleCtor { paren_token, elems }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.peek(LitBool) {
|
||||||
|
return Ok(Self::Bool(input.parse()?));
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.peek(LitInt) {
|
||||||
|
return Ok(Self::Literal(input.parse()?));
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.peek(Token![!]) {
|
||||||
|
return Ok(Self::BoolNegation {
|
||||||
|
not_token: input.parse()?,
|
||||||
|
expr: Box::new(Self::parse_first(input)?)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.peek(Token![-]) {
|
||||||
|
return Ok(Self::IntNegation {
|
||||||
|
minus_token: input.parse()?,
|
||||||
|
expr: Box::new(Self::parse_first(input)?)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.peek(Token![*]) {
|
||||||
|
return Ok(Self::Deref {
|
||||||
|
star_token: input.parse()?,
|
||||||
|
expr: Box::new(Self::parse_first(input)?)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let ident: Ident = input.parse()?;
|
||||||
|
|
||||||
|
if input.peek(Token![.]) {
|
||||||
|
let mut left = Self::Ident(ident);
|
||||||
|
while input.peek(Token![.]) {
|
||||||
|
left = Self::tuple_index(left, input.parse()?, input.parse()?);
|
||||||
|
}
|
||||||
|
return Ok(left);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(if ident == "Rc" {
|
||||||
|
let content;
|
||||||
|
let paren_token = syn::parenthesized!(content in input);
|
||||||
|
Self::RcCtor {
|
||||||
|
span: ident.span(),
|
||||||
|
paren_token,
|
||||||
|
expr: Box::new(content.parse()?)
|
||||||
|
}
|
||||||
|
} else if ident == "Some" {
|
||||||
|
let content;
|
||||||
|
let paren_token = syn::parenthesized!(content in input);
|
||||||
|
Self::OptionCtor {
|
||||||
|
span: ident.span(),
|
||||||
|
expr: Some((paren_token, Box::new(content.parse()?)))
|
||||||
|
}
|
||||||
|
} else if ident == "None" {
|
||||||
|
Self::OptionCtor {
|
||||||
|
span: ident.span(),
|
||||||
|
expr: None
|
||||||
|
}
|
||||||
|
} else if input.peek(Paren) {
|
||||||
|
let content;
|
||||||
|
let paren_token = syn::parenthesized!(content in input);
|
||||||
|
let inputs = Punctuated::parse_terminated(&content)?;
|
||||||
|
Self::FnCall {
|
||||||
|
ident,
|
||||||
|
generics: None,
|
||||||
|
paren_token,
|
||||||
|
inputs
|
||||||
|
}
|
||||||
|
} else if input.peek(Token![::]) {
|
||||||
|
let _turbofish: Token![::] = input.parse()?;
|
||||||
|
let generics = Some(input.parse()?);
|
||||||
|
let content;
|
||||||
|
let paren_token = syn::parenthesized!(content in input);
|
||||||
|
let inputs = Punctuated::parse_terminated(&content)?;
|
||||||
|
Self::FnCall {
|
||||||
|
ident,
|
||||||
|
generics,
|
||||||
|
paren_token,
|
||||||
|
inputs
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Self::Ident(ident)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is the strongest binding binary operator, and since all unary
|
||||||
|
// operators are left of their argument we don't need to take them
|
||||||
|
// into account
|
||||||
|
fn mul_or_div_op(lhs: Self, op: ArithmeticOp, rhs: Self) -> Self {
|
||||||
|
debug_assert!(op.is_mul_or_div_op());
|
||||||
|
reorder_expr!(lhs, op, rhs => Self::Arithmetic {
|
||||||
|
// ¦a + b¦ * c = a + (b * c)
|
||||||
|
Self::Arithmetic { lhs_lhs, lhs_op, lhs_rhs } if lhs_op.is_add_or_sub_op(),
|
||||||
|
|
||||||
|
// ¦a == b¦ * c = a == (b * c)
|
||||||
|
Self::Comparison { lhs_lhs, lhs_op, lhs_rhs },
|
||||||
|
|
||||||
|
// ¦a && b¦ * c = a && (b * c)
|
||||||
|
// (this is essential to allow comparison operators to be parsed next)
|
||||||
|
Self::Conjunction { lhs_lhs, lhs_op, lhs_rhs }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is the 2nd strongest binding binary operator, after mul or div and
|
||||||
|
// before any of the boolean operators
|
||||||
|
fn add_or_sub_op(lhs: Self, op: ArithmeticOp, rhs: Self) -> Self {
|
||||||
|
debug_assert!(op.is_add_or_sub_op());
|
||||||
|
reorder_expr!(lhs, op, rhs => Self::Arithmetic {
|
||||||
|
// ¦a == b¦ * c = a == (b * c)
|
||||||
|
Self::Comparison { lhs_lhs, lhs_op, lhs_rhs },
|
||||||
|
|
||||||
|
// ¦a && b¦ * c = a && (b * c)
|
||||||
|
// (this is essential to allow comparison operators to be parsed next)
|
||||||
|
Self::Conjunction { lhs_lhs, lhs_op, lhs_rhs }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is the 3rd strongest binding binary operator, after both arithmetic
|
||||||
|
// operators
|
||||||
|
fn comparison_op(lhs: Self, op: ComparisonOp, rhs: Self) -> Self {
|
||||||
|
reorder_expr!(lhs, op, rhs => Self::Comparison {
|
||||||
|
// ¦a && b¦ == c = a && (b == c)
|
||||||
|
Self::Conjunction { lhs_lhs, lhs_op, lhs_rhs }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is the least strongest binding binary operator
|
||||||
|
fn conjunction_op(lhs: Self, op: ConjunctionOp, rhs: Self) -> Self {
|
||||||
|
reorder_expr!(lhs, op, rhs => Self::Conjunction {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// tuple indexing binds stronger than unary operators
|
||||||
|
fn tuple_index(left: Self, dot_token: Token![.], index: Index) -> Self {
|
||||||
|
match left {
|
||||||
|
Self::BoolNegation { not_token, expr } => Self::BoolNegation {
|
||||||
|
not_token,
|
||||||
|
expr: Box::new(Self::tuple_index(*expr, dot_token, index))
|
||||||
|
},
|
||||||
|
Self::IntNegation { minus_token, expr } => Self::IntNegation {
|
||||||
|
minus_token,
|
||||||
|
expr: Box::new(Self::tuple_index(*expr, dot_token, index))
|
||||||
|
},
|
||||||
|
Self::Deref { star_token, expr } => Self::Deref {
|
||||||
|
star_token,
|
||||||
|
expr: Box::new(Self::tuple_index(*expr, dot_token, index))
|
||||||
|
},
|
||||||
|
left => Self::TupleIndex {
|
||||||
|
expr: Box::new(left),
|
||||||
|
dot_token,
|
||||||
|
index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_next(left: Self, input: ParseStream<'_>) -> syn::Result<(Self, bool)> {
|
||||||
|
if input.is_empty() {
|
||||||
|
return Ok((left, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(if input.peek(Token![.]) {
|
||||||
|
(
|
||||||
|
Self::tuple_index(left, input.parse()?, input.parse()?),
|
||||||
|
false
|
||||||
|
)
|
||||||
|
} else if input.peek(Token![+]) {
|
||||||
|
parse_next_op!(left, input, Self::add_or_sub_op, ArithmeticOp::Add)
|
||||||
|
} else if input.peek(Token![-]) {
|
||||||
|
parse_next_op!(left, input, Self::add_or_sub_op, ArithmeticOp::Sub)
|
||||||
|
} else if input.peek(Token![*]) {
|
||||||
|
parse_next_op!(left, input, Self::mul_or_div_op, ArithmeticOp::Mul)
|
||||||
|
} else if input.peek(Token![/]) {
|
||||||
|
parse_next_op!(left, input, Self::mul_or_div_op, ArithmeticOp::Div)
|
||||||
|
} else if input.peek(Token![==]) {
|
||||||
|
parse_next_op!(left, input, Self::comparison_op, ComparisonOp::Eq)
|
||||||
|
} else if input.peek(Token![!=]) {
|
||||||
|
parse_next_op!(left, input, Self::comparison_op, ComparisonOp::Neq)
|
||||||
|
} else if input.peek(Token![<=]) {
|
||||||
|
parse_next_op!(left, input, Self::comparison_op, ComparisonOp::Le)
|
||||||
|
} else if input.peek(Token![<]) {
|
||||||
|
parse_next_op!(left, input, Self::comparison_op, ComparisonOp::Lt)
|
||||||
|
} else if input.peek(Token![>=]) {
|
||||||
|
parse_next_op!(left, input, Self::comparison_op, ComparisonOp::Ge)
|
||||||
|
} else if input.peek(Token![>]) {
|
||||||
|
parse_next_op!(left, input, Self::comparison_op, ComparisonOp::Gt)
|
||||||
|
} else if input.peek(Token![&&]) {
|
||||||
|
parse_next_op!(left, input, Self::conjunction_op, ConjunctionOp::And)
|
||||||
|
} else if input.peek(Token![||]) {
|
||||||
|
parse_next_op!(left, input, Self::conjunction_op, ConjunctionOp::Or)
|
||||||
|
} else {
|
||||||
|
(left, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Expression {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
let mut expr = Self::parse_first(input)?;
|
||||||
|
loop {
|
||||||
|
let last;
|
||||||
|
(expr, last) = Self::parse_next(expr, input)?;
|
||||||
|
if last {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(expr)
|
||||||
|
}
|
||||||
|
}
|
259
src/ast/expression/tests.rs
Normal file
259
src/ast/expression/tests.rs
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn parse_expr(input: &str) -> Expression {
|
||||||
|
println!("Input: {input}");
|
||||||
|
syn::parse_str(input).expect("Failed to parse input")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn assert_ident(expr: Expression, name: &str) {
|
||||||
|
match expr {
|
||||||
|
Expression::Ident(ident) if ident == name => {},
|
||||||
|
expr => panic!("Expected `{name}`, found `{expr}`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn assert_bool(expr: Expression, value: bool) {
|
||||||
|
match expr {
|
||||||
|
Expression::Bool(lit) if lit.value() == value => {},
|
||||||
|
expr => panic!("Expected `{value}`, found `{expr}`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn assert_int(expr: Expression, value: isize) {
|
||||||
|
match expr {
|
||||||
|
Expression::Literal(lit)
|
||||||
|
if lit
|
||||||
|
.base10_parse::<isize>()
|
||||||
|
.map(|lit| lit == value)
|
||||||
|
.unwrap_or(false) => {},
|
||||||
|
expr => panic!("Expected `{value}`, found `{expr}`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn assert_bool_negation(expr: Expression) -> Expression {
|
||||||
|
match expr {
|
||||||
|
Expression::BoolNegation { expr, .. } => *expr,
|
||||||
|
expr => {
|
||||||
|
panic!("Expected `!{{ .. }}`, found `{expr}`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn assert_int_negation(expr: Expression) -> Expression {
|
||||||
|
match expr {
|
||||||
|
Expression::IntNegation { expr, .. } => *expr,
|
||||||
|
expr => {
|
||||||
|
panic!("Expected `-{{ .. }}`, found `{expr}`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn assert_arithmetic(expr: Expression, op_char: char) -> (Expression, Expression) {
|
||||||
|
match expr {
|
||||||
|
Expression::Arithmetic { lhs, op, rhs } if op.char() == op_char => (*lhs, *rhs),
|
||||||
|
expr => panic!("Expected `{{ .. }} {op_char} {{ .. }}`, found `{expr}`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn assert_comparison(expr: Expression, op_str: &str) -> (Expression, Expression) {
|
||||||
|
match expr {
|
||||||
|
Expression::Comparison { lhs, op, rhs } if op.str() == op_str => (*lhs, *rhs),
|
||||||
|
expr => panic!("Expected `{{ .. }} {op_str} {{ .. }}`, found `{expr}`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn assert_conjunction(expr: Expression, op_str: &str) -> (Expression, Expression) {
|
||||||
|
match expr {
|
||||||
|
Expression::Conjunction { lhs, op, rhs } if op.str() == op_str => (*lhs, *rhs),
|
||||||
|
expr => panic!("Expected `{{ .. }} {op_str} {{ .. }}`, found `{expr}`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn assert_tuple_index(expr: Expression, idx: u32) -> Expression {
|
||||||
|
match expr {
|
||||||
|
Expression::TupleIndex { expr, index, .. } if index.index == idx => *expr,
|
||||||
|
expr => panic!("Expected `{{ .. }}.{idx}`, found `{expr}`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn assert_deref(expr: Expression) -> Expression {
|
||||||
|
match expr {
|
||||||
|
Expression::Deref { expr, .. } => *expr,
|
||||||
|
expr => panic!("*{{ .. }}`, found `{expr}`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_true() {
|
||||||
|
let expr = parse_expr("true");
|
||||||
|
assert_bool(expr, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_false() {
|
||||||
|
let expr = parse_expr("false");
|
||||||
|
assert_bool(expr, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_zero() {
|
||||||
|
let expr = parse_expr("0");
|
||||||
|
assert_int(expr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_ten() {
|
||||||
|
let expr = parse_expr("10");
|
||||||
|
assert_int(expr, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_minus_ten() {
|
||||||
|
let expr = parse_expr("-10");
|
||||||
|
assert_int(expr, -10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_not_true() {
|
||||||
|
let expr = parse_expr("!true");
|
||||||
|
let expr = assert_bool_negation(expr);
|
||||||
|
assert_bool(expr, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_minus_minus_ten() {
|
||||||
|
let expr = parse_expr("--10");
|
||||||
|
let expr = assert_int_negation(expr);
|
||||||
|
assert_int(expr, -10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_one_plus_two() {
|
||||||
|
let expr = parse_expr("1 + 2");
|
||||||
|
let (lhs, rhs) = assert_arithmetic(expr, '+');
|
||||||
|
assert_int(lhs, 1);
|
||||||
|
assert_int(rhs, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_one_plus_two_times_three() {
|
||||||
|
let expr = parse_expr("1 + 2 * 3");
|
||||||
|
let (lhs, rhs) = assert_arithmetic(expr, '+');
|
||||||
|
assert_int(lhs, 1);
|
||||||
|
let (lhs, rhs) = assert_arithmetic(rhs, '*');
|
||||||
|
assert_int(lhs, 2);
|
||||||
|
assert_int(rhs, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_one_times_two_plus_three_times_four() {
|
||||||
|
let expr = parse_expr("1 * 2 + 3 * 4");
|
||||||
|
let (lhs, rhs) = assert_arithmetic(expr, '+');
|
||||||
|
{
|
||||||
|
let (lhs, rhs) = assert_arithmetic(lhs, '*');
|
||||||
|
assert_int(lhs, 1);
|
||||||
|
assert_int(rhs, 2);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let (lhs, rhs) = assert_arithmetic(rhs, '*');
|
||||||
|
assert_int(lhs, 3);
|
||||||
|
assert_int(rhs, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_one_times_two_eq_three_plus_four() {
|
||||||
|
let expr = parse_expr("1 * 2 == 3 + 4");
|
||||||
|
let (lhs, rhs) = assert_comparison(expr, "==");
|
||||||
|
{
|
||||||
|
let (lhs, rhs) = assert_arithmetic(lhs, '*');
|
||||||
|
assert_int(lhs, 1);
|
||||||
|
assert_int(rhs, 2);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let (lhs, rhs) = assert_arithmetic(rhs, '+');
|
||||||
|
assert_int(lhs, 3);
|
||||||
|
assert_int(rhs, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_true_and_one_plus_two_eq_three_times_four() {
|
||||||
|
let expr = parse_expr("true && 1 + 2 == 3 * 4");
|
||||||
|
let (lhs, rhs) = assert_conjunction(expr, "&&");
|
||||||
|
assert_bool(lhs, true);
|
||||||
|
let (lhs, rhs) = assert_comparison(rhs, "==");
|
||||||
|
{
|
||||||
|
let (lhs, rhs) = assert_arithmetic(lhs, '+');
|
||||||
|
assert_int(lhs, 1);
|
||||||
|
assert_int(rhs, 2);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let (lhs, rhs) = assert_arithmetic(rhs, '*');
|
||||||
|
assert_int(lhs, 3);
|
||||||
|
assert_int(rhs, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_not_true_and_false() {
|
||||||
|
let expr = parse_expr("!true && false");
|
||||||
|
let (lhs, rhs) = assert_conjunction(expr, "&&");
|
||||||
|
let lhs = assert_bool_negation(lhs);
|
||||||
|
assert_bool(lhs, true);
|
||||||
|
assert_bool(rhs, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_not_ident_0_and_false() {
|
||||||
|
let expr = parse_expr("!foo.0 && false");
|
||||||
|
let (lhs, rhs) = assert_conjunction(expr, "&&");
|
||||||
|
let lhs = assert_bool_negation(lhs);
|
||||||
|
let lhs = assert_tuple_index(lhs, 0);
|
||||||
|
assert_ident(lhs, "foo");
|
||||||
|
assert_bool(rhs, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_deref_ident() {
|
||||||
|
let expr = parse_expr("*foo");
|
||||||
|
let expr = assert_deref(expr);
|
||||||
|
assert_ident(expr, "foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_deref_ident_0() {
|
||||||
|
let expr = parse_expr("*foo.0");
|
||||||
|
let expr = assert_deref(expr);
|
||||||
|
let expr = assert_tuple_index(expr, 0);
|
||||||
|
assert_ident(expr, "foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_deref_ident_eq_one() {
|
||||||
|
let expr = parse_expr("*foo == 1");
|
||||||
|
let (lhs, rhs) = assert_comparison(expr, "==");
|
||||||
|
let lhs = assert_deref(lhs);
|
||||||
|
assert_ident(lhs, "foo");
|
||||||
|
assert_int(rhs, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_zero_eq_foo_0() {
|
||||||
|
let expr = parse_expr("0 == foo.0");
|
||||||
|
let (lhs, rhs) = assert_comparison(expr, "==");
|
||||||
|
assert_int(lhs, 0);
|
||||||
|
let rhs = assert_tuple_index(rhs, 0);
|
||||||
|
assert_ident(rhs, "foo");
|
||||||
|
}
|
54
src/ast/function.rs
Normal file
54
src/ast/function.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
use super::{Block, Generics, Type};
|
||||||
|
use proc_macro2::Ident;
|
||||||
|
use syn::{
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
punctuated::Punctuated,
|
||||||
|
token::Paren,
|
||||||
|
Token
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct FnArg {
|
||||||
|
pub ident: Ident,
|
||||||
|
pub colon_token: Token![:],
|
||||||
|
pub ty: Type
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for FnArg {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
ident: input.parse()?,
|
||||||
|
colon_token: input.parse()?,
|
||||||
|
ty: input.parse()?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Fn {
|
||||||
|
pub fn_token: Token![fn],
|
||||||
|
pub ident: Ident,
|
||||||
|
pub generics: Option<Generics<Ident>>,
|
||||||
|
pub paren_token: Paren,
|
||||||
|
pub inputs: Punctuated<FnArg, Token![,]>,
|
||||||
|
pub output: Option<(Token![->], Type)>,
|
||||||
|
pub block: Block
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Fn {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
let content;
|
||||||
|
Ok(Self {
|
||||||
|
fn_token: input.parse()?,
|
||||||
|
ident: input.parse()?,
|
||||||
|
generics: input.peek(Token![<]).then(|| input.parse()).transpose()?,
|
||||||
|
paren_token: syn::parenthesized!(content in input),
|
||||||
|
inputs: Punctuated::parse_terminated(&content)?,
|
||||||
|
output: input
|
||||||
|
.peek(Token![->])
|
||||||
|
.then(|| syn::Result::Ok((input.parse()?, input.parse()?)))
|
||||||
|
.transpose()?,
|
||||||
|
block: input.parse()?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
53
src/ast/generics.rs
Normal file
53
src/ast/generics.rs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
use std::{
|
||||||
|
fmt::{self, Display, Formatter},
|
||||||
|
hash::{Hash, Hasher}
|
||||||
|
};
|
||||||
|
use syn::{
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
punctuated::Punctuated,
|
||||||
|
token::{Comma, Gt, Lt}
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Generics<T> {
|
||||||
|
pub lt_token: Lt,
|
||||||
|
pub params: Punctuated<T, Comma>,
|
||||||
|
pub gt_token: Gt
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Display> Display for Generics<T> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str("<")?;
|
||||||
|
for (i, param) in self.params.iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
f.write_str(", ")?;
|
||||||
|
}
|
||||||
|
write!(f, "{param}")?;
|
||||||
|
}
|
||||||
|
f.write_str(">")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PartialEq> PartialEq for Generics<T> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.params == other.params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Eq> Eq for Generics<T> {}
|
||||||
|
|
||||||
|
impl<T: Hash> Hash for Generics<T> {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.params.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Parse> Parse for Generics<T> {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
lt_token: input.parse()?,
|
||||||
|
params: Punctuated::parse_separated_nonempty(input)?,
|
||||||
|
gt_token: input.parse()?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
94
src/ast/input_output.rs
Normal file
94
src/ast/input_output.rs
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
use proc_macro2::{Ident, Span};
|
||||||
|
use syn::{
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
punctuated::Punctuated,
|
||||||
|
token::{Bang, Paren, Semi},
|
||||||
|
Token
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InputOutputMacro<C> {
|
||||||
|
pub span: Span,
|
||||||
|
pub not_token: Bang,
|
||||||
|
pub paren_token: Paren,
|
||||||
|
pub content: C,
|
||||||
|
pub semi_token: Semi
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Input {
|
||||||
|
pub span: Span,
|
||||||
|
pub not_token: Token![!],
|
||||||
|
pub paren_token: Paren,
|
||||||
|
pub ident: Ident,
|
||||||
|
pub semi_token: Token![;]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<InputOutputMacro<Ident>> for Input {
|
||||||
|
fn from(this: InputOutputMacro<Ident>) -> Self {
|
||||||
|
Self {
|
||||||
|
span: this.span,
|
||||||
|
not_token: this.not_token,
|
||||||
|
paren_token: this.paren_token,
|
||||||
|
ident: this.content,
|
||||||
|
semi_token: this.semi_token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Output {
|
||||||
|
pub span: Span,
|
||||||
|
pub not_token: Token![!],
|
||||||
|
pub paren_token: Paren,
|
||||||
|
pub idents: Punctuated<Ident, Token![,]>,
|
||||||
|
pub semi_token: Token![;]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<InputOutputMacro<Punctuated<Ident, Token![,]>>> for Output {
|
||||||
|
fn from(this: InputOutputMacro<Punctuated<Ident, Token![,]>>) -> Self {
|
||||||
|
Self {
|
||||||
|
span: this.span,
|
||||||
|
not_token: this.not_token,
|
||||||
|
paren_token: this.paren_token,
|
||||||
|
idents: this.content,
|
||||||
|
semi_token: this.semi_token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse<I, F>(
|
||||||
|
input: ParseStream<'_>,
|
||||||
|
macro_ident: &str,
|
||||||
|
content_parser: F
|
||||||
|
) -> syn::Result<InputOutputMacro<I>>
|
||||||
|
where
|
||||||
|
F: FnOnce(ParseStream<'_>) -> syn::Result<I>
|
||||||
|
{
|
||||||
|
let content;
|
||||||
|
Ok(InputOutputMacro {
|
||||||
|
span: {
|
||||||
|
let ident: Ident = input.parse()?;
|
||||||
|
if ident != macro_ident {
|
||||||
|
bail!(ident.span() => "Expected `{macro_ident}`")
|
||||||
|
}
|
||||||
|
ident.span()
|
||||||
|
},
|
||||||
|
not_token: input.parse()?,
|
||||||
|
paren_token: syn::parenthesized!(content in input),
|
||||||
|
content: content_parser(&content)?,
|
||||||
|
semi_token: input.parse()?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Input {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
parse(input, "input", Parse::parse).map(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Output {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
parse(input, "output", Punctuated::parse_separated_nonempty).map(Into::into)
|
||||||
|
}
|
||||||
|
}
|
184
src/ast/mod.rs
Normal file
184
src/ast/mod.rs
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
use crate::verbose;
|
||||||
|
use proc_macro2::Ident;
|
||||||
|
use syn::{
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
token::{Brace, Bracket},
|
||||||
|
LitFloat, Token
|
||||||
|
};
|
||||||
|
|
||||||
|
mod assignment;
|
||||||
|
mod conditional;
|
||||||
|
mod declaration;
|
||||||
|
mod expression;
|
||||||
|
mod function;
|
||||||
|
mod generics;
|
||||||
|
mod input_output;
|
||||||
|
mod ret;
|
||||||
|
mod ty;
|
||||||
|
mod typedef;
|
||||||
|
mod while_loop;
|
||||||
|
|
||||||
|
pub use assignment::Assignment;
|
||||||
|
pub use conditional::{Condition, ElseBranch, If};
|
||||||
|
pub use declaration::Declaration;
|
||||||
|
pub use expression::{ArithmeticOp, ComparisonOp, Expression};
|
||||||
|
pub use function::{Fn, FnArg};
|
||||||
|
pub use generics::Generics;
|
||||||
|
pub use input_output::{Input, Output};
|
||||||
|
pub use ret::Return;
|
||||||
|
pub use ty::{Type, TypeCustom, TypePredef, TypePrimitive, TypeTuple};
|
||||||
|
pub use typedef::TypeDef;
|
||||||
|
pub use while_loop::{Break, Continue, While};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Program {
|
||||||
|
pub types: Vec<TypeDef>,
|
||||||
|
pub functions: Vec<Fn>,
|
||||||
|
pub inputs: Vec<Input>,
|
||||||
|
pub stmts: Vec<Statement>,
|
||||||
|
pub outputs: Vec<Output>
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek_macro(input: ParseStream<'_>, macro_ident: &str) -> bool {
|
||||||
|
let fork = input.fork();
|
||||||
|
let next = fork.parse::<Ident>();
|
||||||
|
let next2 = fork.parse::<Token![!]>();
|
||||||
|
matches!((next, next2), (Ok(ident), Ok(_)) if ident == macro_ident)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Program {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
let mut types = Vec::new();
|
||||||
|
while input.peek(Token![type]) {
|
||||||
|
if verbose() {
|
||||||
|
eprint!("[ast] Parsing typedef ...");
|
||||||
|
}
|
||||||
|
types.push(input.parse()?);
|
||||||
|
if verbose() {
|
||||||
|
eprintln!("done");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut functions = Vec::new();
|
||||||
|
while input.peek(Token![fn]) {
|
||||||
|
if verbose() {
|
||||||
|
eprint!("[ast] Parsing function ...");
|
||||||
|
}
|
||||||
|
functions.push(input.parse()?);
|
||||||
|
if verbose() {
|
||||||
|
eprintln!("done");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut inputs = Vec::new();
|
||||||
|
while peek_macro(input, "input") {
|
||||||
|
if verbose() {
|
||||||
|
eprint!("[ast] Parsing input!() ...");
|
||||||
|
}
|
||||||
|
inputs.push(input.parse()?);
|
||||||
|
if verbose() {
|
||||||
|
eprintln!("done");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut stmts = Vec::new();
|
||||||
|
while !input.is_empty() && !peek_macro(input, "output") {
|
||||||
|
if verbose() {
|
||||||
|
eprint!("[ast] Parsing statement ...");
|
||||||
|
}
|
||||||
|
stmts.push(input.parse()?);
|
||||||
|
if verbose() {
|
||||||
|
eprintln!("done");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut outputs = Vec::new();
|
||||||
|
while !input.is_empty() {
|
||||||
|
if verbose() {
|
||||||
|
eprint!("[ast] Parsing output!() ...");
|
||||||
|
}
|
||||||
|
outputs.push(input.parse()?);
|
||||||
|
if verbose() {
|
||||||
|
eprintln!("done");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose() {
|
||||||
|
eprintln!("[ast] Done parsing");
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
types,
|
||||||
|
functions,
|
||||||
|
inputs,
|
||||||
|
stmts,
|
||||||
|
outputs
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Block {
|
||||||
|
pub stmts: Vec<Statement>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Block {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
let content;
|
||||||
|
syn::braced!(content in input);
|
||||||
|
let mut stmts = Vec::new();
|
||||||
|
while !content.is_empty() {
|
||||||
|
stmts.push(content.parse()?);
|
||||||
|
}
|
||||||
|
Ok(Self { stmts })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Statement {
|
||||||
|
Decl(Declaration),
|
||||||
|
Assign(Assignment),
|
||||||
|
If(If),
|
||||||
|
CoinFlip { head: Block, prob: f32, tail: Block },
|
||||||
|
While(While),
|
||||||
|
Block(Block),
|
||||||
|
Return(Return),
|
||||||
|
Continue(Continue),
|
||||||
|
Break(Break)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Statement {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
Ok(if input.peek(Token![let]) {
|
||||||
|
Self::Decl(input.parse()?)
|
||||||
|
} else if input.peek(Token![if]) {
|
||||||
|
Self::If(input.parse()?)
|
||||||
|
} else if input.peek(Token![while]) {
|
||||||
|
Self::While(input.parse()?)
|
||||||
|
} else if input.peek(Token![return]) {
|
||||||
|
Self::Return(input.parse()?)
|
||||||
|
} else if input.peek(Token![continue]) {
|
||||||
|
Self::Continue(input.parse()?)
|
||||||
|
} else if input.peek(Token![break]) {
|
||||||
|
Self::Break(input.parse()?)
|
||||||
|
} else if input.peek(Brace) {
|
||||||
|
let block = input.parse()?;
|
||||||
|
if input.peek(Bracket) {
|
||||||
|
let content;
|
||||||
|
syn::bracketed!(content in input);
|
||||||
|
let prob: LitFloat = content.parse()?;
|
||||||
|
let tail = input.parse()?;
|
||||||
|
Self::CoinFlip {
|
||||||
|
head: block,
|
||||||
|
prob: prob.base10_parse()?,
|
||||||
|
tail
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Self::Block(block)
|
||||||
|
}
|
||||||
|
} else if input.peek(syn::Ident) {
|
||||||
|
Self::Assign(input.parse()?)
|
||||||
|
} else {
|
||||||
|
bail!(input.span() => "Unexpected token");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
24
src/ast/ret.rs
Normal file
24
src/ast/ret.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
use super::Expression;
|
||||||
|
use syn::{
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
Token
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Return {
|
||||||
|
pub return_token: Token![return],
|
||||||
|
pub expr: Option<Expression>,
|
||||||
|
pub semi_token: Token![;]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Return {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
return_token: input.parse()?,
|
||||||
|
expr: (!input.peek(Token![;]))
|
||||||
|
.then(|| input.parse())
|
||||||
|
.transpose()?,
|
||||||
|
semi_token: input.parse()?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
432
src/ast/ty.rs
Normal file
432
src/ast/ty.rs
Normal file
|
@ -0,0 +1,432 @@
|
||||||
|
use super::Generics;
|
||||||
|
use proc_macro2::{Ident, Span};
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
fmt::{self, Display, Formatter, Write as _},
|
||||||
|
hash::{Hash, Hasher}
|
||||||
|
};
|
||||||
|
use syn::{
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
punctuated::Punctuated,
|
||||||
|
spanned::Spanned,
|
||||||
|
token::Paren,
|
||||||
|
Token
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct TypePrimitive {
|
||||||
|
pub span: Span
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TypePrimitive {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
span: Span::call_site()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for TypePrimitive {
|
||||||
|
fn hash<H: Hasher>(&self, _state: &mut H) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Spanned for TypePrimitive {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
self.span
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct TypeTuple {
|
||||||
|
pub paren_token: Paren,
|
||||||
|
pub elems: Punctuated<Type, Token![,]>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for TypeTuple {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
let len = self.elems.len();
|
||||||
|
if len != other.elems.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for i in 0 .. len {
|
||||||
|
if self.elems[i] != other.elems[i] {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for TypeTuple {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
// Punctuated implements Hash by hashing the inner Vec,
|
||||||
|
// and Token![] types have an empty hash function
|
||||||
|
self.elems.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Spanned for TypeTuple {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
self.paren_token.span
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct TypePredef {
|
||||||
|
pub span: Span,
|
||||||
|
pub lt_token: Token![<],
|
||||||
|
pub inner_ty: Box<Type>,
|
||||||
|
pub gt_token: Token![>]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for TypePredef {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.inner_ty == other.inner_ty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for TypePredef {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.inner_ty.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Spanned for TypePredef {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
self.span
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct TypeCustom {
|
||||||
|
pub ident: Ident,
|
||||||
|
pub generics: Option<Generics<Type>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for TypeCustom {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.ident == other.ident && self.generics == other.generics
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for TypeCustom {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
// Ident implements Hash by hashing its string representation
|
||||||
|
self.ident.hash(state);
|
||||||
|
self.generics.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Spanned for TypeCustom {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
self.ident.span()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Hash)]
|
||||||
|
pub enum Type {
|
||||||
|
Bool(TypePrimitive),
|
||||||
|
Int(TypePrimitive),
|
||||||
|
Tuple(TypeTuple),
|
||||||
|
Rc(TypePredef),
|
||||||
|
Option(TypePredef),
|
||||||
|
Custom(TypeCustom),
|
||||||
|
|
||||||
|
/// This type is only used when expanding expressions, it is
|
||||||
|
/// never parsed from input.
|
||||||
|
Any
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Type {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Bool(_) => f.write_str("bool"),
|
||||||
|
Self::Int(_) => f.write_str("int"),
|
||||||
|
Self::Tuple(tuple) => {
|
||||||
|
f.write_str("(")?;
|
||||||
|
for (i, ty) in tuple.elems.iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
f.write_str(" ")?;
|
||||||
|
}
|
||||||
|
write!(f, "{ty},")?;
|
||||||
|
}
|
||||||
|
f.write_str(")")
|
||||||
|
},
|
||||||
|
Self::Rc(rc) => write!(f, "Rc<{}>", rc.inner_ty),
|
||||||
|
Self::Option(opt) => write!(f, "Option<{}>", opt.inner_ty),
|
||||||
|
Self::Custom(custom) => {
|
||||||
|
write!(f, "{}", custom.ident)?;
|
||||||
|
if let Some(g) = &custom.generics {
|
||||||
|
write!(f, "{g}")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
Self::Any => f.write_str("_")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Type {
|
||||||
|
/// Returns a mangled string representation of this type. Panics
|
||||||
|
/// if this type contains `_`.
|
||||||
|
pub fn mangled(&self) -> Cow<'static, str> {
|
||||||
|
match self {
|
||||||
|
Self::Bool(_) => "B".into(),
|
||||||
|
Self::Int(_) => "I".into(),
|
||||||
|
Self::Tuple(tuple) => {
|
||||||
|
let mut buf = format!("T{}", tuple.elems.len());
|
||||||
|
for ty in tuple.elems.iter() {
|
||||||
|
write!(buf, "_{}", ty.mangled()).unwrap();
|
||||||
|
}
|
||||||
|
buf.into()
|
||||||
|
},
|
||||||
|
Self::Rc(rc) => format!("R{}", rc.inner_ty.mangled()).into(),
|
||||||
|
Self::Option(opt) => format!("O{}", opt.inner_ty.mangled()).into(),
|
||||||
|
Self::Custom(custom) => {
|
||||||
|
let ident = custom.ident.to_string();
|
||||||
|
let mut buf = format!("C{}_{}", ident.len(), ident);
|
||||||
|
if let Some(g) = &custom.generics {
|
||||||
|
write!(buf, "_G{}", g.params.len()).unwrap();
|
||||||
|
for param in g.params.iter() {
|
||||||
|
write!(buf, "_{}", param.mangled()).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.into()
|
||||||
|
},
|
||||||
|
Self::Any => panic!("Cannot mangle type containing _")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Type> for Cow<'_, Type> {
|
||||||
|
fn from(this: Type) -> Self {
|
||||||
|
Cow::Owned(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a Type> for Cow<'a, Type> {
|
||||||
|
fn from(this: &'a Type) -> Self {
|
||||||
|
Cow::Borrowed(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Type {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
match (self, other) {
|
||||||
|
(Self::Bool(_), Self::Bool(_)) | (Self::Int(_), Self::Int(_)) => true,
|
||||||
|
(Self::Tuple(this), Self::Tuple(other)) => this == other,
|
||||||
|
(Self::Rc(this), Self::Rc(other)) => this == other,
|
||||||
|
(Self::Option(this), Self::Option(other)) => this == other,
|
||||||
|
(Self::Custom(this), Self::Custom(other)) => this == other,
|
||||||
|
(Self::Any, _) | (_, Self::Any) => true,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Type {}
|
||||||
|
|
||||||
|
impl Type {
|
||||||
|
/// Return the inner type for `Rc` and `Option`. Panics otherwise.
|
||||||
|
pub fn inner_ty(&self) -> &Type {
|
||||||
|
match self {
|
||||||
|
Self::Rc(this) | Self::Option(this) => &this.inner_ty,
|
||||||
|
_ => panic!("This type does not have an inner type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the inner type for `Rc` and `Option`. Panics otherwise.
|
||||||
|
pub fn into_inner_ty(self) -> Type {
|
||||||
|
match self {
|
||||||
|
Self::Rc(this) | Self::Option(this) => *this.inner_ty,
|
||||||
|
_ => panic!("This type does not have an inner type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the n-th inner type for tuples. Panics if self is not a tuple or
|
||||||
|
/// if n is out of bounds.
|
||||||
|
pub fn nth_ty(&self, n: usize) -> &Type {
|
||||||
|
match self {
|
||||||
|
Self::Tuple(tuple) => tuple.elems.iter().nth(n).expect("out of bounds"),
|
||||||
|
_ => panic!("This type is not a tuple")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Type {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
if input.peek(Paren) {
|
||||||
|
let content;
|
||||||
|
let paren_token = syn::parenthesized!(content in input);
|
||||||
|
return Ok(Self::Tuple(TypeTuple {
|
||||||
|
paren_token,
|
||||||
|
elems: Punctuated::parse_terminated(&content)?
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let ident: Ident = input.parse()?;
|
||||||
|
if ident == "bool" {
|
||||||
|
return Ok(Self::Bool(TypePrimitive { span: ident.span() }));
|
||||||
|
} else if ident == "int" {
|
||||||
|
return Ok(Self::Int(TypePrimitive { span: ident.span() }));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !input.peek(Token![<]) {
|
||||||
|
if ident == "Rc" || ident == "Option" {
|
||||||
|
bail!(ident.span() => "Missing generic type parameter")
|
||||||
|
}
|
||||||
|
return Ok(Self::Custom(TypeCustom {
|
||||||
|
ident,
|
||||||
|
generics: None
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let generics: Generics<Type> = input.parse()?;
|
||||||
|
|
||||||
|
if ident == "Rc" || ident == "Option" {
|
||||||
|
let mut iter = generics.params.into_iter();
|
||||||
|
let inner_ty = Box::new(iter.next().unwrap());
|
||||||
|
if let Some(p) = iter.next() {
|
||||||
|
bail!(p.span() => "Too many generic type paramaters");
|
||||||
|
}
|
||||||
|
let predef = TypePredef {
|
||||||
|
span: ident.span(),
|
||||||
|
lt_token: generics.lt_token,
|
||||||
|
inner_ty,
|
||||||
|
gt_token: generics.gt_token
|
||||||
|
};
|
||||||
|
return Ok(if ident == "Rc" {
|
||||||
|
Self::Rc(predef)
|
||||||
|
} else {
|
||||||
|
Self::Option(predef)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self::Custom(TypeCustom {
|
||||||
|
ident,
|
||||||
|
generics: Some(generics)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Spanned for Type {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
match self {
|
||||||
|
Self::Bool(this) | Self::Int(this) => this.span(),
|
||||||
|
Self::Tuple(this) => this.span(),
|
||||||
|
Self::Rc(this) | Self::Option(this) => this.span(),
|
||||||
|
Self::Custom(this) => this.span(),
|
||||||
|
Self::Any => Span::call_site()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_bool() {
|
||||||
|
let ty: Type = syn::parse_str("bool").unwrap();
|
||||||
|
assert!(matches!(ty, Type::Bool(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_int() {
|
||||||
|
let ty: Type = syn::parse_str("int").unwrap();
|
||||||
|
assert!(matches!(ty, Type::Int(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_unit() {
|
||||||
|
let ty: Type = syn::parse_str("()").unwrap();
|
||||||
|
assert!(matches!(ty, Type::Tuple(_)));
|
||||||
|
match ty {
|
||||||
|
Type::Tuple(tuple) => assert!(tuple.elems.is_empty()),
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_tuple_1() {
|
||||||
|
let ty: Type = syn::parse_str("(int)").unwrap();
|
||||||
|
assert!(matches!(ty, Type::Tuple(_)));
|
||||||
|
match ty {
|
||||||
|
Type::Tuple(tuple) => {
|
||||||
|
let mut iter = tuple.elems.into_iter();
|
||||||
|
assert!(matches!(iter.next(), Some(Type::Int(_))));
|
||||||
|
assert!(iter.next().is_none());
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_tuple_2() {
|
||||||
|
let ty: Type = syn::parse_str("(int, Option<int>)").unwrap();
|
||||||
|
assert!(matches!(ty, Type::Tuple(_)));
|
||||||
|
match ty {
|
||||||
|
Type::Tuple(tuple) => {
|
||||||
|
let mut iter = tuple.elems.into_iter();
|
||||||
|
assert!(matches!(iter.next(), Some(Type::Int(_))));
|
||||||
|
assert!(matches!(iter.next(), Some(Type::Option(_))));
|
||||||
|
assert!(iter.next().is_none());
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_rc() {
|
||||||
|
let ty: Type = syn::parse_str("Rc<int>").unwrap();
|
||||||
|
assert!(matches!(ty, Type::Rc(_)));
|
||||||
|
match ty {
|
||||||
|
Type::Rc(rc) => assert!(matches!(*rc.inner_ty, Type::Int(_))),
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_option() {
|
||||||
|
let ty: Type = syn::parse_str("Option<int>").unwrap();
|
||||||
|
assert!(matches!(ty, Type::Option(_)));
|
||||||
|
match ty {
|
||||||
|
Type::Option(option) => assert!(matches!(*option.inner_ty, Type::Int(_))),
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_custom_without_generics() {
|
||||||
|
let ty: Type = syn::parse_str("Foo").unwrap();
|
||||||
|
assert!(matches!(ty, Type::Custom(_)));
|
||||||
|
match ty {
|
||||||
|
Type::Custom(custom) => {
|
||||||
|
assert_eq!(custom.ident, "Foo");
|
||||||
|
assert!(custom.generics.is_none());
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_custom_with_generics() {
|
||||||
|
let ty: Type = syn::parse_str("Foo<int>").unwrap();
|
||||||
|
assert!(matches!(ty, Type::Custom(_)));
|
||||||
|
match ty {
|
||||||
|
Type::Custom(custom) => {
|
||||||
|
assert_eq!(custom.ident, "Foo");
|
||||||
|
assert!(custom.generics.is_some());
|
||||||
|
match custom.generics {
|
||||||
|
Some(generics) => {
|
||||||
|
let mut iter = generics.params.into_iter();
|
||||||
|
assert!(matches!(iter.next(), Some(Type::Int(_))));
|
||||||
|
assert!(iter.next().is_none());
|
||||||
|
},
|
||||||
|
None => unreachable!()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
src/ast/typedef.rs
Normal file
44
src/ast/typedef.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
use super::{Generics, Type};
|
||||||
|
use proc_macro2::{Ident, Span};
|
||||||
|
use syn::{
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
spanned::Spanned,
|
||||||
|
Token
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TypeDef {
|
||||||
|
pub type_token: Token![type],
|
||||||
|
pub ident: Ident,
|
||||||
|
pub generics: Option<Generics<Ident>>,
|
||||||
|
pub eq_token: Token![=],
|
||||||
|
pub ty: Type,
|
||||||
|
pub semi_token: Token![;]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for TypeDef {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
let type_token = input.parse()?;
|
||||||
|
let ident = input.parse()?;
|
||||||
|
|
||||||
|
let mut generics = None;
|
||||||
|
if input.peek(Token![<]) {
|
||||||
|
generics = Some(input.parse()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
type_token,
|
||||||
|
ident,
|
||||||
|
generics,
|
||||||
|
eq_token: input.parse()?,
|
||||||
|
ty: input.parse()?,
|
||||||
|
semi_token: input.parse()?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Spanned for TypeDef {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
self.ident.span()
|
||||||
|
}
|
||||||
|
}
|
52
src/ast/while_loop.rs
Normal file
52
src/ast/while_loop.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
use super::{Block, Condition};
|
||||||
|
use syn::{
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
Token
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct While {
|
||||||
|
pub while_token: Token![while],
|
||||||
|
pub condition: Condition,
|
||||||
|
pub loop_body: Block
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for While {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
while_token: input.parse()?,
|
||||||
|
condition: input.parse()?,
|
||||||
|
loop_body: input.parse()?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Continue {
|
||||||
|
pub continue_token: Token![continue],
|
||||||
|
pub semi_token: Token![;]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Continue {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
continue_token: input.parse()?,
|
||||||
|
semi_token: input.parse()?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Break {
|
||||||
|
pub break_token: Token![break],
|
||||||
|
pub semi_token: Token![;]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Break {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
break_token: input.parse()?,
|
||||||
|
semi_token: input.parse()?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
100
src/compile/assign.rs
Normal file
100
src/compile/assign.rs
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
use super::{expr::expand_expr_with_type, resolve_ty::resolve_tycustom, State};
|
||||||
|
use crate::{
|
||||||
|
ast::{Assignment, Expression, Type},
|
||||||
|
diagnostic::Diagnostic,
|
||||||
|
verbose
|
||||||
|
};
|
||||||
|
use std::{borrow::Cow, iter::Peekable};
|
||||||
|
use syn::{spanned::Spanned, Member};
|
||||||
|
|
||||||
|
fn cont<I>(
|
||||||
|
state: &State<'_>,
|
||||||
|
current_ty: &Type,
|
||||||
|
mut remaining: Peekable<I>,
|
||||||
|
expr: Expression
|
||||||
|
) -> Result<(Cow<'static, str>, Cow<'static, str>), Diagnostic>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = Member>
|
||||||
|
{
|
||||||
|
if remaining.peek().is_none() {
|
||||||
|
if verbose() {
|
||||||
|
eprintln!("[assign] expanding assignment with type {current_ty} and expression {expr}");
|
||||||
|
}
|
||||||
|
let expr = expand_expr_with_type(state, current_ty, expr)?;
|
||||||
|
return Ok((" = __temp_value;".into(), expr));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(match current_ty {
|
||||||
|
Type::Bool(_) => {
|
||||||
|
return Err(Diagnostic::no_such_field(
|
||||||
|
remaining.next().unwrap().span(),
|
||||||
|
"bool is a primitive type and does not have any fields"
|
||||||
|
));
|
||||||
|
},
|
||||||
|
|
||||||
|
Type::Int(_) => {
|
||||||
|
return Err(Diagnostic::no_such_field(
|
||||||
|
remaining.next().unwrap().span(),
|
||||||
|
"int is a primitive type and does not have any fields"
|
||||||
|
));
|
||||||
|
},
|
||||||
|
|
||||||
|
Type::Tuple(tuple) => {
|
||||||
|
let index = match remaining.next().unwrap() {
|
||||||
|
Member::Named(named) => {
|
||||||
|
return Err(Diagnostic::no_such_field(
|
||||||
|
named.span(),
|
||||||
|
"tuples don't have named fields"
|
||||||
|
)
|
||||||
|
.with_help("To access the first element in a tuple, use `.0`"));
|
||||||
|
},
|
||||||
|
Member::Unnamed(index) if index.index as usize >= tuple.elems.len() => {
|
||||||
|
return Err(Diagnostic::no_such_field(
|
||||||
|
index.span(),
|
||||||
|
"this tuple does not have enough elements"
|
||||||
|
)
|
||||||
|
.with_note("The first element in a tuple has index 0"));
|
||||||
|
},
|
||||||
|
Member::Unnamed(index) => index.index as usize
|
||||||
|
};
|
||||||
|
let (path, expr) = cont(state, current_ty.nth_ty(index), remaining, expr)?;
|
||||||
|
(format!(".{index}{path}").into(), expr)
|
||||||
|
},
|
||||||
|
|
||||||
|
Type::Rc(rc) => {
|
||||||
|
let (path, expr) = cont(state, &rc.inner_ty, remaining, expr)?;
|
||||||
|
(
|
||||||
|
format!(".with_mut(|__temp| {{ __temp{path} }});").into(),
|
||||||
|
expr
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
Type::Option(_) => {
|
||||||
|
return Err(Diagnostic::no_such_field(
|
||||||
|
remaining.next().unwrap().span(),
|
||||||
|
"Option does not have any fields"
|
||||||
|
));
|
||||||
|
},
|
||||||
|
|
||||||
|
Type::Custom(custom) => {
|
||||||
|
let ty = resolve_tycustom(state, custom)?;
|
||||||
|
return cont(state, &ty, remaining, expr);
|
||||||
|
},
|
||||||
|
|
||||||
|
Type::Any => unimplemented!()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn expand_assign(
|
||||||
|
state: &State<'_>,
|
||||||
|
assign: Assignment
|
||||||
|
) -> Result<String, Diagnostic> {
|
||||||
|
let mut lhs = assign.lhs.into_iter().peekable();
|
||||||
|
let first = match lhs.next().unwrap() {
|
||||||
|
Member::Named(ident) => ident,
|
||||||
|
Member::Unnamed(_) => unreachable!()
|
||||||
|
};
|
||||||
|
let first_ty = state.var(&first)?;
|
||||||
|
let (path, expr) = cont(state, first_ty, lhs, assign.rhs)?;
|
||||||
|
Ok(format!("{{ let __temp_value = {expr}; {first}{path} }}",))
|
||||||
|
}
|
83
src/compile/conditional.rs
Normal file
83
src/compile/conditional.rs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
use super::{
|
||||||
|
expand_block,
|
||||||
|
expect_ty::expect_option,
|
||||||
|
expr::{expand_expr, expand_expr_with_type},
|
||||||
|
State
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
ast::{Condition, ElseBranch, If, Type, TypePrimitive, While},
|
||||||
|
diagnostic::Diagnostic
|
||||||
|
};
|
||||||
|
use proc_macro2::Span;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use syn::Token;
|
||||||
|
|
||||||
|
pub(super) fn expand_condition(
|
||||||
|
state: &mut State<'_>,
|
||||||
|
condition: Condition,
|
||||||
|
if_span: Span
|
||||||
|
) -> Result<Cow<'static, str>, Diagnostic> {
|
||||||
|
Ok(match condition {
|
||||||
|
Condition::Bool(expr) => expand_expr_with_type(
|
||||||
|
state,
|
||||||
|
&Type::Bool(TypePrimitive { span: if_span }),
|
||||||
|
expr
|
||||||
|
)?,
|
||||||
|
Condition::LetSome {
|
||||||
|
some_span,
|
||||||
|
ident,
|
||||||
|
expr,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let expr = expand_expr(state, expr)?;
|
||||||
|
expect_option(&expr.ty, expr.expr_span, some_span)?;
|
||||||
|
let buf = format!("let ::stdlib::Option::Some(mut {ident}) = {}", expr.code);
|
||||||
|
let expr_ty = expr.ty.into_owned();
|
||||||
|
state.add_variable(ident, expr_ty.into_inner_ty());
|
||||||
|
buf.into()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_else_branch(
|
||||||
|
state: &State<'_>,
|
||||||
|
else_branch: Option<(Token![else], ElseBranch)>
|
||||||
|
) -> Result<String, Diagnostic> {
|
||||||
|
let mut buf = String::new();
|
||||||
|
if let Some((_, else_branch)) = else_branch {
|
||||||
|
buf += " else ";
|
||||||
|
match else_branch {
|
||||||
|
ElseBranch::If(else_if) => {
|
||||||
|
buf += "{";
|
||||||
|
buf += &expand_if(state, *else_if)?;
|
||||||
|
buf += "}";
|
||||||
|
},
|
||||||
|
ElseBranch::Block(block) => {
|
||||||
|
buf += &expand_block(state, block)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn expand_if(state: &State<'_>, if_expr: If) -> Result<String, Diagnostic> {
|
||||||
|
let mut sub = state.substate();
|
||||||
|
let condition = expand_condition(&mut sub, if_expr.condition, if_expr.if_token.span)?;
|
||||||
|
let block = expand_block(&sub, if_expr.then_branch)?;
|
||||||
|
|
||||||
|
let mut buf = format!("if {condition} {block}");
|
||||||
|
buf += &expand_else_branch(state, if_expr.else_branch)?;
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn expand_while(
|
||||||
|
state: &State<'_>,
|
||||||
|
while_expr: While
|
||||||
|
) -> Result<String, Diagnostic> {
|
||||||
|
let mut sub = state.substate().into_loop();
|
||||||
|
let condition =
|
||||||
|
expand_condition(&mut sub, while_expr.condition, while_expr.while_token.span)?;
|
||||||
|
let block = expand_block(&sub, while_expr.loop_body)?;
|
||||||
|
|
||||||
|
Ok(format!("while {condition} {block}"))
|
||||||
|
}
|
19
src/compile/decl.rs
Normal file
19
src/compile/decl.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
use super::{
|
||||||
|
expr::expand_expr_with_type, resolve_ty::resolve_ty, ty::expand_type, State
|
||||||
|
};
|
||||||
|
use crate::{ast::Declaration, diagnostic::Diagnostic};
|
||||||
|
|
||||||
|
pub(super) fn expand_decl(
|
||||||
|
state: &mut State<'_>,
|
||||||
|
decl: Declaration
|
||||||
|
) -> Result<String, Diagnostic> {
|
||||||
|
let expr = expand_expr_with_type(state, &decl.ty, decl.expr)?;
|
||||||
|
let ident = decl.ident;
|
||||||
|
let ty = expand_type(state, &decl.ty)?;
|
||||||
|
let buf = format!("let mut {ident}: {ty} = {expr};");
|
||||||
|
state.add_variable(
|
||||||
|
ident,
|
||||||
|
resolve_ty(&decl.ty, state.generic_types()).into_owned()
|
||||||
|
);
|
||||||
|
Ok(buf)
|
||||||
|
}
|
157
src/compile/expect_ty.rs
Normal file
157
src/compile/expect_ty.rs
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
use crate::{ast::Type, diagnostic::Diagnostic};
|
||||||
|
use proc_macro2::Span;
|
||||||
|
use syn::spanned::Spanned as _;
|
||||||
|
|
||||||
|
pub(super) fn expect_ty(
|
||||||
|
expected_ty: &Type,
|
||||||
|
expression_ty: &Type,
|
||||||
|
expression_span: Span
|
||||||
|
) -> Result<(), Diagnostic> {
|
||||||
|
if expected_ty == expression_ty {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Diagnostic::type_mismatch(
|
||||||
|
expected_ty,
|
||||||
|
expected_ty.span(),
|
||||||
|
expression_ty,
|
||||||
|
expression_span
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn expect_bool(
|
||||||
|
expr_ty: &Type,
|
||||||
|
expr_span: Span,
|
||||||
|
why_span: Span
|
||||||
|
) -> Result<(), Diagnostic> {
|
||||||
|
match expr_ty {
|
||||||
|
Type::Bool(_) => Ok(()),
|
||||||
|
_ => Err(Diagnostic::type_mismatch(
|
||||||
|
"bool", why_span, expr_ty, expr_span
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn expect_int(
|
||||||
|
expr_ty: &Type,
|
||||||
|
expr_span: Span,
|
||||||
|
why_span: Span
|
||||||
|
) -> Result<(), Diagnostic> {
|
||||||
|
match expr_ty {
|
||||||
|
Type::Int(_) => Ok(()),
|
||||||
|
_ => Err(Diagnostic::type_mismatch(
|
||||||
|
"int", why_span, expr_ty, expr_span
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn expect_tuple_exact(
|
||||||
|
ty: &Type,
|
||||||
|
elems_len: usize,
|
||||||
|
expr_span: Span,
|
||||||
|
why_span: Span
|
||||||
|
) -> Result<(), Diagnostic> {
|
||||||
|
match ty {
|
||||||
|
Type::Tuple(tuple) if tuple.elems.len() == elems_len => Ok(()),
|
||||||
|
Type::Tuple(tuple) => Err(Diagnostic::new(
|
||||||
|
expr_span,
|
||||||
|
"Type Mismatch",
|
||||||
|
format!("but expression has {elems_len} elements")
|
||||||
|
)
|
||||||
|
.with_label(
|
||||||
|
ty.span(),
|
||||||
|
format!("Expected tuple with {} elements,", tuple.elems.len())
|
||||||
|
)),
|
||||||
|
_ => Err(Diagnostic::type_mismatch(
|
||||||
|
ty,
|
||||||
|
expr_span,
|
||||||
|
format!("({})", ", ".repeat(elems_len)),
|
||||||
|
why_span
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn expect_tuple_at_least(
|
||||||
|
ty: &Type,
|
||||||
|
elems_len: usize,
|
||||||
|
expr_span: Span,
|
||||||
|
why_span: Span
|
||||||
|
) -> Result<(), Diagnostic> {
|
||||||
|
match ty {
|
||||||
|
Type::Tuple(tuple) if tuple.elems.len() >= elems_len => Ok(()),
|
||||||
|
Type::Tuple(tuple) => Err(Diagnostic::new(
|
||||||
|
expr_span,
|
||||||
|
"Type Mismatch",
|
||||||
|
format!("but expression requires at least {elems_len} elements")
|
||||||
|
)
|
||||||
|
.with_label(
|
||||||
|
ty.span(),
|
||||||
|
format!("Found tuple with {} elements,", tuple.elems.len())
|
||||||
|
)),
|
||||||
|
_ => Err(Diagnostic::type_mismatch(
|
||||||
|
format!("({}..)", "_, ".repeat(elems_len)),
|
||||||
|
why_span,
|
||||||
|
ty,
|
||||||
|
expr_span
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn expect_rc(
|
||||||
|
expr_ty: &Type,
|
||||||
|
expr_span: Span,
|
||||||
|
why_span: Span
|
||||||
|
) -> Result<(), Diagnostic> {
|
||||||
|
match expr_ty {
|
||||||
|
Type::Rc(_) => Ok(()),
|
||||||
|
_ => Err(Diagnostic::type_mismatch(
|
||||||
|
"Rc<_>", why_span, expr_ty, expr_span
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn expect_option(
|
||||||
|
expr_ty: &Type,
|
||||||
|
expr_span: Span,
|
||||||
|
why_span: Span
|
||||||
|
) -> Result<(), Diagnostic> {
|
||||||
|
match expr_ty {
|
||||||
|
Type::Option(_) => Ok(()),
|
||||||
|
_ => Err(Diagnostic::type_mismatch(
|
||||||
|
"Option<_>",
|
||||||
|
why_span,
|
||||||
|
expr_ty,
|
||||||
|
expr_span
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn expect_rc_ctor(
|
||||||
|
expected_ty: &Type,
|
||||||
|
expr_span: Span
|
||||||
|
) -> Result<(), Diagnostic> {
|
||||||
|
match expected_ty {
|
||||||
|
Type::Rc(_) => Ok(()),
|
||||||
|
_ => Err(Diagnostic::type_mismatch(
|
||||||
|
expected_ty,
|
||||||
|
expected_ty.span(),
|
||||||
|
"Rc<_>",
|
||||||
|
expr_span
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn expect_option_ctor(
|
||||||
|
expected_ty: &Type,
|
||||||
|
expr_span: Span
|
||||||
|
) -> Result<(), Diagnostic> {
|
||||||
|
match expected_ty {
|
||||||
|
Type::Option(_) => Ok(()),
|
||||||
|
_ => Err(Diagnostic::type_mismatch(
|
||||||
|
expected_ty,
|
||||||
|
expected_ty.span(),
|
||||||
|
"Option<_>",
|
||||||
|
expr_span
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
304
src/compile/expr.rs
Normal file
304
src/compile/expr.rs
Normal file
|
@ -0,0 +1,304 @@
|
||||||
|
use super::{
|
||||||
|
expect_ty::{
|
||||||
|
expect_bool, expect_int, expect_option_ctor, expect_rc, expect_rc_ctor,
|
||||||
|
expect_tuple_at_least, expect_tuple_exact, expect_ty
|
||||||
|
},
|
||||||
|
resolve_ty::{resolve_ty, resolve_tycustom},
|
||||||
|
State
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
ast::{Expression, Type, TypePredef, TypeTuple},
|
||||||
|
diagnostic::Diagnostic
|
||||||
|
};
|
||||||
|
use proc_macro2::Span;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use syn::{punctuated::Punctuated, spanned::Spanned};
|
||||||
|
|
||||||
|
pub(super) struct ExpandedExpression<'a> {
|
||||||
|
pub(super) code: Cow<'static, str>,
|
||||||
|
pub(super) expr_span: Span,
|
||||||
|
pub(super) ty: Cow<'a, Type>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, C, S, T> From<(C, S, T)> for ExpandedExpression<'a>
|
||||||
|
where
|
||||||
|
C: Into<Cow<'static, str>>,
|
||||||
|
S: Spanned,
|
||||||
|
T: Into<Cow<'a, Type>>
|
||||||
|
{
|
||||||
|
fn from((code, expr_span, ty): (C, S, T)) -> Self {
|
||||||
|
Self {
|
||||||
|
code: code.into(),
|
||||||
|
expr_span: expr_span.span(),
|
||||||
|
ty: ty.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn expand_expr<'a>(
|
||||||
|
state: &'a State<'_>,
|
||||||
|
expr: Expression
|
||||||
|
) -> Result<ExpandedExpression<'a>, Diagnostic> {
|
||||||
|
let span = expr.span();
|
||||||
|
let expanded: ExpandedExpression<'a> = match expr {
|
||||||
|
Expression::Term { expr, .. } => {
|
||||||
|
let expr = expand_expr(state, *expr)?;
|
||||||
|
(format!("({})", expr.code), span, expr.ty).into()
|
||||||
|
},
|
||||||
|
|
||||||
|
Expression::Ident(ident) => (
|
||||||
|
format!("::core::clone::Clone::clone(&{ident})"),
|
||||||
|
span,
|
||||||
|
state.var(&ident)?
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
|
||||||
|
Expression::Bool(lit) => {
|
||||||
|
let code = match lit.value {
|
||||||
|
true => "true",
|
||||||
|
false => "false"
|
||||||
|
};
|
||||||
|
(code, span, Type::Bool(Default::default())).into()
|
||||||
|
},
|
||||||
|
|
||||||
|
Expression::Literal(lit) => {
|
||||||
|
(lit.to_string(), span, Type::Int(Default::default())).into()
|
||||||
|
},
|
||||||
|
|
||||||
|
Expression::BoolNegation { not_token, expr } => {
|
||||||
|
let bool = Type::Bool(Default::default());
|
||||||
|
let expr = expand_expr(state, *expr)?;
|
||||||
|
expect_bool(&expr.ty, expr.expr_span, not_token.span)?;
|
||||||
|
(format!("!({})", expr.code), span, bool).into()
|
||||||
|
},
|
||||||
|
|
||||||
|
Expression::IntNegation { minus_token, expr } => {
|
||||||
|
let int = Type::Int(Default::default());
|
||||||
|
let expr = expand_expr(state, *expr)?;
|
||||||
|
expect_int(&expr.ty, expr.expr_span, minus_token.span)?;
|
||||||
|
(format!("-({})", expr.code), span, int).into()
|
||||||
|
},
|
||||||
|
|
||||||
|
Expression::Arithmetic { lhs, op, rhs } => {
|
||||||
|
let int = Type::Int(Default::default());
|
||||||
|
let lhs = expand_expr(state, *lhs)?;
|
||||||
|
expect_int(&lhs.ty, lhs.expr_span, op.span())?;
|
||||||
|
let rhs = expand_expr(state, *rhs)?;
|
||||||
|
expect_int(&rhs.ty, rhs.expr_span, op.span())?;
|
||||||
|
(format!("{} {op} {}", lhs.code, rhs.code), span, int).into()
|
||||||
|
},
|
||||||
|
|
||||||
|
Expression::Comparison { lhs, op, rhs } => {
|
||||||
|
let bool = Type::Bool(Default::default());
|
||||||
|
let lhs = expand_expr(state, *lhs)?;
|
||||||
|
expect_int(&lhs.ty, lhs.expr_span, op.span())?;
|
||||||
|
let rhs = expand_expr(state, *rhs)?;
|
||||||
|
expect_int(&rhs.ty, rhs.expr_span, op.span())?;
|
||||||
|
(format!("{} {op} {}", lhs.code, rhs.code), span, bool).into()
|
||||||
|
},
|
||||||
|
|
||||||
|
Expression::Conjunction { lhs, op, rhs } => {
|
||||||
|
let bool = Type::Bool(Default::default());
|
||||||
|
let lhs = expand_expr(state, *lhs)?;
|
||||||
|
expect_bool(&lhs.ty, lhs.expr_span, op.span())?;
|
||||||
|
let rhs = expand_expr(state, *rhs)?;
|
||||||
|
expect_bool(&rhs.ty, rhs.expr_span, op.span())?;
|
||||||
|
(format!("{} {op} {}", lhs.code, rhs.code), span, bool).into()
|
||||||
|
},
|
||||||
|
|
||||||
|
Expression::TupleCtor { paren_token, elems } => {
|
||||||
|
let mut ty_elems = Punctuated::new();
|
||||||
|
let mut buf = "(".to_owned();
|
||||||
|
for v in elems.into_iter() {
|
||||||
|
let elem = expand_expr(state, v)?;
|
||||||
|
buf += &elem.code;
|
||||||
|
buf += ", ";
|
||||||
|
ty_elems.push(elem.ty.into_owned());
|
||||||
|
}
|
||||||
|
buf += ")";
|
||||||
|
let tuple = Type::Tuple(TypeTuple {
|
||||||
|
paren_token,
|
||||||
|
elems: ty_elems
|
||||||
|
});
|
||||||
|
(buf, span, tuple).into()
|
||||||
|
},
|
||||||
|
|
||||||
|
Expression::TupleIndex { expr, index, .. } => {
|
||||||
|
let left = expand_expr(state, *expr)?;
|
||||||
|
let idx = index.index as usize;
|
||||||
|
expect_tuple_at_least(&left.ty, idx + 1, span, index.span())?;
|
||||||
|
(
|
||||||
|
format!("{}.{}", left.code, index.index),
|
||||||
|
span,
|
||||||
|
resolve_ty(left.ty.nth_ty(idx), state.generic_types()).into_owned()
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
},
|
||||||
|
|
||||||
|
Expression::RcCtor { span, expr, .. } => {
|
||||||
|
let inner = expand_expr(state, *expr)?;
|
||||||
|
let rc = Type::Rc(TypePredef {
|
||||||
|
span,
|
||||||
|
lt_token: Default::default(),
|
||||||
|
inner_ty: Box::new(
|
||||||
|
resolve_ty(&inner.ty, state.generic_types()).into_owned()
|
||||||
|
),
|
||||||
|
gt_token: Default::default()
|
||||||
|
});
|
||||||
|
(format!("::stdlib::Rc::new({})", inner.code), span, rc).into()
|
||||||
|
},
|
||||||
|
|
||||||
|
Expression::OptionCtor { span, expr: None } => {
|
||||||
|
let option = Type::Option(TypePredef {
|
||||||
|
span,
|
||||||
|
lt_token: Default::default(),
|
||||||
|
inner_ty: Box::new(Type::Any),
|
||||||
|
gt_token: Default::default()
|
||||||
|
});
|
||||||
|
("::stdlib::Option::None", span, option).into()
|
||||||
|
},
|
||||||
|
|
||||||
|
Expression::OptionCtor {
|
||||||
|
span,
|
||||||
|
expr: Some((_, expr))
|
||||||
|
} => {
|
||||||
|
let inner = expand_expr(state, *expr)?;
|
||||||
|
let option = Type::Option(TypePredef {
|
||||||
|
span,
|
||||||
|
lt_token: Default::default(),
|
||||||
|
inner_ty: Box::new(
|
||||||
|
resolve_ty(&inner.ty, state.generic_types()).into_owned()
|
||||||
|
),
|
||||||
|
gt_token: Default::default()
|
||||||
|
});
|
||||||
|
(
|
||||||
|
format!("::stdlib::Option::Some({})", inner.code),
|
||||||
|
span,
|
||||||
|
option
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
},
|
||||||
|
|
||||||
|
Expression::Deref { star_token, expr } => {
|
||||||
|
let rc = expand_expr(state, *expr)?;
|
||||||
|
expect_rc(&rc.ty, rc.expr_span, star_token.span)?;
|
||||||
|
(
|
||||||
|
format!("::stdlib::Rc::into_inner({})", rc.code),
|
||||||
|
span,
|
||||||
|
rc.ty.inner_ty().to_owned()
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
},
|
||||||
|
|
||||||
|
Expression::FnCall {
|
||||||
|
ident,
|
||||||
|
generics,
|
||||||
|
inputs,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let fun = state.fun(&ident, &generics)?;
|
||||||
|
|
||||||
|
let fun_inputs_len = fun.inputs_len();
|
||||||
|
let inputs_len = inputs.len();
|
||||||
|
if fun_inputs_len > inputs_len {
|
||||||
|
return Err(Diagnostic::new(
|
||||||
|
ident.span(),
|
||||||
|
"Too few arguments",
|
||||||
|
format!("but only {inputs_len} arguments were supplied")
|
||||||
|
)
|
||||||
|
.with_label(
|
||||||
|
fun.span(),
|
||||||
|
format!("This function takes {fun_inputs_len} arguments,")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buf = format!("{}(", fun.ident);
|
||||||
|
for (input_ty, input_expr) in fun.inputs().zip(inputs.iter()) {
|
||||||
|
let resolved_ty = resolve_ty(&input_ty, state.generic_types());
|
||||||
|
buf += &expand_expr_with_type(state, &resolved_ty, input_expr.clone())?;
|
||||||
|
buf += ", ";
|
||||||
|
}
|
||||||
|
buf += ")";
|
||||||
|
|
||||||
|
(buf, span, fun.output().into_owned()).into()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let expanded_ty = resolve_ty(&expanded.ty, state.generic_types());
|
||||||
|
let expanded_ty: &Type = &expanded_ty;
|
||||||
|
Ok(if matches!(expanded_ty, Type::Custom(_)) {
|
||||||
|
let mut ty = expanded.ty.into_owned();
|
||||||
|
let mut buf = expanded.code.into_owned();
|
||||||
|
while let Type::Custom(custom) = ty {
|
||||||
|
buf = format!("{}::into_inner({buf})", custom.ident);
|
||||||
|
ty = resolve_tycustom(state, &custom)?.into_owned();
|
||||||
|
}
|
||||||
|
(buf, expanded.expr_span, ty).into()
|
||||||
|
} else {
|
||||||
|
expanded
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn expand_expr_with_type(
|
||||||
|
state: &State<'_>,
|
||||||
|
ty: &Type,
|
||||||
|
expr: Expression
|
||||||
|
) -> Result<Cow<'static, str>, Diagnostic> {
|
||||||
|
let ty = resolve_ty(ty, state.generic_types());
|
||||||
|
let ty: &Type = &ty;
|
||||||
|
|
||||||
|
// special case: custom outer type, requires wrapping in new
|
||||||
|
if let Type::Custom(custom) = ty {
|
||||||
|
let inner_ty = resolve_tycustom(state, custom)?;
|
||||||
|
let ident = &custom.ident;
|
||||||
|
let expr = expand_expr_with_type(state, &inner_ty, expr)?;
|
||||||
|
return Ok(format!("{ident}::new({expr})").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(match expr {
|
||||||
|
// special case: possibly custom type(s) inside a term
|
||||||
|
Expression::Term { expr, .. } => {
|
||||||
|
let expr = expand_expr_with_type(state, ty, *expr)?;
|
||||||
|
format!("({expr})").into()
|
||||||
|
},
|
||||||
|
|
||||||
|
// special case: possibly custom type(s) inside a tuple
|
||||||
|
Expression::TupleCtor {
|
||||||
|
paren_token, elems, ..
|
||||||
|
} => {
|
||||||
|
expect_tuple_exact(ty, elems.len(), paren_token.span, ty.span())?;
|
||||||
|
let mut buf = "(".to_owned();
|
||||||
|
for (n, v) in elems.into_iter().enumerate() {
|
||||||
|
let elem = expand_expr_with_type(state, ty.nth_ty(n), v)?;
|
||||||
|
buf += &elem;
|
||||||
|
buf += ", ";
|
||||||
|
}
|
||||||
|
buf += ")";
|
||||||
|
buf.into()
|
||||||
|
},
|
||||||
|
|
||||||
|
// special case: possibly custom type inside an Rc
|
||||||
|
Expression::RcCtor { span, expr, .. } => {
|
||||||
|
expect_rc_ctor(ty, span)?;
|
||||||
|
let inner = expand_expr_with_type(state, ty.inner_ty(), *expr)?;
|
||||||
|
format!("::stdlib::Rc::new({inner})").into()
|
||||||
|
},
|
||||||
|
|
||||||
|
// special case: possibly custom type inside a Some
|
||||||
|
Expression::OptionCtor {
|
||||||
|
span,
|
||||||
|
expr: Some((_, expr))
|
||||||
|
} => {
|
||||||
|
expect_option_ctor(ty, span)?;
|
||||||
|
let inner = expand_expr_with_type(state, ty.inner_ty(), *expr)?;
|
||||||
|
format!("::stdlib::Option::Some({inner})").into()
|
||||||
|
},
|
||||||
|
|
||||||
|
// otherwise, just expand the expression and expect its type
|
||||||
|
_ => {
|
||||||
|
let expr = expand_expr(state, expr)?;
|
||||||
|
expect_ty(ty, &expr.ty, expr.expr_span)?;
|
||||||
|
expr.code
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
66
src/compile/function.rs
Normal file
66
src/compile/function.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
use super::{expand_stmt, resolve_ty::resolve_ty, ty::expand_type, State};
|
||||||
|
use crate::{
|
||||||
|
ast::{Fn, Generics, Type, TypeTuple},
|
||||||
|
diagnostic::Diagnostic
|
||||||
|
};
|
||||||
|
use std::fmt::Write as _;
|
||||||
|
|
||||||
|
pub(super) fn expand_function(
|
||||||
|
buf: &mut String,
|
||||||
|
state: &State<'_>,
|
||||||
|
fun: &Fn,
|
||||||
|
generics: Option<Generics<Type>>,
|
||||||
|
ident: String
|
||||||
|
) -> Result<(), Diagnostic> {
|
||||||
|
let mut sub = state.substate();
|
||||||
|
if let Some((fun_generics, inst_generics)) = fun
|
||||||
|
.generics
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|fg| generics.as_ref().map(|ig| (fg, ig)))
|
||||||
|
{
|
||||||
|
for (ident, ty) in fun_generics.params.iter().zip(inst_generics.params.iter()) {
|
||||||
|
sub.add_ty(ident.clone(), ty.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// function signature
|
||||||
|
*buf += "#[allow(non_snake_case)]\n";
|
||||||
|
write!(buf, "fn {ident}").unwrap();
|
||||||
|
*buf += "(";
|
||||||
|
for arg in &fun.inputs {
|
||||||
|
write!(buf, "mut {}: {}, ", arg.ident, expand_type(&sub, &arg.ty)?).unwrap();
|
||||||
|
sub.add_variable(
|
||||||
|
arg.ident.clone(),
|
||||||
|
resolve_ty(&arg.ty, sub.generic_types()).into_owned()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
*buf += ")";
|
||||||
|
if let Some((_, out)) = fun.output.as_ref() {
|
||||||
|
write!(buf, " -> {}", expand_type(&sub, out)?).unwrap();
|
||||||
|
sub.set_return_ty(out.clone());
|
||||||
|
} else {
|
||||||
|
sub.set_return_ty(Type::Tuple(TypeTuple::default()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// function body
|
||||||
|
*buf += " {\n";
|
||||||
|
for s in &fun.block.stmts {
|
||||||
|
*buf += &expand_stmt(&mut sub, s.clone())?;
|
||||||
|
}
|
||||||
|
if fun.output.is_some() {
|
||||||
|
*buf += "\n#[allow(unreachable_code)]\n{\n";
|
||||||
|
write!(buf, "::stdlib::fn_no_return({:?})", fun.ident.to_string()).unwrap();
|
||||||
|
for arg in &fun.inputs {
|
||||||
|
write!(
|
||||||
|
buf,
|
||||||
|
".add_input(::core::stringify!({ident}), &{ident})",
|
||||||
|
ident = arg.ident
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
*buf += ".panic()\n}\n";
|
||||||
|
}
|
||||||
|
*buf += "}\n";
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
276
src/compile/mod.rs
Normal file
276
src/compile/mod.rs
Normal file
|
@ -0,0 +1,276 @@
|
||||||
|
use crate::{
|
||||||
|
ast::{Block, Expression, Program, Return, Statement, Type},
|
||||||
|
diagnostic::Diagnostic,
|
||||||
|
verbose
|
||||||
|
};
|
||||||
|
use askama::Template;
|
||||||
|
use std::{borrow::Cow, fmt::Write as _};
|
||||||
|
|
||||||
|
mod assign;
|
||||||
|
mod conditional;
|
||||||
|
mod decl;
|
||||||
|
mod expect_ty;
|
||||||
|
mod expr;
|
||||||
|
mod function;
|
||||||
|
mod resolve_ty;
|
||||||
|
mod state;
|
||||||
|
mod ty;
|
||||||
|
|
||||||
|
use assign::expand_assign;
|
||||||
|
use conditional::{expand_if, expand_while};
|
||||||
|
use decl::expand_decl;
|
||||||
|
use expect_ty::expect_tuple_exact;
|
||||||
|
use expr::expand_expr_with_type;
|
||||||
|
use function::expand_function;
|
||||||
|
use state::State;
|
||||||
|
use ty::expand_type;
|
||||||
|
|
||||||
|
pub enum CompileResult {
|
||||||
|
Ok {
|
||||||
|
main_rs: syn::File,
|
||||||
|
funs_rs: syn::File,
|
||||||
|
types_rs: syn::File
|
||||||
|
},
|
||||||
|
InternalErr(String, syn::Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
mod tpl {
|
||||||
|
use crate::ast::{Generics, Output};
|
||||||
|
use askama::Template;
|
||||||
|
use proc_macro2::Ident;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
mod filters {
|
||||||
|
use std::fmt::Display;
|
||||||
|
pub fn if_some<T: Display>(opt: &Option<T>) -> askama::Result<String> {
|
||||||
|
Ok(if let Some(value) = opt.as_ref() {
|
||||||
|
value.to_string()
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "main.rs.j2", escape = "none")]
|
||||||
|
pub(super) struct MainRs<'a> {
|
||||||
|
pub(super) inputs: Vec<Input<'a>>,
|
||||||
|
pub(super) outputs: &'a [Output]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "types.rs.j2", escape = "none")]
|
||||||
|
pub(super) struct TypesRs<'a> {
|
||||||
|
pub(super) typedefs: Vec<TypeDef<'a>>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct TypeDef<'a> {
|
||||||
|
pub(super) ident: &'a Ident,
|
||||||
|
pub(super) generics: Option<&'a Generics<Ident>>,
|
||||||
|
pub(super) inner_ty: Cow<'static, str>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TypeDef<'a> {
|
||||||
|
pub(super) fn new(
|
||||||
|
ident: &'a Ident,
|
||||||
|
generics: Option<&'a Generics<Ident>>,
|
||||||
|
inner_ty: Cow<'static, str>
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
ident,
|
||||||
|
generics,
|
||||||
|
inner_ty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct Input<'a> {
|
||||||
|
pub(super) ident: &'a Ident,
|
||||||
|
short_long: &'static str,
|
||||||
|
quote: char
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Input<'a> {
|
||||||
|
pub(super) fn new(ident: &'a Ident) -> Self {
|
||||||
|
let (short_long, quote) = match ident.to_string() {
|
||||||
|
str if str.len() == 1 && str != "h" && str != "n" && str != "v" => {
|
||||||
|
("short", '\'')
|
||||||
|
},
|
||||||
|
_ => ("long", '"')
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
ident,
|
||||||
|
short_long,
|
||||||
|
quote
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::write_with_newline)]
|
||||||
|
pub fn compile(input: &str) -> Result<CompileResult, Diagnostic> {
|
||||||
|
let program: Program = syn::parse_str(input)?;
|
||||||
|
let mut state = State::new(program.types, program.functions);
|
||||||
|
|
||||||
|
let main_rs = tpl::MainRs {
|
||||||
|
inputs: program
|
||||||
|
.inputs
|
||||||
|
.iter()
|
||||||
|
.map(|arg| tpl::Input::new(&arg.ident))
|
||||||
|
.collect(),
|
||||||
|
outputs: &program.outputs
|
||||||
|
};
|
||||||
|
let main_rs = main_rs.render().unwrap();
|
||||||
|
|
||||||
|
let types_rs = tpl::TypesRs {
|
||||||
|
typedefs: state
|
||||||
|
.types()
|
||||||
|
.into_iter()
|
||||||
|
.map(|ty| {
|
||||||
|
Ok(tpl::TypeDef::new(
|
||||||
|
&ty.ident,
|
||||||
|
ty.generics.as_ref(),
|
||||||
|
expand_type(&state, &ty.ty)?
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, Diagnostic>>()?
|
||||||
|
};
|
||||||
|
let types_rs = types_rs.render().unwrap();
|
||||||
|
|
||||||
|
let mut buf = String::new();
|
||||||
|
buf += "#![no_implicit_prelude]\n";
|
||||||
|
buf += "#![allow(unused_mut, while_true)]\n\n";
|
||||||
|
buf += "use crate::types::*;\n\n";
|
||||||
|
|
||||||
|
buf += "pub(crate) fn run(crate::Values {\n";
|
||||||
|
for input in program.inputs {
|
||||||
|
write!(buf, " mut {},\n", input.ident).unwrap();
|
||||||
|
state.add_variable(input.ident, Type::Int(Default::default()));
|
||||||
|
}
|
||||||
|
buf += "}: crate::Values) -> crate::Output {\n";
|
||||||
|
for s in program.stmts {
|
||||||
|
buf += " ";
|
||||||
|
buf += &expand_stmt(&mut state, s)?;
|
||||||
|
buf += "\n";
|
||||||
|
}
|
||||||
|
buf += " crate::Output {\n";
|
||||||
|
for output in program.outputs {
|
||||||
|
for ident in output.idents {
|
||||||
|
write!(buf, " {ident}: ").unwrap();
|
||||||
|
buf += &expand_expr_with_type(
|
||||||
|
&state,
|
||||||
|
&Type::Int(Default::default()),
|
||||||
|
Expression::Ident(ident)
|
||||||
|
)?;
|
||||||
|
buf += ",\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf += " }\n";
|
||||||
|
buf += "}\n\n";
|
||||||
|
|
||||||
|
while let Some(idx) = state.next_function_instance() {
|
||||||
|
state.with_function_instance(idx, |fun, generics, ident| {
|
||||||
|
if verbose() {
|
||||||
|
eprint!("[compile] Expanding function instance {ident} ...");
|
||||||
|
}
|
||||||
|
let res = expand_function(&mut buf, &state, fun, generics, ident);
|
||||||
|
if verbose() {
|
||||||
|
eprintln!("done");
|
||||||
|
}
|
||||||
|
res
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(CompileResult::Ok {
|
||||||
|
main_rs: match syn::parse_file(&main_rs) {
|
||||||
|
Ok(main_rs) => main_rs,
|
||||||
|
Err(err) => return Ok(CompileResult::InternalErr(main_rs, err))
|
||||||
|
},
|
||||||
|
funs_rs: match syn::parse_file(&buf) {
|
||||||
|
Ok(funs_rs) => funs_rs,
|
||||||
|
Err(err) => return Ok(CompileResult::InternalErr(buf, err))
|
||||||
|
},
|
||||||
|
types_rs: match syn::parse_file(&types_rs) {
|
||||||
|
Ok(types_rs) => types_rs,
|
||||||
|
Err(err) => return Ok(CompileResult::InternalErr(types_rs, err))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_stmt(
|
||||||
|
state: &mut State<'_>,
|
||||||
|
stmt: Statement
|
||||||
|
) -> Result<Cow<'static, str>, Diagnostic> {
|
||||||
|
Ok(match stmt {
|
||||||
|
Statement::Decl(decl) => expand_decl(state, decl)?.into(),
|
||||||
|
Statement::Assign(assign) => expand_assign(state, assign)?.into(),
|
||||||
|
Statement::If(if_expr) => expand_if(state, if_expr)?.into(),
|
||||||
|
Statement::CoinFlip { head, prob, tail } => {
|
||||||
|
let mut buf = format!("match ::stdlib::Coin::flip({}) {{", prob);
|
||||||
|
buf += "::stdlib::Coin::Head => ";
|
||||||
|
buf += &expand_block(state, head)?;
|
||||||
|
buf += ", ::stdlib::Coin::Tail => ";
|
||||||
|
buf += &expand_block(state, tail)?;
|
||||||
|
buf += "}";
|
||||||
|
buf.into()
|
||||||
|
},
|
||||||
|
Statement::While(while_expr) => expand_while(state, while_expr)?.into(),
|
||||||
|
Statement::Block(block) => expand_block(state, block)?.into(),
|
||||||
|
Statement::Return(Return {
|
||||||
|
return_token, expr, ..
|
||||||
|
}) => {
|
||||||
|
let return_ty = state.return_ty().ok_or_else(|| {
|
||||||
|
Diagnostic::new(
|
||||||
|
return_token.span,
|
||||||
|
"Cannot return",
|
||||||
|
"Return statements may only be used in function bodies"
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
match expr {
|
||||||
|
Some(expr) => {
|
||||||
|
format!("return {};", expand_expr_with_type(state, return_ty, expr)?)
|
||||||
|
.into()
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
expect_tuple_exact(
|
||||||
|
return_ty,
|
||||||
|
0,
|
||||||
|
return_token.span,
|
||||||
|
return_token.span
|
||||||
|
)?;
|
||||||
|
"return;".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Statement::Continue(cont) => {
|
||||||
|
if !state.is_loop() {
|
||||||
|
return Err(Diagnostic::new(
|
||||||
|
cont.continue_token.span,
|
||||||
|
"Not a loop",
|
||||||
|
"You may only use `continue` in a loop body"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
"continue;".into()
|
||||||
|
},
|
||||||
|
Statement::Break(brake) => {
|
||||||
|
if !state.is_loop() {
|
||||||
|
return Err(Diagnostic::new(
|
||||||
|
brake.break_token.span,
|
||||||
|
"Not a loop",
|
||||||
|
"You may only use `break` in a loop body"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
"break;".into()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_block(state: &State<'_>, block: Block) -> Result<String, Diagnostic> {
|
||||||
|
let mut sub = state.substate();
|
||||||
|
let mut buf = "{".to_owned();
|
||||||
|
for s in block.stmts {
|
||||||
|
buf += &expand_stmt(&mut sub, s)?;
|
||||||
|
}
|
||||||
|
buf += "}";
|
||||||
|
Ok(buf)
|
||||||
|
}
|
126
src/compile/resolve_ty.rs
Normal file
126
src/compile/resolve_ty.rs
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
use super::State;
|
||||||
|
use crate::{
|
||||||
|
ast::{Generics, Type, TypeCustom, TypePredef, TypeTuple},
|
||||||
|
diagnostic::Diagnostic
|
||||||
|
};
|
||||||
|
use proc_macro2::Ident;
|
||||||
|
use std::{borrow::Cow, collections::HashMap};
|
||||||
|
|
||||||
|
pub(super) trait GenericsMap {
|
||||||
|
type Out;
|
||||||
|
|
||||||
|
fn get(&self, ident: &Ident) -> Option<Self::Out>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> GenericsMap for (&Generics<Ident>, &'a Generics<Type>) {
|
||||||
|
type Out = &'a Type;
|
||||||
|
|
||||||
|
fn get(&self, ident: &Ident) -> Option<Self::Out> {
|
||||||
|
self.0
|
||||||
|
.params
|
||||||
|
.iter()
|
||||||
|
.position(|param| param == ident)
|
||||||
|
.map(|index| self.1.params.iter().nth(index).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> GenericsMap for &'a HashMap<Ident, Type> {
|
||||||
|
type Out = &'a Type;
|
||||||
|
|
||||||
|
fn get(&self, ident: &Ident) -> Option<Self::Out> {
|
||||||
|
HashMap::get(self, ident)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: GenericsMap> GenericsMap for Option<T> {
|
||||||
|
type Out = T::Out;
|
||||||
|
|
||||||
|
fn get(&self, ident: &Ident) -> Option<Self::Out> {
|
||||||
|
self.as_ref().and_then(|this| this.get(ident))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> GenericsMap for (T, U)
|
||||||
|
where
|
||||||
|
T: GenericsMap,
|
||||||
|
U: GenericsMap<Out = T::Out>
|
||||||
|
{
|
||||||
|
type Out = T::Out;
|
||||||
|
|
||||||
|
fn get(&self, ident: &Ident) -> Option<Self::Out> {
|
||||||
|
self.0.get(ident).or_else(|| self.1.get(ident))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_typredef<'a, T>(ty: &'a TypePredef, generics: T) -> TypePredef
|
||||||
|
where
|
||||||
|
T: GenericsMap<Out = &'a Type> + Copy
|
||||||
|
{
|
||||||
|
TypePredef {
|
||||||
|
span: ty.span,
|
||||||
|
lt_token: ty.lt_token,
|
||||||
|
inner_ty: Box::new(resolve_ty(&ty.inner_ty, generics).into_owned()),
|
||||||
|
gt_token: ty.gt_token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn resolve_ty<'a, T>(ty: &'a Type, generics: T) -> Cow<'a, Type>
|
||||||
|
where
|
||||||
|
T: GenericsMap<Out = &'a Type> + Copy
|
||||||
|
{
|
||||||
|
match ty {
|
||||||
|
Type::Bool(_) | Type::Int(_) => ty.into(),
|
||||||
|
Type::Tuple(tuple) => Type::Tuple(TypeTuple {
|
||||||
|
paren_token: tuple.paren_token,
|
||||||
|
elems: tuple
|
||||||
|
.elems
|
||||||
|
.iter()
|
||||||
|
.map(|elem| resolve_ty(elem, generics).into_owned())
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
Type::Rc(rc) => Type::Rc(resolve_typredef(rc, generics)).into(),
|
||||||
|
Type::Option(option) => Type::Option(resolve_typredef(option, generics)).into(),
|
||||||
|
Type::Custom(custom) => {
|
||||||
|
if let Some(resolved_ty) = generics.get(&custom.ident) {
|
||||||
|
resolved_ty.into()
|
||||||
|
} else {
|
||||||
|
Type::Custom(TypeCustom {
|
||||||
|
ident: custom.ident.clone(),
|
||||||
|
generics: custom.generics.as_ref().map(|g| Generics {
|
||||||
|
lt_token: g.lt_token,
|
||||||
|
params: g
|
||||||
|
.params
|
||||||
|
.iter()
|
||||||
|
.map(|ty| resolve_ty(ty, generics).into_owned())
|
||||||
|
.collect(),
|
||||||
|
gt_token: g.gt_token
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Type::Any => Type::Any.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn resolve_tycustom<'a, 'b: 'a>(
|
||||||
|
state: &'a State<'b>,
|
||||||
|
custom: &'a TypeCustom
|
||||||
|
) -> Result<Cow<'a, Type>, Diagnostic> {
|
||||||
|
if custom.generics.is_none() {
|
||||||
|
if let Some(resolved_ty) = state.generic_types().get(&custom.ident) {
|
||||||
|
return Ok(resolved_ty.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let inner_ty = state.ty(&custom.ident)?;
|
||||||
|
Ok(resolve_ty(
|
||||||
|
&inner_ty.ty,
|
||||||
|
inner_ty.generics.as_ref().and_then(|def_generics| {
|
||||||
|
custom
|
||||||
|
.generics
|
||||||
|
.as_ref()
|
||||||
|
.map(|generics| (def_generics, generics))
|
||||||
|
})
|
||||||
|
))
|
||||||
|
}
|
399
src/compile/state.rs
Normal file
399
src/compile/state.rs
Normal file
|
@ -0,0 +1,399 @@
|
||||||
|
use super::resolve_ty::{resolve_ty, GenericsMap};
|
||||||
|
use crate::{
|
||||||
|
ast::{Fn, Generics, Type, TypeDef, TypeTuple},
|
||||||
|
diagnostic::Diagnostic,
|
||||||
|
verbose
|
||||||
|
};
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use proc_macro2::{Ident, Span};
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
cell::RefCell,
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
fmt::Write as _
|
||||||
|
};
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
|
||||||
|
fn mangled_fn_name(function: &Fn, generics: &Option<Generics<Type>>) -> String {
|
||||||
|
let mut ident = format!("fun_{}", function.ident);
|
||||||
|
if let Some(g) = generics {
|
||||||
|
for ty in &g.params {
|
||||||
|
write!(ident, "_{}", ty.mangled()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ident
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FunctionMap {
|
||||||
|
/// A list of function definitions.
|
||||||
|
functions: Vec<Fn>,
|
||||||
|
|
||||||
|
/// A mapping of function identifier to the function definition index.
|
||||||
|
idents: HashMap<Ident, usize>,
|
||||||
|
|
||||||
|
/// A list of generics for this function that are being used with the
|
||||||
|
/// mangled name of the function.
|
||||||
|
used_generics: RefCell<Vec<IndexMap<Option<Generics<Type>>, String>>>,
|
||||||
|
|
||||||
|
/// A list of function instances that have been emitted already.
|
||||||
|
emitted_generics: Vec<HashSet<usize>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FunctionMap {
|
||||||
|
/// Create a new function map with these available functions of which
|
||||||
|
/// none have any instances that are being used.
|
||||||
|
fn new(functions: Vec<Fn>) -> Self {
|
||||||
|
let idents = functions
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, fun)| (fun.ident.clone(), i))
|
||||||
|
.collect();
|
||||||
|
let len = functions.len();
|
||||||
|
Self {
|
||||||
|
functions,
|
||||||
|
idents,
|
||||||
|
used_generics: RefCell::new(vec![IndexMap::new(); len]),
|
||||||
|
emitted_generics: vec![HashSet::new(); len]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new, empty function map.
|
||||||
|
fn new_empty() -> Self {
|
||||||
|
Self {
|
||||||
|
functions: Vec::new(),
|
||||||
|
idents: HashMap::new(),
|
||||||
|
used_generics: RefCell::new(Vec::new()),
|
||||||
|
emitted_generics: Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an instance of a function and return its mangled name. If no
|
||||||
|
/// function with said ident exists, `None` is being returned.
|
||||||
|
fn get(
|
||||||
|
&self,
|
||||||
|
ident: &Ident,
|
||||||
|
generics: &Option<Generics<Type>>
|
||||||
|
) -> Option<(&Fn, String)> {
|
||||||
|
let idx = *self.idents.get(ident)?;
|
||||||
|
if !self
|
||||||
|
.used_generics
|
||||||
|
.borrow()
|
||||||
|
.get(idx)
|
||||||
|
.unwrap()
|
||||||
|
.contains_key(generics)
|
||||||
|
{
|
||||||
|
self.used_generics
|
||||||
|
.borrow_mut()
|
||||||
|
.get_mut(idx)
|
||||||
|
.unwrap()
|
||||||
|
.insert(
|
||||||
|
generics.clone(),
|
||||||
|
mangled_fn_name(&self.functions[idx], generics)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Some((
|
||||||
|
&self.functions[idx],
|
||||||
|
self.used_generics
|
||||||
|
.borrow()
|
||||||
|
.get(idx)
|
||||||
|
.unwrap()
|
||||||
|
.get(generics)
|
||||||
|
.unwrap()
|
||||||
|
.clone()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct FunctionIndex {
|
||||||
|
fun_idx: usize,
|
||||||
|
inst_idx: usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct State<'a> {
|
||||||
|
/// The parent state, or `None`.
|
||||||
|
parent: Option<&'a State<'a>>,
|
||||||
|
|
||||||
|
/// True if this substate represents a loop body.
|
||||||
|
is_loop: bool,
|
||||||
|
|
||||||
|
/// A mapping of custom type identifier to the corresponding typedef.
|
||||||
|
types: HashMap<Ident, TypeDef>,
|
||||||
|
|
||||||
|
/// A mapping of generic ident to concrete instance. Used only for
|
||||||
|
/// function.
|
||||||
|
generic_types: HashMap<Ident, Type>,
|
||||||
|
|
||||||
|
/// When the state represents a function, this stores the return
|
||||||
|
/// type of the function.
|
||||||
|
return_ty: Option<Type>,
|
||||||
|
|
||||||
|
/// All the functions, which is a bit more complex since we need to
|
||||||
|
/// track the generic instances that are being used in the program.
|
||||||
|
functions: FunctionMap,
|
||||||
|
|
||||||
|
/// A mapping of variable identifier to the type of the variable.
|
||||||
|
variables: HashMap<Ident, Type>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State<'static> {
|
||||||
|
pub(super) fn new(types: Vec<TypeDef>, functions: Vec<Fn>) -> Self {
|
||||||
|
Self {
|
||||||
|
parent: None,
|
||||||
|
is_loop: false,
|
||||||
|
types: types.into_iter().map(|ty| (ty.ident.clone(), ty)).collect(),
|
||||||
|
generic_types: HashMap::new(),
|
||||||
|
return_ty: None,
|
||||||
|
functions: FunctionMap::new(functions),
|
||||||
|
variables: HashMap::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn types(&self) -> impl Iterator<Item = &TypeDef> {
|
||||||
|
if self.parent.is_some() {
|
||||||
|
panic!("This function may only be called on the root state");
|
||||||
|
}
|
||||||
|
self.types.values()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn next_function_instance(&mut self) -> Option<FunctionIndex> {
|
||||||
|
if self.parent.is_some() {
|
||||||
|
panic!("This function may only be called on the root state");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (fun_idx, instances) in
|
||||||
|
self.functions.used_generics.get_mut().iter().enumerate()
|
||||||
|
{
|
||||||
|
for (inst_idx, _) in instances.iter().enumerate() {
|
||||||
|
if self.functions.emitted_generics[fun_idx].contains(&inst_idx) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
self.functions.emitted_generics[fun_idx].insert(inst_idx);
|
||||||
|
return Some(FunctionIndex { fun_idx, inst_idx });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn with_function_instance<F, T>(
|
||||||
|
&self,
|
||||||
|
idx: FunctionIndex,
|
||||||
|
callback: F
|
||||||
|
) -> T
|
||||||
|
where
|
||||||
|
F: FnOnce(&Fn, Option<Generics<Type>>, String) -> T
|
||||||
|
{
|
||||||
|
if self.parent.is_some() {
|
||||||
|
panic!("This function may only be called on the root state");
|
||||||
|
}
|
||||||
|
|
||||||
|
let used_generics = self.functions.used_generics.borrow();
|
||||||
|
|
||||||
|
let fun = &self.functions.functions[idx.fun_idx];
|
||||||
|
let (generics, ident) = used_generics
|
||||||
|
.get(idx.fun_idx)
|
||||||
|
.unwrap()
|
||||||
|
.get_index(idx.inst_idx)
|
||||||
|
.unwrap();
|
||||||
|
let generics = generics.clone();
|
||||||
|
let ident = ident.clone();
|
||||||
|
|
||||||
|
// since the callback might need to add another function instance
|
||||||
|
// or do a lookup, we cannot keep the borrow past this point.
|
||||||
|
drop(used_generics);
|
||||||
|
callback(fun, generics, ident)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> State<'a> {
|
||||||
|
pub(super) fn is_loop(&self) -> bool {
|
||||||
|
self.is_loop || self.parent.map(State::is_loop).unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn into_loop(mut self) -> Self {
|
||||||
|
self.is_loop = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn ty(&self, ident: &Ident) -> Result<&TypeDef, Diagnostic> {
|
||||||
|
if let Some(ty) = self.types.get(ident) {
|
||||||
|
return Ok(ty);
|
||||||
|
}
|
||||||
|
match self.parent {
|
||||||
|
Some(parent) => parent.ty(ident),
|
||||||
|
None => Err(Diagnostic::new(
|
||||||
|
ident.span(),
|
||||||
|
"Unknown type",
|
||||||
|
"This type has not been defined"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_generic_types(&self) -> bool {
|
||||||
|
!self.generic_types.is_empty()
|
||||||
|
|| self
|
||||||
|
.parent
|
||||||
|
.map(|parent| parent.has_generic_types())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn generic_types(&self) -> &HashMap<Ident, Type> {
|
||||||
|
if !self.generic_types.is_empty() {
|
||||||
|
return &self.generic_types;
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.parent.as_ref() {
|
||||||
|
Some(parent) => parent.generic_types(),
|
||||||
|
None => &self.generic_types
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn add_ty(&mut self, ident: Ident, ty: Type) {
|
||||||
|
if self.generic_types.is_empty() {
|
||||||
|
// ensure that only one (sub)state in the chain has generic types
|
||||||
|
if let Some(parent) = self.parent.as_ref() {
|
||||||
|
if parent.has_generic_types() {
|
||||||
|
panic!("One of the parent state already has generic types");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.generic_types.insert(ident, ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn return_ty(&self) -> Option<&Type> {
|
||||||
|
self.return_ty
|
||||||
|
.as_ref()
|
||||||
|
.or_else(|| self.parent.and_then(|parent| parent.return_ty()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn set_return_ty(&mut self, ty: Type) {
|
||||||
|
if self.return_ty().is_some() {
|
||||||
|
panic!(
|
||||||
|
"This state or one of its parent states already has a return type set"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.return_ty = Some(ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn fun(
|
||||||
|
&self,
|
||||||
|
ident: &Ident,
|
||||||
|
generics: &Option<Generics<Type>>
|
||||||
|
) -> Result<FunctionInstance<'_>, Diagnostic> {
|
||||||
|
let generics = generics.as_ref().map(|generics| Generics {
|
||||||
|
lt_token: generics.lt_token,
|
||||||
|
params: generics
|
||||||
|
.params
|
||||||
|
.iter()
|
||||||
|
.map(|param| resolve_ty(param, self.generic_types()).into_owned())
|
||||||
|
.collect(),
|
||||||
|
gt_token: generics.gt_token
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some((fun, fun_ident)) = self.functions.get(ident, &generics) {
|
||||||
|
match (fun.generics.as_ref(), generics.as_ref()) {
|
||||||
|
(None, None) => {},
|
||||||
|
(Some(fun_generics), Some(generics))
|
||||||
|
if fun_generics.params.len() == generics.params.len() => {},
|
||||||
|
_ => return Err(Diagnostic::new(
|
||||||
|
ident.span(),
|
||||||
|
"Mismatched generics",
|
||||||
|
"The generics at the call site differ with the function declaration"
|
||||||
|
))
|
||||||
|
};
|
||||||
|
return Ok(FunctionInstance {
|
||||||
|
fun,
|
||||||
|
generics,
|
||||||
|
ident: fun_ident
|
||||||
|
});
|
||||||
|
}
|
||||||
|
match self.parent {
|
||||||
|
Some(parent) => parent.fun(ident, &generics),
|
||||||
|
None => Err(Diagnostic::new(
|
||||||
|
ident.span(),
|
||||||
|
"Unknown function",
|
||||||
|
"This function has not been defined"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn var(&self, ident: &Ident) -> Result<&Type, Diagnostic> {
|
||||||
|
if let Some(ty) = self.variables.get(ident) {
|
||||||
|
return Ok(ty);
|
||||||
|
}
|
||||||
|
match self.parent {
|
||||||
|
Some(parent) => parent.var(ident),
|
||||||
|
None => Err(Diagnostic::new(
|
||||||
|
ident.span(),
|
||||||
|
"No such variable",
|
||||||
|
"Variable not defined in scope"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn add_variable(&mut self, ident: Ident, ty: Type) {
|
||||||
|
if verbose() {
|
||||||
|
eprintln!("[state] adding variable `{ident}` with type `{ty}`");
|
||||||
|
}
|
||||||
|
self.variables.insert(ident, ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn substate<'b>(&'b self) -> State<'b>
|
||||||
|
where
|
||||||
|
'a: 'b
|
||||||
|
{
|
||||||
|
State {
|
||||||
|
parent: Some(self),
|
||||||
|
is_loop: false,
|
||||||
|
types: HashMap::new(),
|
||||||
|
generic_types: HashMap::new(),
|
||||||
|
return_ty: None,
|
||||||
|
functions: FunctionMap::new_empty(),
|
||||||
|
variables: HashMap::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct FunctionInstance<'a> {
|
||||||
|
fun: &'a Fn,
|
||||||
|
generics: Option<Generics<Type>>,
|
||||||
|
pub ident: String
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Spanned for FunctionInstance<'_> {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
self.fun.ident.span()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FunctionInstance<'_> {
|
||||||
|
pub(super) fn inputs_len(&self) -> usize {
|
||||||
|
self.fun.inputs.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generics(&self) -> impl GenericsMap<Out = &Type> + Copy {
|
||||||
|
self.fun
|
||||||
|
.generics
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|fg| self.generics.as_ref().map(|ig| (fg, ig)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn inputs(&self) -> impl Iterator<Item = Cow<'_, Type>> {
|
||||||
|
let generics = self.generics();
|
||||||
|
self.fun
|
||||||
|
.inputs
|
||||||
|
.iter()
|
||||||
|
.map(move |arg| resolve_ty(&arg.ty, generics))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn output(&self) -> Cow<'_, Type> {
|
||||||
|
let generics = self.generics();
|
||||||
|
self.fun
|
||||||
|
.output
|
||||||
|
.as_ref()
|
||||||
|
.map(|(_, ty)| resolve_ty(ty, generics))
|
||||||
|
.unwrap_or_else(|| Type::Tuple(TypeTuple::default()).into())
|
||||||
|
}
|
||||||
|
}
|
61
src/compile/ty.rs
Normal file
61
src/compile/ty.rs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
use super::State;
|
||||||
|
use crate::{ast::Type, diagnostic::Diagnostic};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
pub(super) fn expand_type(
|
||||||
|
state: &State<'_>,
|
||||||
|
ty: &Type
|
||||||
|
) -> Result<Cow<'static, str>, Diagnostic> {
|
||||||
|
Ok(match ty {
|
||||||
|
Type::Bool(_) => "::stdlib::bool".into(),
|
||||||
|
Type::Int(_) => "::stdlib::int".into(),
|
||||||
|
|
||||||
|
Type::Tuple(tuple) => {
|
||||||
|
let mut buf = "(".to_owned();
|
||||||
|
for elem in &tuple.elems {
|
||||||
|
buf += &expand_type(state, elem)?;
|
||||||
|
buf += ", "
|
||||||
|
}
|
||||||
|
buf += ")";
|
||||||
|
buf.into()
|
||||||
|
},
|
||||||
|
|
||||||
|
Type::Rc(rc) => {
|
||||||
|
format!("::stdlib::Rc<{}>", expand_type(state, &rc.inner_ty)?).into()
|
||||||
|
},
|
||||||
|
|
||||||
|
Type::Option(option) => format!(
|
||||||
|
"::stdlib::Option<{}>",
|
||||||
|
expand_type(state, &option.inner_ty)?
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
|
||||||
|
Type::Custom(custom) => {
|
||||||
|
if let Some(ty) = state.generic_types().get(&custom.ident) {
|
||||||
|
if let Some(g) = custom.generics.as_ref() {
|
||||||
|
return Err(Diagnostic::new(
|
||||||
|
g.lt_token.span,
|
||||||
|
"Invalid Type",
|
||||||
|
"This generic type parameter cannot accept generics"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
expand_type(state, ty)?
|
||||||
|
} else {
|
||||||
|
let mut buf = format!("{}", custom.ident);
|
||||||
|
if let Some(g) = &custom.generics {
|
||||||
|
buf += "::<";
|
||||||
|
for (i, ty) in g.params.iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
buf += ", ";
|
||||||
|
}
|
||||||
|
buf += &expand_type(state, ty)?;
|
||||||
|
}
|
||||||
|
buf += ">";
|
||||||
|
}
|
||||||
|
buf.into()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Type::Any => unimplemented!()
|
||||||
|
})
|
||||||
|
}
|
220
src/diagnostic.rs
Normal file
220
src/diagnostic.rs
Normal file
|
@ -0,0 +1,220 @@
|
||||||
|
use ariadne::{Label, Report, ReportKind};
|
||||||
|
use proc_macro2::{LineColumn, Span};
|
||||||
|
use std::{
|
||||||
|
fmt::{Debug, Display},
|
||||||
|
io::{self, Write}
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||||
|
pub struct Source<'a> {
|
||||||
|
pub code: &'a str,
|
||||||
|
pub filename: Option<&'a str>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Source<'a> {
|
||||||
|
pub fn new(code: &'a str) -> Self {
|
||||||
|
Self {
|
||||||
|
code,
|
||||||
|
filename: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_filename(mut self, filename: &'a str) -> Self {
|
||||||
|
self.filename = Some(filename);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offset(&self, at: LineColumn) -> usize {
|
||||||
|
let line_offset: usize = self
|
||||||
|
.code
|
||||||
|
.split('\n')
|
||||||
|
.take(at.line - 1)
|
||||||
|
.map(|line| line.len() + 1)
|
||||||
|
.sum();
|
||||||
|
line_offset + at.column
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SourceSpan<'a> {
|
||||||
|
source: Source<'a>,
|
||||||
|
start: usize,
|
||||||
|
end: usize
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ariadne::Span for SourceSpan<'a> {
|
||||||
|
type SourceId = Source<'a>;
|
||||||
|
|
||||||
|
fn source(&self) -> &Self::SourceId {
|
||||||
|
&self.source
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start(&self) -> usize {
|
||||||
|
self.start
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(&self) -> usize {
|
||||||
|
self.end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SourceCache {
|
||||||
|
source: ariadne::Source
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SourceCache {
|
||||||
|
fn new(source: Source<'_>) -> Self {
|
||||||
|
Self {
|
||||||
|
source: ariadne::Source::from(source.code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ariadne::Cache<Source<'_>> for SourceCache {
|
||||||
|
fn fetch(&mut self, _: &Source<'_>) -> Result<&ariadne::Source, Box<dyn Debug>> {
|
||||||
|
Ok(&self.source)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display<'a>(&self, id: &'a Source<'_>) -> Option<Box<dyn Display + 'a>> {
|
||||||
|
id.filename
|
||||||
|
.map(|filename| Box::new(filename) as Box<dyn Display + 'a>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Diagnostic {
|
||||||
|
span: Span,
|
||||||
|
msg: String,
|
||||||
|
labels: Vec<(Span, String)>,
|
||||||
|
note: Option<String>,
|
||||||
|
help: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Diagnostic {
|
||||||
|
pub fn new<T, M>(span: Span, topic: T, msg: M) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
M: Into<String>
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
span,
|
||||||
|
msg: topic.into(),
|
||||||
|
labels: vec![(span, msg.into())],
|
||||||
|
note: None,
|
||||||
|
help: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_label<M>(mut self, span: Span, msg: M) -> Self
|
||||||
|
where
|
||||||
|
M: Into<String>
|
||||||
|
{
|
||||||
|
self.labels.push((span, msg.into()));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_note<N>(mut self, note: N) -> Self
|
||||||
|
where
|
||||||
|
N: Into<String>
|
||||||
|
{
|
||||||
|
self.note = Some(note.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_help<H>(mut self, help: H) -> Self
|
||||||
|
where
|
||||||
|
H: Into<String>
|
||||||
|
{
|
||||||
|
self.help = Some(help.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn no_such_field<M>(span: Span, msg: M) -> Self
|
||||||
|
where
|
||||||
|
M: Into<String>
|
||||||
|
{
|
||||||
|
Self::new(span, "No such field", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn type_mismatch<T, U>(
|
||||||
|
expected_ty: T,
|
||||||
|
expected_span: Span,
|
||||||
|
expression_ty: U,
|
||||||
|
expression_span: Span
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
T: Display,
|
||||||
|
U: Display
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
span: expression_span,
|
||||||
|
msg: "Type Mismatch".into(),
|
||||||
|
labels: if expected_span.start() < expression_span.start() {
|
||||||
|
vec![
|
||||||
|
(expected_span, format!("Expected type `{expected_ty}`,")),
|
||||||
|
(
|
||||||
|
expression_span,
|
||||||
|
format!("but expression is of type `{expression_ty}`")
|
||||||
|
),
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
vec![
|
||||||
|
(
|
||||||
|
expression_span,
|
||||||
|
format!("Expression is of type `{expression_ty}`,")
|
||||||
|
),
|
||||||
|
(expected_span, format!("but expected type `{expected_ty}`")),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
note: None,
|
||||||
|
help: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ice(err: syn::Error) -> Self {
|
||||||
|
Self::new(err.span(), "Internal Compiler Error", err.to_string()).with_note(
|
||||||
|
"The compiler produced invalid intermediate Rust code. This is a bug."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<syn::Error> for Diagnostic {
|
||||||
|
fn from(err: syn::Error) -> Self {
|
||||||
|
Self::new(err.span(), "Syntax Error", err.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Diagnostic {
|
||||||
|
fn into_report(self, source: Source<'_>) -> Report<SourceSpan<'_>> {
|
||||||
|
let start_offset = source.offset(self.span.start());
|
||||||
|
let mut report =
|
||||||
|
Report::build(ReportKind::Error, source, start_offset).with_message(self.msg);
|
||||||
|
for (label_span, label_msg) in self.labels {
|
||||||
|
report.add_label(
|
||||||
|
Label::new(SourceSpan {
|
||||||
|
source,
|
||||||
|
start: source.offset(label_span.start()),
|
||||||
|
end: source.offset(label_span.end())
|
||||||
|
})
|
||||||
|
.with_message(label_msg)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some(note) = self.note {
|
||||||
|
report.set_note(note);
|
||||||
|
}
|
||||||
|
if let Some(help) = self.help {
|
||||||
|
report.set_help(help);
|
||||||
|
}
|
||||||
|
|
||||||
|
report.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write<W>(self, source: Source<'_>, w: W) -> io::Result<()>
|
||||||
|
where
|
||||||
|
W: Write
|
||||||
|
{
|
||||||
|
self.into_report(source).write(SourceCache::new(source), w)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eprint(self, source: Source<'_>) -> io::Result<()> {
|
||||||
|
self.into_report(source).eprint(SourceCache::new(source))
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue