STL rework
- fixed lots of bugs - overlay libraries work correctly and reliably - the STL is an overlay library - examples updated
This commit is contained in:
225
Cargo.lock
generated
225
Cargo.lock
generated
@@ -24,6 +24,15 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.3.2"
|
||||
@@ -85,6 +94,25 @@ version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.79"
|
||||
@@ -109,9 +137,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.2.7"
|
||||
version = "4.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938"
|
||||
checksum = "80672091db20273a15cf9fdd4e47ed43b5091ec9841bf4c6145c9dfbbcae09ed"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -120,9 +148,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.2.7"
|
||||
version = "4.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd"
|
||||
checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -133,9 +161,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.2.0"
|
||||
version = "4.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4"
|
||||
checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -145,9 +173,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.4.1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1"
|
||||
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
@@ -155,6 +183,35 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.11"
|
||||
@@ -188,6 +245,22 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.8"
|
||||
@@ -199,6 +272,19 @@ dependencies = [
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"fnv",
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
@@ -273,6 +359,21 @@ version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.15"
|
||||
@@ -299,15 +400,16 @@ dependencies = [
|
||||
"itertools",
|
||||
"ordered-float",
|
||||
"paste",
|
||||
"rust-embed",
|
||||
"thiserror",
|
||||
"trait-set",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered-float"
|
||||
version = "3.6.0"
|
||||
version = "3.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13a384337e997e6860ffbaa83708b2ef329fd8c54cb67a5f64d421e0f943254f"
|
||||
checksum = "2fc2dbde8f8a79f2102cc474ceb0ad68e3b80b85289ea62389b60e66777e4213"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
@@ -345,6 +447,58 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "6.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b68543d5527e158213414a92832d2aab11a84d2571a5eb021ebe22c43aab066"
|
||||
dependencies = [
|
||||
"rust-embed-impl",
|
||||
"rust-embed-utils",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-impl"
|
||||
version = "6.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d4e0f0ced47ded9a68374ac145edd65a6c1fa13a96447b873660b2a568a0fd7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rust-embed-utils",
|
||||
"syn 1.0.109",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-utils"
|
||||
version = "7.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731"
|
||||
dependencies = [
|
||||
"globset",
|
||||
"sha2",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.19"
|
||||
@@ -359,6 +513,32 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.160"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stacker"
|
||||
version = "0.1.15"
|
||||
@@ -431,6 +611,12 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.8"
|
||||
@@ -449,6 +635,16 @@ version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
@@ -471,6 +667,15 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
|
||||
15
Cargo.toml
15
Cargo.toml
@@ -23,11 +23,12 @@ doc = false
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1.0"
|
||||
chumsky = "0.9.2"
|
||||
hashbrown = "0.13.2"
|
||||
ordered-float = "3.0"
|
||||
chumsky = "0.9"
|
||||
hashbrown = "0.13"
|
||||
ordered-float = "3.7"
|
||||
itertools = "0.10"
|
||||
dyn-clone = "1.0.11"
|
||||
clap = { version = "4.2.4", features = ["derive"] }
|
||||
trait-set = "0.3.0"
|
||||
paste = "1.0.12"
|
||||
dyn-clone = "1.0"
|
||||
clap = { version = "4.3", features = ["derive"] }
|
||||
trait-set = "0.3"
|
||||
paste = "1.0"
|
||||
rust-embed = { version = "6.6", features = ["include-exclude"] }
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import std::(parse_float, to_string)
|
||||
import std::(readline, print)
|
||||
import std::(proc::*, to_float, to_string, io::(readline, print))
|
||||
|
||||
export main := do{
|
||||
cps print "left operand: ";
|
||||
cps data = readline;
|
||||
let a = parse_float data;
|
||||
let a = to_float data;
|
||||
cps print "operator: ";
|
||||
cps op = readline;
|
||||
cps print ("you selected \"" ++ op ++ "\"\n");
|
||||
cps print "right operand: ";
|
||||
cps data = readline;
|
||||
let b = parse_float data;
|
||||
let b = to_float data;
|
||||
let result = (
|
||||
if op == "+" then a + b
|
||||
else if op == "-" then a - b
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import std::print
|
||||
import std::io::print
|
||||
|
||||
main := print "Hello, world!\n" "goodbye"
|
||||
@@ -1,41 +0,0 @@
|
||||
import option
|
||||
import super::fn::*
|
||||
|
||||
pair := \a.\b. \f. f a b
|
||||
|
||||
-- Constructors
|
||||
|
||||
export cons := \hd.\tl. option::some (pair hd tl)
|
||||
export end := option::none
|
||||
|
||||
export pop := \list.\default.\f.list default \cons.cons f
|
||||
|
||||
-- Operators
|
||||
|
||||
export reduce := \list.\acc.\f. (
|
||||
loop r on (list acc) with
|
||||
pop list acc \head.\tail. r tail (f acc head)
|
||||
)
|
||||
|
||||
export map := \list.\f. (
|
||||
loop r on (list) with
|
||||
pop list end \head.\tail. cons (f head) (r tail)
|
||||
)
|
||||
|
||||
export skip := \list.\n. (
|
||||
loop r on (list n) with
|
||||
if n == 0 then list
|
||||
else pop list end \head.\tail. r tail (n - 1)
|
||||
)
|
||||
|
||||
export take := \list.\n. (
|
||||
loop r on (list n) with
|
||||
if n == 0 then end
|
||||
else pop list end \head.\tail. cons head $ r tail $ n - 1
|
||||
)
|
||||
|
||||
new[...$item, ...$rest:1] =0x2p84=> (cons (...$item) new[...$rest])
|
||||
new[...$end] =0x1p84=> (cons (...$end) end)
|
||||
new[] =0x1p84=> end
|
||||
|
||||
export ::(new)
|
||||
@@ -1,6 +1,4 @@
|
||||
import std::(to_string, print)
|
||||
import super::list
|
||||
import fn::*
|
||||
import std::(proc::*, io::print, to_string)
|
||||
|
||||
export main := do{
|
||||
let foo = list::new[1, 2, 3, 4, 5, 6];
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
export some := \v. \d.\f. f v
|
||||
export none := \d.\f. d
|
||||
|
||||
export map := \option.\f. option none f
|
||||
export flatten := \option. option none \opt. opt
|
||||
export flatmap := \option.\f. option none \opt. map opt f
|
||||
@@ -1,15 +0,0 @@
|
||||
export Y := \f.(\x.f (x x))(\x.f (x x))
|
||||
|
||||
export loop $r on (..$parameters) with ...$tail =0x5p129=> Y (\$r.
|
||||
bind_names (..$parameters) (...$tail)
|
||||
) ..$parameters
|
||||
|
||||
-- bind each of the names in the first argument as a parameter for the second argument
|
||||
bind_names ($name ..$rest) $payload =0x1p250=> \$name. bind_names (..$rest) $payload
|
||||
bind_names () (...$payload) =0x1p250=> ...$payload
|
||||
|
||||
export ...$prefix $ ...$suffix:1 =0x1p34=> ...$prefix (...$suffix)
|
||||
export ...$prefix |> $fn ..$suffix:1 =0x2p32=> $fn (...$prefix) ..$suffix
|
||||
|
||||
export (...$argv) => ...$body =0x2p129=> (bind_names (...$argv) (...$body))
|
||||
$name => ...$body =0x1p129=> (\$name. ...$body)
|
||||
@@ -1,8 +1,4 @@
|
||||
import list
|
||||
import map
|
||||
import option
|
||||
import fn::*
|
||||
import std::(print, to_string)
|
||||
import std::(proc::*, io::print, to_string)
|
||||
|
||||
export main := do{
|
||||
let foo = map::new[
|
||||
|
||||
@@ -36,35 +36,13 @@
|
||||
},
|
||||
"extensions": {
|
||||
"recommendations": [
|
||||
"tomoki1207.pdf",
|
||||
"james-yu.latex-workshop",
|
||||
"bungcip.better-toml",
|
||||
"maptz.regionfolder",
|
||||
"serayuzgur.crates",
|
||||
"tamasfe.even-better-toml",
|
||||
"haskell.haskell",
|
||||
"justusadam.language-haskell",
|
||||
"yzhang.markdown-all-in-one",
|
||||
"goessner.mdmath",
|
||||
"gruntfuggly.todo-tree"
|
||||
"gruntfuggly.todo-tree",
|
||||
"vadimcn.vscode-lldb"
|
||||
]
|
||||
},
|
||||
"launch": {
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Cargo launch",
|
||||
"cwd": "${workspaceFolder:orchid}",
|
||||
"program": "${workspaceFolder}/target/debug/orchid",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"run",
|
||||
]
|
||||
},
|
||||
"args": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ newline_style = "Unix"
|
||||
normalize_comments = true
|
||||
wrap_comments = true
|
||||
overflow_delimited_expr = true
|
||||
single_line_if_else_max_width = 50
|
||||
use_small_heuristics = "Max"
|
||||
|
||||
# literals
|
||||
|
||||
2
src/bin/cli/mod.rs
Normal file
2
src/bin/cli/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod prompt;
|
||||
pub use prompt::cmd_prompt;
|
||||
11
src/bin/cli/prompt.rs
Normal file
11
src/bin/cli/prompt.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use std::io::{self, Error, Write};
|
||||
|
||||
pub fn cmd_prompt(prompt: &str) -> Result<(String, Vec<String>), Error> {
|
||||
print!("{}", prompt);
|
||||
io::stdout().flush()?;
|
||||
let mut cmdln = String::new();
|
||||
io::stdin().read_line(&mut cmdln)?;
|
||||
let mut segments = cmdln.split(' ');
|
||||
let cmd = if let Some(cmd) = segments.next() { cmd } else { "" };
|
||||
Ok((cmd.to_string(), segments.map(str::to_string).collect()))
|
||||
}
|
||||
259
src/bin/main.rs
259
src/bin/main.rs
@@ -1,127 +1,129 @@
|
||||
use std::borrow::Borrow;
|
||||
mod cli;
|
||||
|
||||
use std::fs::File;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
use std::process;
|
||||
|
||||
use clap::Parser;
|
||||
use hashbrown::HashMap;
|
||||
use itertools::Itertools;
|
||||
use orchidlang::interner::{InternedDisplay, Interner, Sym};
|
||||
use orchidlang::pipeline::file_loader::{mk_cache, Loaded};
|
||||
use orchidlang::pipeline::{
|
||||
collect_consts, collect_rules, from_const_tree, parse_layer, ProjectTree,
|
||||
};
|
||||
use orchidlang::rule::Repo;
|
||||
use orchidlang::sourcefile::{FileEntry, Import};
|
||||
use orchidlang::{ast_to_interpreted, interpreter, stl};
|
||||
use orchidlang::{ast, ast_to_interpreted, interpreter, pipeline, rule, stl};
|
||||
|
||||
use crate::cli::cmd_prompt;
|
||||
|
||||
/// Orchid interpreter
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// Folder containing main.orc
|
||||
/// Folder containing main.orc or the manually specified entry module
|
||||
#[arg(short, long, default_value = ".")]
|
||||
pub project: String,
|
||||
pub dir: String,
|
||||
/// Entrypoint for the interpreter
|
||||
#[arg(short, long, default_value = "main::main")]
|
||||
pub main: String,
|
||||
/// Maximum number of steps taken by the macro executor
|
||||
#[arg(long, default_value_t = 10_000)]
|
||||
pub macro_limit: usize,
|
||||
/// Print the parsed ruleset and exit
|
||||
#[arg(long)]
|
||||
pub dump_repo: bool,
|
||||
/// Step through the macro execution process in the specified symbol
|
||||
#[arg(long, default_value = "")]
|
||||
pub macro_debug: String,
|
||||
}
|
||||
impl Args {
|
||||
pub fn chk_proj(&self) -> Result<(), String> {
|
||||
let mut path = PathBuf::from(&self.project);
|
||||
path.push(PathBuf::from("main.orc"));
|
||||
if File::open(&path).is_ok() {
|
||||
/// Validate the project directory and the
|
||||
pub fn chk_dir_main(&self) -> Result<(), String> {
|
||||
let dir_path = PathBuf::from(&self.dir);
|
||||
if !dir_path.is_dir() {
|
||||
return Err(format!("{} is not a directory", dir_path.display()));
|
||||
}
|
||||
let segs = self.main.split("::").collect::<Vec<_>>();
|
||||
if segs.len() < 2 {
|
||||
return Err("Entry point too short".to_string());
|
||||
}
|
||||
let (pathsegs, _) = segs.split_at(segs.len() - 1);
|
||||
let mut possible_files = pathsegs.iter().scan(dir_path, |path, seg| {
|
||||
path.push(seg);
|
||||
Some(path.with_extension("orc"))
|
||||
});
|
||||
if possible_files.all(|p| File::open(p).is_err()) {
|
||||
return Err(format!(
|
||||
"{} not found in {}",
|
||||
pathsegs.join("::"),
|
||||
PathBuf::from(&self.dir).display()
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("{} not found", path.display()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
args.chk_proj().unwrap_or_else(|e| panic!("{e}"));
|
||||
run_dir(PathBuf::try_from(args.project).unwrap().borrow());
|
||||
pub fn chk_proj(&self) -> Result<(), String> {
|
||||
self.chk_dir_main()
|
||||
}
|
||||
}
|
||||
|
||||
static PRELUDE_TXT: &str = r#"
|
||||
import std::(
|
||||
add, subtract, multiply, remainder, divide,
|
||||
equals, ifthenelse,
|
||||
concatenate
|
||||
)
|
||||
|
||||
export ...$a + ...$b =0x2p36=> (add (...$a) (...$b))
|
||||
export ...$a - ...$b:1 =0x2p36=> (subtract (...$a) (...$b))
|
||||
export ...$a * ...$b =0x1p36=> (multiply (...$a) (...$b))
|
||||
export ...$a % ...$b:1 =0x1p36=> (remainder (...$a) (...$b))
|
||||
export ...$a / ...$b:1 =0x1p36=> (divide (...$a) (...$b))
|
||||
export ...$a == ...$b =0x3p36=> (equals (...$a) (...$b))
|
||||
export ...$a ++ ...$b =0x4p36=> (concatenate (...$a) (...$b))
|
||||
|
||||
export do { ...$statement ; ...$rest:1 } =0x2p130=> statement (...$statement) do { ...$rest }
|
||||
export do { ...$return } =0x1p130=> ...$return
|
||||
|
||||
export statement (let $name = ...$value) ...$next =0x1p230=> (
|
||||
(\$name. ...$next) (...$value)
|
||||
)
|
||||
export statement (cps $name = ...$operation) ...$next =0x2p230=> (
|
||||
(...$operation) \$name. ...$next
|
||||
)
|
||||
export statement (cps ...$operation) ...$next =0x1p230=> (
|
||||
(...$operation) (...$next)
|
||||
)
|
||||
|
||||
export if ...$cond then ...$true else ...$false:1 =0x1p84=> (
|
||||
ifthenelse (...$cond) (...$true) (...$false)
|
||||
)
|
||||
|
||||
export ::(,)
|
||||
"#;
|
||||
|
||||
fn prelude_path(i: &Interner) -> Sym {
|
||||
i.i(&[i.i("prelude")][..])
|
||||
}
|
||||
fn mainmod_path(i: &Interner) -> Sym {
|
||||
i.i(&[i.i("main")][..])
|
||||
}
|
||||
fn entrypoint(i: &Interner) -> Sym {
|
||||
i.i(&[i.i("main"), i.i("main")][..])
|
||||
}
|
||||
|
||||
fn load_environment(i: &Interner) -> ProjectTree {
|
||||
let env = from_const_tree(
|
||||
HashMap::from([(i.i("std"), stl::mk_stl(i))]),
|
||||
&[i.i("std")],
|
||||
/// Load and parse all source related to the symbol `target` or all symbols
|
||||
/// in the namespace `target` in the context of the STL. All sourcefiles must
|
||||
/// reside within `dir`.
|
||||
fn load_dir(dir: &Path, target: Sym, i: &Interner) -> pipeline::ProjectTree {
|
||||
let file_cache = pipeline::file_loader::mk_dir_cache(dir.to_path_buf(), i);
|
||||
let library = stl::mk_stl(i, stl::StlOptions::default());
|
||||
pipeline::parse_layer(
|
||||
&[target],
|
||||
&|path| file_cache.find(&path),
|
||||
&library,
|
||||
&stl::mk_prelude(i),
|
||||
i,
|
||||
);
|
||||
let loader = |path: Sym| {
|
||||
if path == prelude_path(i) {
|
||||
Ok(Loaded::Code(Rc::new(PRELUDE_TXT.to_string())))
|
||||
} else {
|
||||
panic!(
|
||||
"Prelude pointed to non-std path {}",
|
||||
i.extern_vec(path).join("::")
|
||||
)
|
||||
}
|
||||
};
|
||||
parse_layer(&[prelude_path(i)], &loader, &env, &[], i).expect("prelude error")
|
||||
}
|
||||
|
||||
fn load_dir(i: &Interner, dir: &Path) -> ProjectTree {
|
||||
let environment = load_environment(i);
|
||||
let file_cache = mk_cache(dir.to_path_buf(), i);
|
||||
let loader = |path| file_cache.find(&path);
|
||||
let prelude =
|
||||
[FileEntry::Import(vec![Import { path: prelude_path(i), name: None }])];
|
||||
parse_layer(&[mainmod_path(i)], &loader, &environment, &prelude, i)
|
||||
.expect("Failed to load source code")
|
||||
}
|
||||
|
||||
pub fn run_dir(dir: &Path) {
|
||||
pub fn to_sym(data: &str, i: &Interner) -> Sym {
|
||||
i.i(&data.split("::").map(|s| i.i(s)).collect::<Vec<_>>()[..])
|
||||
}
|
||||
|
||||
/// A little utility to step through the resolution of a macro set
|
||||
pub fn macro_debug(repo: rule::Repo, mut code: ast::Expr, i: &Interner) {
|
||||
let mut idx = 0;
|
||||
println!("Macro debugger working on {}", code.bundle(i));
|
||||
loop {
|
||||
let (cmd, _) = cmd_prompt("cmd> ").unwrap();
|
||||
match cmd.trim() {
|
||||
"" | "n" | "next" =>
|
||||
if let Some(c) = repo.step(&code) {
|
||||
idx += 1;
|
||||
code = c;
|
||||
println!("Step {idx}: {}", code.bundle(i));
|
||||
},
|
||||
"p" | "print" => println!("Step {idx}: {}", code.bundle(i)),
|
||||
"d" | "dump" => println!("Rules: {}", repo.bundle(i)),
|
||||
"q" | "quit" => return,
|
||||
"h" | "help" => println!(
|
||||
"Available commands:
|
||||
\t<blank>, n, next\t\ttake a step
|
||||
\tp, print\t\tprint the current state
|
||||
\tq, quit\t\texit
|
||||
\th, help\t\tprint this text"
|
||||
),
|
||||
_ => {
|
||||
println!("unrecognized command \"{}\", try \"help\"", cmd);
|
||||
continue;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let args = Args::parse();
|
||||
args.chk_proj().unwrap_or_else(|e| panic!("{e}"));
|
||||
let dir = PathBuf::try_from(args.dir).unwrap();
|
||||
let i = Interner::new();
|
||||
let project = load_dir(&i, dir);
|
||||
let rules = collect_rules(&project);
|
||||
let consts = collect_consts(&project, &i);
|
||||
println!("Initializing rule repository with {} rules", rules.len());
|
||||
let repo = Repo::new(rules, &i).unwrap_or_else(|(rule, error)| {
|
||||
let main = to_sym(&args.main, &i);
|
||||
let project = load_dir(&dir, main, &i);
|
||||
let rules = pipeline::collect_rules(&project);
|
||||
let consts = pipeline::collect_consts(&project, &i);
|
||||
let repo = rule::Repo::new(rules, &i).unwrap_or_else(|(rule, error)| {
|
||||
panic!(
|
||||
"Rule error: {}
|
||||
Offending rule: {}",
|
||||
@@ -129,42 +131,36 @@ pub fn run_dir(dir: &Path) {
|
||||
rule.bundle(&i)
|
||||
)
|
||||
});
|
||||
println!("Repo dump: {}", repo.bundle(&i));
|
||||
if args.dump_repo {
|
||||
println!("Parsed rules: {}", repo.bundle(&i));
|
||||
return;
|
||||
} else if !args.macro_debug.is_empty() {
|
||||
let name = to_sym(&args.macro_debug, &i);
|
||||
let code = consts
|
||||
.get(&name)
|
||||
.unwrap_or_else(|| panic!("Constant {} not found", args.macro_debug));
|
||||
return macro_debug(repo, code.clone(), &i);
|
||||
}
|
||||
let mut exec_table = HashMap::new();
|
||||
for (name, source) in consts.iter() {
|
||||
// let nval = entrypoint(&i); let name = &nval; let source = &consts[name];
|
||||
let mut tree = source.clone();
|
||||
let displayname = i.extern_vec(*name).join("::");
|
||||
let macro_timeout = 100;
|
||||
println!("Executing macros in {displayname}...",);
|
||||
let mut idx = 0;
|
||||
let unmatched = loop {
|
||||
if idx == macro_timeout {
|
||||
panic!("Macro execution in {displayname} didn't halt")
|
||||
}
|
||||
match repo.step(&tree) {
|
||||
None => break tree,
|
||||
Some(phase) => {
|
||||
println!("Step {idx}/{macro_timeout}: {}", phase.bundle(&i));
|
||||
tree = phase;
|
||||
},
|
||||
}
|
||||
idx += 1;
|
||||
};
|
||||
let runtree = ast_to_interpreted(&unmatched)
|
||||
.unwrap_or_else(|e| panic!("Postmacro conversion error: {e}"));
|
||||
let (unmatched, steps_left) = repo.long_step(source, args.macro_limit + 1);
|
||||
assert!(steps_left > 0, "Macro execution in {displayname} did not halt");
|
||||
let runtree = ast_to_interpreted(&unmatched).unwrap_or_else(|e| {
|
||||
panic!("Postmacro conversion error in {displayname}: {e}")
|
||||
});
|
||||
exec_table.insert(*name, runtree);
|
||||
}
|
||||
println!("macro execution complete");
|
||||
let ctx =
|
||||
interpreter::Context { symbols: &exec_table, interner: &i, gas: None };
|
||||
let entrypoint = exec_table.get(&entrypoint(&i)).unwrap_or_else(|| {
|
||||
let entrypoint = exec_table.get(&main).unwrap_or_else(|| {
|
||||
let main = args.main;
|
||||
let symbols =
|
||||
exec_table.keys().map(|t| i.extern_vec(*t).join("::")).join(", ");
|
||||
panic!(
|
||||
"entrypoint not found, known keys are: {}",
|
||||
exec_table
|
||||
.keys()
|
||||
.map(|t| i.r(*t).iter().map(|t| i.r(*t)).join("::"))
|
||||
.join(", ")
|
||||
"Entrypoint not found!
|
||||
Entrypoint was {main}
|
||||
known keys are {symbols}"
|
||||
)
|
||||
});
|
||||
let io_handler = orchidlang::stl::handleIO;
|
||||
@@ -173,12 +169,11 @@ pub fn run_dir(dir: &Path) {
|
||||
ret.unwrap_or_else(|e| panic!("Runtime error: {}", e));
|
||||
if inert {
|
||||
println!("Settled at {}", state.expr().clause.bundle(&i));
|
||||
println!(
|
||||
"Remaining gas: {}",
|
||||
gas.map(|g| g.to_string()).unwrap_or(String::from("∞"))
|
||||
);
|
||||
if let Some(g) = gas {
|
||||
println!("Remaining gas: {g}")
|
||||
}
|
||||
if gas == Some(0) {
|
||||
println!("Ran out of gas!")
|
||||
} else if gas == Some(0) {
|
||||
eprintln!("Ran out of gas!");
|
||||
process::exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,21 @@ use std::fmt::Debug;
|
||||
#[allow(unused)] // for the doc comments
|
||||
use dyn_clone::DynClone;
|
||||
|
||||
#[allow(unused)] // for the doc comments
|
||||
use crate::define_fn;
|
||||
#[allow(unused)] // for the doc comments
|
||||
use crate::foreign::{Atomic, ExternFn};
|
||||
#[allow(unused)] // for the doc comments
|
||||
use crate::write_fn_step;
|
||||
#[allow(unused)] // for the doc comments
|
||||
use crate::Primitive;
|
||||
|
||||
/// A macro that generates implementations of [Atomic] to simplify the
|
||||
/// development of external bindings for Orchid.
|
||||
///
|
||||
/// Most use cases are fulfilled by [define_fn], pathological cases can combine
|
||||
/// [write_fn_step] with manual [Atomic] implementations.
|
||||
///
|
||||
/// The macro depends on implementations of [`AsRef<Clause>`] and
|
||||
/// [`From<(&Self, Clause)>`] for extracting the clause to be processed and then
|
||||
/// reconstructing the [Atomic]. Naturally, supertraits of [Atomic] are also
|
||||
@@ -32,34 +39,35 @@ use crate::Primitive;
|
||||
///
|
||||
/// _definition of the `add` function in the STL_
|
||||
/// ```
|
||||
/// use orchidlang::{Literal};
|
||||
/// use orchidlang::interpreted::ExprInst;
|
||||
/// use orchidlang::stl::Numeric;
|
||||
/// use orchidlang::stl::litconv::with_lit;
|
||||
/// use orchidlang::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
///
|
||||
/// /// Convert a literal to a string using Rust's conversions for floats, chars and
|
||||
/// /// uints respectively
|
||||
/// #[derive(Clone)]
|
||||
/// pub struct Add2;
|
||||
/// externfn_impl!(Add2, |_: &Self, x: ExprInst| Ok(Add1 { x }));
|
||||
/// struct ToString;
|
||||
///
|
||||
/// #[derive(Debug, Clone)]
|
||||
/// pub struct Add1 {
|
||||
/// x: ExprInst,
|
||||
/// externfn_impl!{
|
||||
/// ToString, |_: &Self, expr_inst: ExprInst|{
|
||||
/// Ok(InternalToString {
|
||||
/// expr_inst
|
||||
/// })
|
||||
/// }
|
||||
/// atomic_redirect!(Add1, x);
|
||||
/// atomic_impl!(Add1);
|
||||
/// externfn_impl!(Add1, |this: &Self, x: ExprInst| {
|
||||
/// let a: Numeric = this.x.clone().try_into()?;
|
||||
/// Ok(Add0 { a, x })
|
||||
/// });
|
||||
///
|
||||
/// #[derive(Debug, Clone)]
|
||||
/// pub struct Add0 {
|
||||
/// a: Numeric,
|
||||
/// x: ExprInst,
|
||||
/// }
|
||||
/// atomic_redirect!(Add0, x);
|
||||
/// atomic_impl!(Add0, |Self { a, x }: &Self, _| {
|
||||
/// let b: Numeric = x.clone().try_into()?;
|
||||
/// Ok((*a + b).into())
|
||||
/// #[derive(std::fmt::Debug,Clone)]
|
||||
/// struct InternalToString {
|
||||
/// expr_inst: ExprInst,
|
||||
/// }
|
||||
/// atomic_redirect!(InternalToString, expr_inst);
|
||||
/// atomic_impl!(InternalToString, |Self { expr_inst }: &Self, _|{
|
||||
/// with_lit(expr_inst, |l| Ok(match l {
|
||||
/// Literal::Char(c) => c.to_string(),
|
||||
/// Literal::Uint(i) => i.to_string(),
|
||||
/// Literal::Num(n) => n.to_string(),
|
||||
/// Literal::Str(s) => s.clone(),
|
||||
/// })).map(|s| Literal::Str(s).into())
|
||||
/// });
|
||||
/// ```
|
||||
#[macro_export]
|
||||
@@ -92,7 +100,11 @@ macro_rules! atomic_impl {
|
||||
// branch off or wrap up
|
||||
let clause = if inert {
|
||||
let closure = $next_phase;
|
||||
match closure(&next_self, ctx) {
|
||||
let res: Result<
|
||||
$crate::interpreted::Clause,
|
||||
std::rc::Rc<dyn $crate::foreign::ExternError>,
|
||||
> = closure(&next_self, ctx);
|
||||
match res {
|
||||
Ok(r) => r,
|
||||
Err(e) => return Err($crate::interpreter::RuntimeError::Extern(e)),
|
||||
}
|
||||
|
||||
@@ -26,6 +26,15 @@ use crate::write_fn_step;
|
||||
/// defined in the first step and returns a [Result] of the success type or
|
||||
/// `Rc<dyn ExternError>`.
|
||||
///
|
||||
/// To avoid typing the same expression a lot, the conversion is optional.
|
||||
/// If it is omitted, the field is initialized with a [TryInto::try_into] call
|
||||
/// from `&ExprInst` to the target type. In this case, the error is
|
||||
/// short-circuited using `?` so conversions through `FromResidual` are allowed.
|
||||
/// The optional syntax starts with `as`.
|
||||
///
|
||||
/// If all conversions are omitted, the alias definition (`expr=$ident in`) has
|
||||
/// no effect and is therefore optional.
|
||||
///
|
||||
/// Finally, the body of the function is provided as an expression which can
|
||||
/// reference all of the arguments by their names, each bound to a ref of the
|
||||
/// specified type.
|
||||
@@ -45,13 +54,59 @@ use crate::write_fn_step;
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// A simpler format is also offered for unary functions:
|
||||
///
|
||||
/// ```
|
||||
/// use orchidlang::stl::litconv::with_lit;
|
||||
/// use orchidlang::{define_fn, Literal};
|
||||
///
|
||||
/// define_fn! {
|
||||
/// /// Convert a literal to a string using Rust's conversions for floats,
|
||||
/// /// chars and uints respectively
|
||||
/// ToString = |x| with_lit(x, |l| Ok(match l {
|
||||
/// Literal::Char(c) => c.to_string(),
|
||||
/// Literal::Uint(i) => i.to_string(),
|
||||
/// Literal::Num(n) => n.to_string(),
|
||||
/// Literal::Str(s) => s.clone(),
|
||||
/// })).map(|s| Literal::Str(s).into())
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! define_fn {
|
||||
// Unary function entry
|
||||
($( #[ $attr:meta ] )* $qual:vis $name:ident = $body:expr) => {paste::paste!{
|
||||
$crate::write_fn_step!(
|
||||
$( #[ $attr ] )* $qual $name
|
||||
>
|
||||
[< Internal $name >]
|
||||
);
|
||||
$crate::write_fn_step!(
|
||||
[< Internal $name >]
|
||||
{}
|
||||
out = expr => Ok(expr);
|
||||
{
|
||||
let lambda = $body;
|
||||
lambda(out)
|
||||
}
|
||||
);
|
||||
}};
|
||||
// xname is optional only if every conversion is implicit
|
||||
($( #[ $attr:meta ] )* $qual:vis $name:ident {
|
||||
$( $arg:ident: $typ:ty ),+
|
||||
} => $body:expr) => {
|
||||
$crate::define_fn!{expr=expr in
|
||||
$( #[ $attr ] )* $qual $name {
|
||||
$( $arg: $typ ),*
|
||||
} => $body
|
||||
}
|
||||
};
|
||||
// multi-parameter function entry
|
||||
(expr=$xname:ident in
|
||||
$( #[ $attr:meta ] )*
|
||||
$qual:vis $name:ident {
|
||||
$arg0:ident: $typ0:ty as $parse0:expr
|
||||
$(, $arg:ident: $typ:ty as $parse:expr )*
|
||||
$arg0:ident: $typ0:ty $( as $parse0:expr )?
|
||||
$(, $arg:ident: $typ:ty $( as $parse:expr )? )*
|
||||
} => $body:expr
|
||||
) => {paste::paste!{
|
||||
// Generate initial state
|
||||
@@ -64,8 +119,10 @@ macro_rules! define_fn {
|
||||
$crate::define_fn!(@MIDDLE $xname [< Internal $name >] ($body)
|
||||
()
|
||||
(
|
||||
($arg0: $typ0 as $parse0)
|
||||
$( ($arg: $typ as $parse) )*
|
||||
( $arg0: $typ0 $( as $parse0)? )
|
||||
$(
|
||||
( $arg: $typ $( as $parse)? )
|
||||
)*
|
||||
)
|
||||
);
|
||||
}};
|
||||
@@ -80,10 +137,10 @@ macro_rules! define_fn {
|
||||
// later fields
|
||||
(
|
||||
// field that should be processed by this step
|
||||
( $arg0:ident: $typ0:ty as $parse0:expr )
|
||||
( $arg0:ident: $typ0:ty $( as $parse0:expr )? )
|
||||
// ensure that we have a next stage
|
||||
$(
|
||||
( $arg:ident: $typ:ty as $parse:expr )
|
||||
( $arg:ident: $typ:ty $( as $parse:expr )? )
|
||||
)+
|
||||
)
|
||||
) => {paste::paste!{
|
||||
@@ -93,7 +150,7 @@ macro_rules! define_fn {
|
||||
$( $arg_prev:ident : $typ_prev:ty ),*
|
||||
}
|
||||
[< $name $arg0:upper >]
|
||||
where $arg0:$typ0 = $xname => $parse0;
|
||||
where $arg0:$typ0 $( = $xname => $parse0 )? ;
|
||||
);
|
||||
$crate::define_fn!(@MIDDLE $xname [< $name $arg0:upper >] ($body)
|
||||
(
|
||||
@@ -101,7 +158,9 @@ macro_rules! define_fn {
|
||||
($arg0: $typ0)
|
||||
)
|
||||
(
|
||||
$( ($arg: $typ as $parse) )+
|
||||
$(
|
||||
( $arg: $typ $( as $parse)? )
|
||||
)+
|
||||
)
|
||||
);
|
||||
}};
|
||||
@@ -113,7 +172,7 @@ macro_rules! define_fn {
|
||||
)
|
||||
// the last one is initialized before the body runs
|
||||
(
|
||||
($arg0:ident: $typ0:ty as $parse0:expr)
|
||||
($arg0:ident: $typ0:ty $( as $parse0:expr )? )
|
||||
)
|
||||
) => {
|
||||
$crate::write_fn_step!(
|
||||
@@ -121,7 +180,7 @@ macro_rules! define_fn {
|
||||
{
|
||||
$( $arg_prev: $typ_prev ),*
|
||||
}
|
||||
$arg0:$typ0 = $xname => $parse0;
|
||||
$arg0:$typ0 $( = $xname => $parse0 )? ;
|
||||
$body
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
#[allow(unused)] // for doc
|
||||
use crate::define_fn;
|
||||
#[allow(unused)] // for doc
|
||||
use crate::foreign::Atomic;
|
||||
#[allow(unused)] // for doc
|
||||
use crate::foreign::ExternFn;
|
||||
#[allow(unused)] // for doc
|
||||
use crate::interpreted::ExprInst;
|
||||
|
||||
/// Write one step in the state machine representing a simple n-ary non-variadic
|
||||
/// Orchid function. There are no known use cases for it that aren't expressed
|
||||
/// better with [define_fn] which generates calls to this macro.
|
||||
/// Orchid function. Most use cases are better covered by [define_fn] which
|
||||
/// generates calls to this macro. This macro can be used in combination with
|
||||
/// manual [Atomic] implementations to define a function that only behaves like
|
||||
/// a simple n-ary non-variadic function with respect to some of its arguments.
|
||||
///
|
||||
/// There are three ways to call this macro for the initial state, internal
|
||||
/// state, and exit state. All of them are demonstrated in one example and
|
||||
@@ -25,14 +29,14 @@ use crate::interpreted::ExprInst;
|
||||
/// // Middle state
|
||||
/// write_fn_step!(
|
||||
/// CharAt1 {}
|
||||
/// CharAt0 where s = x => with_str(x, |s| Ok(s.clone()));
|
||||
/// CharAt0 where s: String = x => with_str(x, |s| Ok(s.clone()));
|
||||
/// );
|
||||
/// // Exit state
|
||||
/// write_fn_step!(
|
||||
/// CharAt0 { s: String }
|
||||
/// i = x => with_uint(x, Ok);
|
||||
/// {
|
||||
/// if let Some(c) = s.chars().nth(i as usize) {
|
||||
/// if let Some(c) = s.chars().nth(*i as usize) {
|
||||
/// Ok(Clause::P(Primitive::Literal(Literal::Char(c))))
|
||||
/// } else {
|
||||
/// RuntimeError::fail(
|
||||
@@ -52,7 +56,7 @@ use crate::interpreted::ExprInst;
|
||||
/// struct definition. A field called `expr_inst` of type [ExprInst] is added
|
||||
/// implicitly, so the first middle state has an empty field list. The next
|
||||
/// state is also provided, alongside the name and conversion of the next
|
||||
/// parameter from a [&ExprInst] under the provided alias to a
|
||||
/// parameter from a `&ExprInst` under the provided alias to a
|
||||
/// `Result<_, Rc<dyn ExternError>>`. The success type is inferred from the
|
||||
/// type of the field at the place of its actual definition. This conversion is
|
||||
/// done in the implementation of [ExternFn] which also places the new
|
||||
@@ -67,6 +71,12 @@ use crate::interpreted::ExprInst;
|
||||
/// argument names bound. The arguments here are all references to their actual
|
||||
/// types except for the last one which is converted from [ExprInst] immediately
|
||||
/// before the body is evaluated.
|
||||
///
|
||||
/// To avoid typing the same parsing process a lot, the conversion is optional.
|
||||
/// If it is omitted, the field is initialized with a [TryInto::try_into] call
|
||||
/// from `&ExprInst` to the target type. In this case, the error is
|
||||
/// short-circuited using `?` so conversions through `FromResidual` are allowed.
|
||||
/// The optional syntax starts with the `=` sign and ends before the semicolon.
|
||||
#[macro_export]
|
||||
macro_rules! write_fn_step {
|
||||
// write entry stage
|
||||
@@ -87,7 +97,7 @@ macro_rules! write_fn_step {
|
||||
$( $arg:ident : $typ:ty ),*
|
||||
}
|
||||
$next:ident where
|
||||
$added:ident $( : $added_typ:ty )? = $xname:ident => $extract:expr ;
|
||||
$added:ident $( : $added_typ:ty )? $( = $xname:ident => $extract:expr )? ;
|
||||
) => {
|
||||
$( #[ $attr ] )*
|
||||
#[derive(std::fmt::Debug, Clone)]
|
||||
@@ -100,8 +110,8 @@ macro_rules! write_fn_step {
|
||||
$crate::externfn_impl!(
|
||||
$name,
|
||||
|this: &Self, expr_inst: $crate::interpreted::ExprInst| {
|
||||
let $xname = &this.expr_inst;
|
||||
let $added $( :$added_typ )? = $extract?;
|
||||
let $added $( :$added_typ )? =
|
||||
$crate::write_fn_step!(@CONV &this.expr_inst $(, $xname $extract )?);
|
||||
Ok($next{
|
||||
$( $arg: this.$arg.clone(), )*
|
||||
$added, expr_inst
|
||||
@@ -114,23 +124,37 @@ macro_rules! write_fn_step {
|
||||
$( #[ $attr:meta ] )* $quant:vis $name:ident {
|
||||
$( $arg:ident: $typ:ty ),*
|
||||
}
|
||||
$added:ident $(: $added_typ:ty )? = $xname:ident => $extract:expr ;
|
||||
$added:ident $(: $added_typ:ty )? $( = $xname:ident => $extract:expr )? ;
|
||||
$process:expr
|
||||
) => {
|
||||
$( #[ $attr ] )*
|
||||
#[derive(std::fmt::Debug, Clone)]
|
||||
$quant struct $name {
|
||||
$( $arg: $typ, )+
|
||||
$( $arg: $typ, )*
|
||||
expr_inst: $crate::interpreted::ExprInst,
|
||||
}
|
||||
$crate::atomic_redirect!($name, expr_inst);
|
||||
$crate::atomic_impl!(
|
||||
$name,
|
||||
|Self{ $($arg, )* expr_inst }: &Self, _| {
|
||||
let $xname = expr_inst;
|
||||
let $added $(: $added_typ )? = $extract?;
|
||||
let added $(: $added_typ )? =
|
||||
$crate::write_fn_step!(@CONV expr_inst $(, $xname $extract )?);
|
||||
let $added = &added;
|
||||
$process
|
||||
}
|
||||
);
|
||||
};
|
||||
// Write conversion expression for an ExprInst
|
||||
(@CONV $locxname:expr, $xname:ident $extract:expr) => {
|
||||
{
|
||||
let $xname = $locxname;
|
||||
match $extract {
|
||||
Err(e) => return Err(e),
|
||||
Ok(r) => r,
|
||||
}
|
||||
}
|
||||
};
|
||||
(@CONV $locxname:expr) => {
|
||||
($locxname).try_into()?
|
||||
};
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ impl<T: Eq + Hash + Clone> TypedInterner<T> {
|
||||
pub fn r(&self, t: Tok<T>) -> &T {
|
||||
let values = self.values.borrow();
|
||||
let key = t.into_usize() - 1;
|
||||
values[key].0
|
||||
values[key].0.borrow()
|
||||
}
|
||||
|
||||
/// Intern a static reference without allocating the data on the heap
|
||||
@@ -91,7 +91,7 @@ impl<T: Eq + Hash + Clone> Drop for TypedInterner<T> {
|
||||
if !owned {
|
||||
continue;
|
||||
}
|
||||
unsafe { Box::from_raw((item as *const T).cast_mut()) };
|
||||
let _ = unsafe { Box::from_raw((item as *const T).cast_mut()) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ pub mod rule;
|
||||
pub mod stl;
|
||||
mod utils;
|
||||
|
||||
pub use interner::Sym;
|
||||
pub use representations::ast_to_interpreted::ast_to_interpreted;
|
||||
pub use representations::{
|
||||
ast, interpreted, sourcefile, tree, Literal, Location, PathSet, Primitive,
|
||||
|
||||
@@ -83,11 +83,7 @@ pub fn import_parser<'a>(
|
||||
Some(Import {
|
||||
path: ctx.interner().i(&path),
|
||||
name: {
|
||||
if name == ctx.interner().i("*") {
|
||||
None
|
||||
} else {
|
||||
Some(name)
|
||||
}
|
||||
if name == ctx.interner().i("*") { None } else { Some(name) }
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@@ -115,11 +115,7 @@ impl InternedDisplay for Lexeme {
|
||||
Self::PH(Placeholder { name, class }) => match *class {
|
||||
PHClass::Scalar => write!(f, "${}", i.r(*name)),
|
||||
PHClass::Vec { nonzero, prio } => {
|
||||
if nonzero {
|
||||
write!(f, "...")
|
||||
} else {
|
||||
write!(f, "..")
|
||||
}?;
|
||||
if nonzero { write!(f, "...") } else { write!(f, "..") }?;
|
||||
write!(f, "${}", i.r(*name))?;
|
||||
if prio != 0 {
|
||||
write!(f, ":{}", prio)?;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
//! File system implementation of the source loader callback
|
||||
//! Source loader callback definition and builtin implementations
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
use std::{fs, io};
|
||||
|
||||
use chumsky::text::Character;
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
use crate::interner::{Interner, Sym};
|
||||
use crate::pipeline::error::{
|
||||
ErrorPosition, ProjectError, UnexpectedDirectory,
|
||||
@@ -86,13 +89,52 @@ pub fn load_file(root: &Path, path: &[impl AsRef<str>]) -> IOResult {
|
||||
}
|
||||
|
||||
/// Generates a cached file loader for a directory
|
||||
pub fn mk_cache(root: PathBuf, i: &Interner) -> Cache<Sym, IOResult> {
|
||||
pub fn mk_dir_cache(root: PathBuf, i: &Interner) -> Cache<Sym, IOResult> {
|
||||
Cache::new(move |token: Sym, _this| -> IOResult {
|
||||
let path = i.r(token).iter().map(|t| i.r(*t).as_str()).collect::<Vec<_>>();
|
||||
load_file(&root, &path)
|
||||
})
|
||||
}
|
||||
|
||||
/// Load a file from the specified path from an embed table
|
||||
pub fn load_embed<T: 'static + RustEmbed>(path: &str, ext: &str) -> IOResult {
|
||||
let file_path = path.to_string() + ext;
|
||||
if let Some(file) = T::get(&file_path) {
|
||||
let s = file.data.iter().map(|c| c.to_char()).collect::<String>();
|
||||
Ok(Loaded::Code(Rc::new(s)))
|
||||
} else {
|
||||
let entries = T::iter()
|
||||
.map(|c| c.to_string())
|
||||
.filter_map(|path: String| {
|
||||
let item_prefix = path.to_string() + "/";
|
||||
path.strip_prefix(&item_prefix).map(|subpath| {
|
||||
let item_name = subpath
|
||||
.split_inclusive('/')
|
||||
.next()
|
||||
.expect("Exact match excluded earlier");
|
||||
item_name
|
||||
.strip_suffix('/') // subdirectory
|
||||
.or_else(|| item_name.strip_suffix(ext)) // file
|
||||
.expect("embed should be filtered to extension")
|
||||
.to_string()
|
||||
})
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
Ok(Loaded::Collection(Rc::new(entries)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a cached file loader for a [RustEmbed]
|
||||
pub fn mk_embed_cache<'a, T: 'static + RustEmbed>(
|
||||
ext: &'a str,
|
||||
i: &'a Interner,
|
||||
) -> Cache<'a, Sym, IOResult> {
|
||||
Cache::new(move |token: Sym, _this| -> IOResult {
|
||||
let path = i.extern_vec(token).join("/");
|
||||
load_embed::<T>(&path, ext)
|
||||
})
|
||||
}
|
||||
|
||||
/// Loads the string contents of a file at the given location.
|
||||
/// If the path points to a directory, raises an error.
|
||||
pub fn load_text(
|
||||
|
||||
@@ -3,14 +3,14 @@ use std::rc::Rc;
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use super::alias_map::AliasMap;
|
||||
use super::decls::InjectedAsFn;
|
||||
use super::decls::{InjectedAsFn, UpdatedFn};
|
||||
use crate::ast::{Expr, Rule};
|
||||
use crate::interner::{Interner, Sym, Tok};
|
||||
use crate::pipeline::{ProjectExt, ProjectModule};
|
||||
use crate::representations::tree::{ModEntry, ModMember};
|
||||
use crate::utils::Substack;
|
||||
|
||||
fn resolve(
|
||||
fn resolve_rec(
|
||||
token: Sym,
|
||||
alias_map: &AliasMap,
|
||||
i: &Interner,
|
||||
@@ -18,7 +18,7 @@ fn resolve(
|
||||
if let Some(alias) = alias_map.resolve(token) {
|
||||
Some(i.r(alias).clone())
|
||||
} else if let Some((foot, body)) = i.r(token).split_last() {
|
||||
let mut new_beginning = resolve(i.i(body), alias_map, i)?;
|
||||
let mut new_beginning = resolve_rec(i.i(body), alias_map, i)?;
|
||||
new_beginning.push(*foot);
|
||||
Some(new_beginning)
|
||||
} else {
|
||||
@@ -26,6 +26,18 @@ fn resolve(
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve(
|
||||
token: Sym,
|
||||
alias_map: &AliasMap,
|
||||
injected_as: &impl InjectedAsFn,
|
||||
i: &Interner,
|
||||
) -> Option<Sym> {
|
||||
injected_as(&i.r(token)[..]).or_else(|| {
|
||||
let next_v = resolve_rec(token, alias_map, i)?;
|
||||
Some(injected_as(&next_v).unwrap_or_else(|| i.i(&next_v)))
|
||||
})
|
||||
}
|
||||
|
||||
fn process_expr(
|
||||
expr: &Expr,
|
||||
alias_map: &AliasMap,
|
||||
@@ -33,16 +45,7 @@ fn process_expr(
|
||||
i: &Interner,
|
||||
) -> Expr {
|
||||
expr
|
||||
.map_names(&|n| {
|
||||
injected_as(&i.r(n)[..]).or_else(|| {
|
||||
let next_v = resolve(n, alias_map, i)?;
|
||||
// println!("Resolved alias {} to {}",
|
||||
// i.extern_vec(n).join("::"),
|
||||
// i.extern_all(&next_v).join("::")
|
||||
// );
|
||||
Some(injected_as(&next_v).unwrap_or_else(|| i.i(&next_v)))
|
||||
})
|
||||
})
|
||||
.map_names(&|n| resolve(n, alias_map, injected_as, i))
|
||||
.unwrap_or_else(|| expr.clone())
|
||||
}
|
||||
|
||||
@@ -54,10 +57,9 @@ fn apply_aliases_rec(
|
||||
alias_map: &AliasMap,
|
||||
i: &Interner,
|
||||
injected_as: &impl InjectedAsFn,
|
||||
updated: &impl UpdatedFn,
|
||||
) -> ProjectModule {
|
||||
let items = module
|
||||
.items
|
||||
.iter()
|
||||
let items = (module.items.iter())
|
||||
.map(|(name, ent)| {
|
||||
let ModEntry { exported, member } = ent;
|
||||
let member = match member {
|
||||
@@ -65,9 +67,7 @@ fn apply_aliases_rec(
|
||||
ModMember::Item(process_expr(expr, alias_map, injected_as, i)),
|
||||
ModMember::Sub(module) => {
|
||||
let subpath = path.push(*name);
|
||||
let is_ignored =
|
||||
injected_as(&subpath.iter().rev_vec_clone()).is_some();
|
||||
let new_mod = if is_ignored {
|
||||
let new_mod = if !updated(&subpath.iter().rev_vec_clone()) {
|
||||
module.clone()
|
||||
} else {
|
||||
let module = module.as_ref();
|
||||
@@ -77,6 +77,7 @@ fn apply_aliases_rec(
|
||||
alias_map,
|
||||
i,
|
||||
injected_as,
|
||||
updated,
|
||||
))
|
||||
};
|
||||
ModMember::Sub(new_mod)
|
||||
@@ -85,23 +86,18 @@ fn apply_aliases_rec(
|
||||
(*name, ModEntry { exported: *exported, member })
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
let rules = module
|
||||
.extra
|
||||
.rules
|
||||
.iter()
|
||||
let rules = (module.extra.rules.iter())
|
||||
.map(|rule| {
|
||||
let Rule { pattern, prio, template } = rule;
|
||||
Rule {
|
||||
prio: *prio,
|
||||
pattern: Rc::new(
|
||||
pattern
|
||||
.iter()
|
||||
(pattern.iter())
|
||||
.map(|expr| process_expr(expr, alias_map, injected_as, i))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
template: Rc::new(
|
||||
template
|
||||
.iter()
|
||||
(template.iter())
|
||||
.map(|expr| process_expr(expr, alias_map, injected_as, i))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
@@ -113,7 +109,11 @@ fn apply_aliases_rec(
|
||||
imports: module.imports.clone(),
|
||||
extra: ProjectExt {
|
||||
rules,
|
||||
exports: module.extra.exports.clone(),
|
||||
exports: (module.extra.exports.iter())
|
||||
.map(|(k, v)| {
|
||||
(*k, resolve(*v, alias_map, injected_as, i).unwrap_or(*v))
|
||||
})
|
||||
.collect(),
|
||||
file: module.extra.file.clone(),
|
||||
imports_from: module.extra.imports_from.clone(),
|
||||
},
|
||||
@@ -125,6 +125,14 @@ pub fn apply_aliases(
|
||||
alias_map: &AliasMap,
|
||||
i: &Interner,
|
||||
injected_as: &impl InjectedAsFn,
|
||||
updated: &impl UpdatedFn,
|
||||
) -> ProjectModule {
|
||||
apply_aliases_rec(Substack::Bottom, module, alias_map, i, injected_as)
|
||||
apply_aliases_rec(
|
||||
Substack::Bottom,
|
||||
module,
|
||||
alias_map,
|
||||
i,
|
||||
injected_as,
|
||||
updated,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use core::panic;
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::alias_map::AliasMap;
|
||||
use super::decls::InjectedAsFn;
|
||||
use super::decls::UpdatedFn;
|
||||
use crate::interner::{Interner, Tok};
|
||||
use crate::pipeline::error::{NotExported, ProjectError};
|
||||
use crate::pipeline::project_tree::{split_path, ProjectModule, ProjectTree};
|
||||
use crate::representations::tree::{ModMember, WalkErrorKind};
|
||||
use crate::utils::{pushed, Substack};
|
||||
use crate::utils::{pushed, unwrap_or, Substack};
|
||||
|
||||
/// Assert that a module identified by a path can see a given symbol
|
||||
fn assert_visible(
|
||||
@@ -15,18 +16,24 @@ fn assert_visible(
|
||||
project: &ProjectTree,
|
||||
i: &Interner,
|
||||
) -> Result<(), Rc<dyn ProjectError>> {
|
||||
let (tgt_item, tgt_path) = if let Some(s) = target.split_last() {
|
||||
s
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
let (tgt_item, tgt_path) = unwrap_or!(target.split_last(); return Ok(()));
|
||||
let shared_len =
|
||||
source.iter().zip(tgt_path.iter()).take_while(|(a, b)| a == b).count();
|
||||
let shared_root =
|
||||
project.0.walk(&tgt_path[..shared_len], false).expect("checked in parsing");
|
||||
let direct_parent =
|
||||
shared_root.walk(&tgt_path[shared_len..], true).map_err(|e| {
|
||||
match e.kind {
|
||||
let vis_ignored_len = usize::min(tgt_path.len(), shared_len + 1);
|
||||
let private_root =
|
||||
(project.0).walk(&tgt_path[..vis_ignored_len], false).unwrap_or_else(|e| {
|
||||
let path_slc = &tgt_path[..vis_ignored_len];
|
||||
let bad_path = i.extern_all(path_slc).join("::");
|
||||
eprintln!(
|
||||
"Error while walking {bad_path}; {:?} on step {}",
|
||||
e.kind, e.pos
|
||||
);
|
||||
eprintln!("looking from {}", i.extern_all(source).join("::"));
|
||||
panic!("")
|
||||
});
|
||||
let direct_parent = private_root
|
||||
.walk(&tgt_path[vis_ignored_len..], true)
|
||||
.map_err(|e| match e.kind {
|
||||
WalkErrorKind::Missing => panic!("checked in parsing"),
|
||||
WalkErrorKind::Private => {
|
||||
let full_path = &tgt_path[..shared_len + e.pos];
|
||||
@@ -40,11 +47,9 @@ fn assert_visible(
|
||||
}
|
||||
.rc()
|
||||
},
|
||||
}
|
||||
})?;
|
||||
let tgt_item_exported = direct_parent.extra.exports.contains_key(tgt_item);
|
||||
let target_prefixes_source =
|
||||
shared_len == tgt_path.len() && source.get(shared_len) == Some(tgt_item);
|
||||
let target_prefixes_source = shared_len == tgt_path.len();
|
||||
if !tgt_item_exported && !target_prefixes_source {
|
||||
let (file, sub) = split_path(target, project);
|
||||
let (ref_file, ref_sub) = split_path(source, project);
|
||||
@@ -69,20 +74,30 @@ fn collect_aliases_rec(
|
||||
project: &ProjectTree,
|
||||
alias_map: &mut AliasMap,
|
||||
i: &Interner,
|
||||
injected_as: &impl InjectedAsFn,
|
||||
updated: &impl UpdatedFn,
|
||||
) -> Result<(), Rc<dyn ProjectError>> {
|
||||
// Assume injected module has been alias-resolved
|
||||
let mod_path_v = path.iter().rev_vec_clone();
|
||||
if injected_as(&mod_path_v).is_some() {
|
||||
if !updated(&mod_path_v) {
|
||||
return Ok(());
|
||||
};
|
||||
for (&name, &target_mod) in module.extra.imports_from.iter() {
|
||||
let target_mod_v = i.r(target_mod);
|
||||
for (&name, &target_mod_name) in module.extra.imports_from.iter() {
|
||||
let target_mod_v = i.r(target_mod_name);
|
||||
let target_sym_v = pushed(target_mod_v, name);
|
||||
assert_visible(&mod_path_v, &target_sym_v, project, i)?;
|
||||
let sym_path_v = pushed(&mod_path_v, name);
|
||||
let sym_path = i.i(&sym_path_v);
|
||||
let target_sym = i.i(&target_sym_v);
|
||||
let target_mod = (project.0.walk(target_mod_v, false))
|
||||
.expect("checked above in assert_visible");
|
||||
let target_sym =
|
||||
*target_mod.extra.exports.get(&name).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"error in {}, {} has no member {}",
|
||||
i.extern_all(&mod_path_v).join("::"),
|
||||
i.extern_all(target_mod_v).join("::"),
|
||||
i.r(name)
|
||||
)
|
||||
});
|
||||
alias_map.link(sym_path, target_sym);
|
||||
}
|
||||
for (&name, entry) in module.items.iter() {
|
||||
@@ -97,7 +112,7 @@ fn collect_aliases_rec(
|
||||
project,
|
||||
alias_map,
|
||||
i,
|
||||
injected_as,
|
||||
updated,
|
||||
)?
|
||||
}
|
||||
Ok(())
|
||||
@@ -109,14 +124,7 @@ pub fn collect_aliases(
|
||||
project: &ProjectTree,
|
||||
alias_map: &mut AliasMap,
|
||||
i: &Interner,
|
||||
injected_as: &impl InjectedAsFn,
|
||||
updated: &impl UpdatedFn,
|
||||
) -> Result<(), Rc<dyn ProjectError>> {
|
||||
collect_aliases_rec(
|
||||
Substack::Bottom,
|
||||
module,
|
||||
project,
|
||||
alias_map,
|
||||
i,
|
||||
injected_as,
|
||||
)
|
||||
collect_aliases_rec(Substack::Bottom, module, project, alias_map, i, updated)
|
||||
}
|
||||
|
||||
@@ -4,4 +4,5 @@ use crate::interner::{Sym, Tok};
|
||||
|
||||
trait_set! {
|
||||
pub trait InjectedAsFn = Fn(&[Tok<String>]) -> Option<Sym>;
|
||||
pub trait UpdatedFn = Fn(&[Tok<String>]) -> bool;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::alias_map::AliasMap;
|
||||
use super::apply_aliases::apply_aliases;
|
||||
use super::collect_aliases::collect_aliases;
|
||||
use super::decls::InjectedAsFn;
|
||||
use super::decls::{InjectedAsFn, UpdatedFn};
|
||||
use crate::interner::Interner;
|
||||
use crate::pipeline::error::ProjectError;
|
||||
use crate::pipeline::project_tree::ProjectTree;
|
||||
@@ -16,21 +14,11 @@ pub fn resolve_imports(
|
||||
project: ProjectTree,
|
||||
i: &Interner,
|
||||
injected_as: &impl InjectedAsFn,
|
||||
updated: &impl UpdatedFn,
|
||||
) -> Result<ProjectTree, Rc<dyn ProjectError>> {
|
||||
let mut map = AliasMap::new();
|
||||
collect_aliases(project.0.as_ref(), &project, &mut map, i, injected_as)?;
|
||||
println!(
|
||||
"Aliases: {{{:?}}}",
|
||||
map
|
||||
.targets
|
||||
.iter()
|
||||
.map(|(kt, vt)| format!(
|
||||
"{} => {}",
|
||||
i.extern_vec(*kt).join("::"),
|
||||
i.extern_vec(*vt).join("::")
|
||||
))
|
||||
.join(", ")
|
||||
);
|
||||
let new_mod = apply_aliases(project.0.as_ref(), &map, i, injected_as);
|
||||
collect_aliases(project.0.as_ref(), &project, &mut map, i, updated)?;
|
||||
let new_mod =
|
||||
apply_aliases(project.0.as_ref(), &map, i, injected_as, updated);
|
||||
Ok(ProjectTree(Rc::new(new_mod)))
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ mod import_resolution;
|
||||
mod parse_layer;
|
||||
mod project_tree;
|
||||
mod source_loader;
|
||||
mod split_name;
|
||||
|
||||
pub use parse_layer::parse_layer;
|
||||
pub use project_tree::{
|
||||
|
||||
@@ -34,13 +34,16 @@ pub fn parse_layer(
|
||||
};
|
||||
let source =
|
||||
source_loader::load_source(targets, prelude, i, loader, &|path| {
|
||||
injected_as(path).is_some()
|
||||
environment.0.walk(&i.r(path)[..], false).is_ok()
|
||||
})?;
|
||||
let tree = project_tree::build_tree(source, i, prelude, &injected_names)?;
|
||||
let sum = ProjectTree(Rc::new(
|
||||
environment.0.as_ref().clone() + tree.0.as_ref().clone(),
|
||||
environment.0.as_ref().clone().overlay(tree.0.as_ref().clone()),
|
||||
));
|
||||
let resolvd = import_resolution::resolve_imports(sum, i, &injected_as)?;
|
||||
let resolvd =
|
||||
import_resolution::resolve_imports(sum, i, &injected_as, &|path| {
|
||||
tree.0.walk(path, false).is_ok()
|
||||
})?;
|
||||
// Addition among modules favours the left hand side.
|
||||
Ok(resolvd)
|
||||
}
|
||||
|
||||
@@ -57,11 +57,7 @@ fn source_to_module(
|
||||
let imports = data
|
||||
.iter()
|
||||
.filter_map(|ent| {
|
||||
if let FileEntry::Import(impv) = ent {
|
||||
Some(impv.iter())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if let FileEntry::Import(impv) = ent { Some(impv.iter()) } else { None }
|
||||
})
|
||||
.flatten()
|
||||
.cloned()
|
||||
@@ -182,6 +178,10 @@ fn files_to_module(
|
||||
i: &Interner,
|
||||
) -> Rc<Module<Expr, ProjectExt>> {
|
||||
let lvl = path.len();
|
||||
debug_assert!(
|
||||
files.iter().map(|f| f.path.len()).max().unwrap() >= lvl,
|
||||
"path is longer than any of the considered file paths"
|
||||
);
|
||||
let path_v = path.iter().rev_vec_clone();
|
||||
if files.len() == 1 && files[0].path.len() == lvl {
|
||||
return source_to_module(
|
||||
@@ -227,6 +227,7 @@ pub fn build_tree(
|
||||
prelude: &[FileEntry],
|
||||
injected: &impl InjectedOperatorsFn,
|
||||
) -> Result<ProjectTree, Rc<dyn ProjectError>> {
|
||||
assert!(!files.is_empty(), "A tree requires at least one module");
|
||||
let ops_cache = collect_ops::mk_cache(&files, i, injected);
|
||||
let mut entries = files
|
||||
.iter()
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
use std::println;
|
||||
use std::rc::Rc;
|
||||
|
||||
use hashbrown::HashSet;
|
||||
use itertools::Itertools;
|
||||
use trait_set::trait_set;
|
||||
|
||||
use crate::interner::{Interner, Sym, Tok};
|
||||
use crate::pipeline::error::{ModuleNotFound, ProjectError};
|
||||
use crate::pipeline::source_loader::LoadedSourceTable;
|
||||
use crate::pipeline::split_name::split_name;
|
||||
use crate::representations::tree::WalkErrorKind;
|
||||
use crate::utils::Cache;
|
||||
use crate::utils::{split_max_prefix, unwrap_or, Cache};
|
||||
|
||||
pub type OpsResult = Result<Rc<HashSet<Tok<String>>>, Rc<dyn ProjectError>>;
|
||||
pub type ExportedOpsCache<'a> = Cache<'a, Sym, OpsResult>;
|
||||
@@ -33,22 +30,12 @@ pub fn collect_exported_ops(
|
||||
i: &Interner,
|
||||
injected: &impl InjectedOperatorsFn,
|
||||
) -> OpsResult {
|
||||
if let Some(ops) = injected(path) {
|
||||
if path == i.i(&[i.i("prelude")][..]) {
|
||||
println!("%%% Prelude exported ops %%%");
|
||||
println!("{}", ops.iter().map(|t| i.r(*t)).join(", "));
|
||||
}
|
||||
return Ok(ops);
|
||||
}
|
||||
let injected = injected(path).unwrap_or_else(|| Rc::new(HashSet::new()));
|
||||
let is_file = |n: &[Tok<String>]| loaded.contains_key(&i.i(n));
|
||||
let path_s = &i.r(path)[..];
|
||||
let name_split = split_name(path_s, &is_file);
|
||||
let (fpath_v, subpath_v) = if let Some(f) = name_split {
|
||||
f
|
||||
} else {
|
||||
return Ok(Rc::new(
|
||||
loaded
|
||||
.keys()
|
||||
let name_split = split_max_prefix(path_s, &is_file);
|
||||
let (fpath_v, subpath_v) = unwrap_or!(name_split; return Ok(Rc::new(
|
||||
(loaded.keys())
|
||||
.copied()
|
||||
.filter_map(|modname| {
|
||||
let modname_s = i.r(modname);
|
||||
@@ -58,9 +45,9 @@ pub fn collect_exported_ops(
|
||||
None
|
||||
}
|
||||
})
|
||||
.chain(injected.iter().copied())
|
||||
.collect::<HashSet<_>>(),
|
||||
));
|
||||
};
|
||||
)));
|
||||
let fpath = i.i(fpath_v);
|
||||
let preparsed = &loaded[&fpath].preparsed;
|
||||
let module = preparsed.0.walk(subpath_v, false).map_err(|walk_err| {
|
||||
@@ -70,8 +57,7 @@ pub fn collect_exported_ops(
|
||||
},
|
||||
WalkErrorKind::Missing => ModuleNotFound {
|
||||
file: i.extern_vec(fpath),
|
||||
subpath: subpath_v
|
||||
.iter()
|
||||
subpath: (subpath_v.iter())
|
||||
.take(walk_err.pos)
|
||||
.map(|t| i.r(*t))
|
||||
.cloned()
|
||||
@@ -80,12 +66,11 @@ pub fn collect_exported_ops(
|
||||
.rc(),
|
||||
}
|
||||
})?;
|
||||
let out: HashSet<_> =
|
||||
module.items.iter().filter(|(_, v)| v.exported).map(|(k, _)| *k).collect();
|
||||
if path == i.i(&[i.i("prelude")][..]) {
|
||||
println!("%%% Prelude exported ops %%%");
|
||||
println!("{}", out.iter().map(|t| i.r(*t)).join(", "));
|
||||
}
|
||||
let out = (module.items.iter())
|
||||
.filter(|(_, v)| v.exported)
|
||||
.map(|(k, _)| *k)
|
||||
.chain(injected.iter().copied())
|
||||
.collect::<HashSet<_>>();
|
||||
Ok(Rc::new(out))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use hashbrown::HashSet;
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::exported_ops::{ExportedOpsCache, OpsResult};
|
||||
use crate::interner::{Interner, Tok};
|
||||
@@ -34,13 +33,11 @@ pub fn collect_ops_for(
|
||||
) -> OpsResult {
|
||||
let tree = &loaded[&i.i(file)].preparsed.0;
|
||||
let mut ret = HashSet::new();
|
||||
println!("collecting ops for {}", i.extern_all(file).join("::"));
|
||||
tree_all_ops(tree.as_ref(), &mut ret);
|
||||
tree.visit_all_imports(&mut |modpath, _module, import| {
|
||||
if let Some(n) = import.name {
|
||||
ret.insert(n);
|
||||
} else {
|
||||
println!("\tglob import from {}", i.extern_vec(import.path).join("::"));
|
||||
let path = import_abs_path(file, modpath, &i.r(import.path)[..], i)
|
||||
.expect("This error should have been caught during loading");
|
||||
ret.extend(ops_cache.find(&i.i(&path))?.iter().copied());
|
||||
@@ -48,9 +45,5 @@ pub fn collect_ops_for(
|
||||
Ok::<_, Rc<dyn ProjectError>>(())
|
||||
})?;
|
||||
ret.drain_filter(|t| !is_op(i.r(*t)));
|
||||
if file == &[i.i("map")][..] {
|
||||
println!(" %%% ops in map %%% ");
|
||||
println!("{}", ret.iter().map(|t| i.r(*t)).join(", "))
|
||||
}
|
||||
Ok(Rc::new(ret))
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ impl Add for ProjectExt {
|
||||
pub type ProjectModule = Module<Expr, ProjectExt>;
|
||||
|
||||
/// Module corresponding to the root of a project
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProjectTree(pub Rc<ProjectModule>);
|
||||
|
||||
fn collect_rules_rec(bag: &mut Vec<Rule>, module: &ProjectModule) {
|
||||
|
||||
@@ -3,12 +3,12 @@ use std::rc::Rc;
|
||||
|
||||
use super::loaded_source::{LoadedSource, LoadedSourceTable};
|
||||
use super::preparse::preparse;
|
||||
use crate::interner::{Interner, Sym, Tok};
|
||||
use crate::interner::{Interner, Sym};
|
||||
use crate::pipeline::error::ProjectError;
|
||||
use crate::pipeline::file_loader::{load_text, IOResult, Loaded};
|
||||
use crate::pipeline::import_abs_path::import_abs_path;
|
||||
use crate::pipeline::split_name::split_name;
|
||||
use crate::representations::sourcefile::FileEntry;
|
||||
use crate::utils::split_max_prefix;
|
||||
|
||||
/// Load the source at the given path or all within if it's a collection,
|
||||
/// and all sources imported from these.
|
||||
@@ -18,46 +18,26 @@ fn load_abs_path_rec(
|
||||
prelude: &[FileEntry],
|
||||
i: &Interner,
|
||||
get_source: &impl Fn(Sym) -> IOResult,
|
||||
is_injected: &impl Fn(&[Tok<String>]) -> bool,
|
||||
is_injected_module: &impl Fn(Sym) -> bool,
|
||||
) -> Result<(), Rc<dyn ProjectError>> {
|
||||
let abs_pathv = i.r(abs_path);
|
||||
// short-circuit if this import is defined externally or already known
|
||||
if is_injected(abs_pathv) | table.contains_key(&abs_path) {
|
||||
// # Termination
|
||||
//
|
||||
// Every recursion of this function either
|
||||
// - adds one of the files in the source directory to `table` or
|
||||
// - recursively traverses a directory tree
|
||||
// therefore eventually the function exits, assuming that the directory tree
|
||||
// contains no cycles.
|
||||
|
||||
// Termination: exit if entry already visited
|
||||
if table.contains_key(&abs_path) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// try splitting the path to file, swallowing any IO errors
|
||||
let is_file = |p| (get_source)(p).map(|l| l.is_code()).unwrap_or(false);
|
||||
let name_split = split_name(abs_pathv, &|p| is_file(i.i(p)));
|
||||
let filename = if let Some((f, _)) = name_split {
|
||||
f
|
||||
} else {
|
||||
// If the path could not be split to file, load it as directory
|
||||
let coll = if let Loaded::Collection(c) = (get_source)(abs_path)? {
|
||||
c
|
||||
}
|
||||
// ^^ raise any IO error that was previously swallowed
|
||||
else {
|
||||
panic!("split_name returned None but the path is a file")
|
||||
};
|
||||
// recurse on all files and folders within
|
||||
for item in coll.iter() {
|
||||
let abs_subpath = abs_pathv
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(iter::once(i.i(item)))
|
||||
.collect::<Vec<_>>();
|
||||
load_abs_path_rec(
|
||||
i.i(&abs_subpath),
|
||||
table,
|
||||
prelude,
|
||||
i,
|
||||
get_source,
|
||||
is_injected,
|
||||
)?
|
||||
}
|
||||
return Ok(());
|
||||
};
|
||||
// otherwise load, preparse and record this file
|
||||
let is_file = |sym| get_source(sym).map(|l| l.is_code()).unwrap_or(false);
|
||||
let name_split = split_max_prefix(&i.r(abs_path)[..], &|p| is_file(i.i(p)));
|
||||
if let Some((filename, _)) = name_split {
|
||||
// if the filename is valid, load, preparse and record this file
|
||||
let text = load_text(i.i(filename), &get_source, i)?;
|
||||
let preparsed = preparse(
|
||||
filename.iter().map(|t| i.r(*t)).cloned().collect(),
|
||||
@@ -65,7 +45,10 @@ fn load_abs_path_rec(
|
||||
prelude,
|
||||
i,
|
||||
)?;
|
||||
table.insert(abs_path, LoadedSource { text, preparsed: preparsed.clone() });
|
||||
table.insert(i.i(filename), LoadedSource {
|
||||
text,
|
||||
preparsed: preparsed.clone(),
|
||||
});
|
||||
// recurse on all imported modules
|
||||
preparsed.0.visit_all_imports(&mut |modpath, _module, import| {
|
||||
let abs_pathv =
|
||||
@@ -77,23 +60,64 @@ fn load_abs_path_rec(
|
||||
prelude,
|
||||
i,
|
||||
get_source,
|
||||
is_injected,
|
||||
is_injected_module,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
// If the path is not within a file, load it as directory
|
||||
let coll = match get_source(abs_path) {
|
||||
Ok(Loaded::Collection(coll)) => coll,
|
||||
Ok(Loaded::Code(_)) =>
|
||||
unreachable!("split_name returned None but the path is a file"),
|
||||
Err(e) => {
|
||||
let parent = i.r(abs_path).split_last().expect("import path nonzero").1;
|
||||
// exit without error if it was injected, or raise any IO error that was
|
||||
// previously swallowed
|
||||
return if is_injected_module(i.i(parent)) { Ok(()) } else { Err(e) };
|
||||
},
|
||||
};
|
||||
// recurse on all files and folders within
|
||||
for item in coll.iter() {
|
||||
let abs_subpath = (i.r(abs_path).iter())
|
||||
.copied()
|
||||
.chain(iter::once(i.i(item)))
|
||||
.collect::<Vec<_>>();
|
||||
load_abs_path_rec(
|
||||
i.i(&abs_subpath),
|
||||
table,
|
||||
prelude,
|
||||
i,
|
||||
get_source,
|
||||
is_injected_module,
|
||||
)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Load and preparse all files reachable from the load targets via
|
||||
/// imports that aren't injected.
|
||||
///
|
||||
/// is_injected_module must return false for injected symbols, but may return
|
||||
/// true for parents of injected modules that are not directly part of the
|
||||
/// injected data (the ProjectTree doesn't make a distinction between the two)
|
||||
pub fn load_source(
|
||||
targets: &[Sym],
|
||||
prelude: &[FileEntry],
|
||||
i: &Interner,
|
||||
get_source: &impl Fn(Sym) -> IOResult,
|
||||
is_injected: &impl Fn(&[Tok<String>]) -> bool,
|
||||
is_injected_module: &impl Fn(Sym) -> bool,
|
||||
) -> Result<LoadedSourceTable, Rc<dyn ProjectError>> {
|
||||
let mut table = LoadedSourceTable::new();
|
||||
for target in targets {
|
||||
load_abs_path_rec(*target, &mut table, prelude, i, get_source, is_injected)?
|
||||
load_abs_path_rec(
|
||||
*target,
|
||||
&mut table,
|
||||
prelude,
|
||||
i,
|
||||
get_source,
|
||||
is_injected_module,
|
||||
)?
|
||||
}
|
||||
Ok(table)
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
use crate::interner::Tok;
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
// FIXME couldn't find a good factoring
|
||||
pub fn split_name<'a>(
|
||||
path: &'a [Tok<String>],
|
||||
is_valid: &impl Fn(&[Tok<String>]) -> bool,
|
||||
) -> Option<(&'a [Tok<String>], &'a [Tok<String>])> {
|
||||
for split in (0..=path.len()).rev() {
|
||||
let (filename, subpath) = path.split_at(split);
|
||||
if is_valid(filename) {
|
||||
return Some((filename, subpath));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -179,11 +179,8 @@ impl Clause {
|
||||
match self {
|
||||
Clause::Lambda(arg, body) => {
|
||||
arg.visit_names(binds, cb);
|
||||
let new_binds = if let Clause::Name(n) = arg.value {
|
||||
binds.push(n)
|
||||
} else {
|
||||
binds
|
||||
};
|
||||
let new_binds =
|
||||
if let Clause::Name(n) = arg.value { binds.push(n) } else { binds };
|
||||
for x in body.iter() {
|
||||
x.visit_names(new_binds, cb)
|
||||
}
|
||||
@@ -216,11 +213,7 @@ impl Clause {
|
||||
val.unwrap_or_else(|| e.clone())
|
||||
})
|
||||
.collect();
|
||||
if any_some {
|
||||
Some(Clause::S(*c, Rc::new(new_body)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if any_some { Some(Clause::S(*c, Rc::new(new_body))) } else { None }
|
||||
},
|
||||
Clause::Lambda(arg, body) => {
|
||||
let new_arg = arg.map_names(pred);
|
||||
|
||||
@@ -219,3 +219,9 @@ impl<T: Into<Literal>> From<T> for Clause {
|
||||
Self::P(Primitive::Literal(value.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<Clause>> From<T> for ExprInst {
|
||||
fn from(value: T) -> Self {
|
||||
value.into().wrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
//! Building blocks of a source file
|
||||
use std::iter;
|
||||
|
||||
use itertools::{Either, Itertools};
|
||||
|
||||
use crate::ast::{Constant, Rule};
|
||||
@@ -157,7 +159,9 @@ pub fn absolute_path(
|
||||
if tail.is_empty() {
|
||||
Ok(new_abs.to_vec())
|
||||
} else {
|
||||
absolute_path(new_abs, tail, i)
|
||||
let new_rel =
|
||||
iter::once(i.i("self")).chain(tail.iter().copied()).collect::<Vec<_>>();
|
||||
absolute_path(new_abs, &new_rel, i)
|
||||
}
|
||||
} else if *head == i.i("self") {
|
||||
Ok(abs_location.iter().chain(tail.iter()).copied().collect())
|
||||
|
||||
@@ -107,23 +107,23 @@ impl<TItem: Clone, TExt: Clone> Module<TItem, TExt> {
|
||||
) -> Result<(), E> {
|
||||
self.visit_all_imports_rec(Substack::Bottom, callback)
|
||||
}
|
||||
}
|
||||
|
||||
impl<TItem: Clone, TExt: Clone + Add<Output = TExt>> Add
|
||||
for Module<TItem, TExt>
|
||||
/// Combine two module trees; wherever they conflict, the overlay is
|
||||
/// preferred.
|
||||
pub fn overlay(mut self, overlay: Self) -> Self
|
||||
where
|
||||
TExt: Add<TExt, Output = TExt>,
|
||||
{
|
||||
type Output = Self;
|
||||
|
||||
fn add(mut self, rhs: Self) -> Self::Output {
|
||||
let Module { extra, imports, items } = rhs;
|
||||
let Module { extra, imports, items } = overlay;
|
||||
let mut new_items = HashMap::new();
|
||||
for (key, right) in items {
|
||||
// if both contain a submodule
|
||||
if let Some(left) = self.items.remove(&key) {
|
||||
if let ModMember::Sub(rsub) = &right.member {
|
||||
if let ModMember::Sub(lsub) = &left.member {
|
||||
// merge them with rhs exportedness
|
||||
let new_mod = lsub.as_ref().clone() + rsub.as_ref().clone();
|
||||
self.items.insert(key, ModEntry {
|
||||
let new_mod = lsub.as_ref().clone().overlay(rsub.as_ref().clone());
|
||||
new_items.insert(key, ModEntry {
|
||||
exported: right.exported,
|
||||
member: ModMember::Sub(Rc::new(new_mod)),
|
||||
});
|
||||
@@ -132,10 +132,14 @@ impl<TItem: Clone, TExt: Clone + Add<Output = TExt>> Add
|
||||
}
|
||||
}
|
||||
// otherwise right shadows left
|
||||
self.items.insert(key, right);
|
||||
new_items.insert(key, right);
|
||||
}
|
||||
new_items.extend(self.items.into_iter());
|
||||
self.imports.extend(imports.into_iter());
|
||||
self.extra = self.extra + extra;
|
||||
self
|
||||
Module {
|
||||
items: new_items,
|
||||
imports: self.imports,
|
||||
extra: self.extra + extra,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,11 +56,8 @@ fn mk_vec(pattern: &[Expr]) -> VecMatcher {
|
||||
pattern.last().map(vec_attrs).is_some(),
|
||||
"pattern must end with a vectorial"
|
||||
);
|
||||
let (left, (key, prio, nonzero), right) = split_at_max_vec(pattern)
|
||||
let (left, (key, _, nonzero), right) = split_at_max_vec(pattern)
|
||||
.expect("pattern must have vectorial placeholders at least at either end");
|
||||
if prio >= 1 {
|
||||
println!("Nondefault priority {} found", prio)
|
||||
}
|
||||
let r_sep_size = scal_cnt(right.iter());
|
||||
let (r_sep, r_side) = right.split_at(r_sep_size);
|
||||
let l_sep_size = scal_cnt(left.iter().rev());
|
||||
|
||||
@@ -114,29 +114,25 @@ impl<M: Matcher> Repository<M> {
|
||||
/// Attempt to run each rule in priority order `limit` times. Returns
|
||||
/// the final tree and the number of iterations left to the limit.
|
||||
#[allow(unused)]
|
||||
pub fn long_step(
|
||||
&self,
|
||||
code: &Expr,
|
||||
mut limit: usize,
|
||||
) -> Result<(Expr, usize), RuleError> {
|
||||
pub fn long_step(&self, code: &Expr, mut limit: usize) -> (Expr, usize) {
|
||||
if limit == 0 {
|
||||
return Ok((code.clone(), 0));
|
||||
return (code.clone(), 0);
|
||||
}
|
||||
if let Some(mut processed) = self.step(code) {
|
||||
limit -= 1;
|
||||
if limit == 0 {
|
||||
return Ok((processed, 0));
|
||||
return (processed, 0);
|
||||
}
|
||||
while let Some(out) = self.step(&processed) {
|
||||
limit -= 1;
|
||||
if limit == 0 {
|
||||
return Ok((out, 0));
|
||||
return (out, 0);
|
||||
}
|
||||
processed = out;
|
||||
}
|
||||
Ok((processed, limit))
|
||||
(processed, limit)
|
||||
} else {
|
||||
Ok((code.clone(), limit))
|
||||
(code.clone(), limit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
28
src/stl/arithmetic_error.rs
Normal file
28
src/stl/arithmetic_error.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::foreign::ExternError;
|
||||
|
||||
/// Various errors produced by arithmetic operations
|
||||
pub enum ArithmeticError {
|
||||
/// Integer overflow
|
||||
Overflow,
|
||||
/// Float overflow
|
||||
Infinity,
|
||||
/// Division or modulo by zero
|
||||
DivByZero,
|
||||
/// Other, unexpected operation produced NaN
|
||||
NaN,
|
||||
}
|
||||
|
||||
impl Display for ArithmeticError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::NaN => write!(f, "Operation resulted in NaN"),
|
||||
Self::Overflow => write!(f, "Integer overflow"),
|
||||
Self::Infinity => write!(f, "Operation resulted in Infinity"),
|
||||
Self::DivByZero => write!(f, "A division by zero was attempted"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternError for ArithmeticError {}
|
||||
4
src/stl/bool.orc
Normal file
4
src/stl/bool.orc
Normal file
@@ -0,0 +1,4 @@
|
||||
export ...$a == ...$b =0x3p36=> (equals (...$a) (...$b))
|
||||
export if ...$cond then ...$true else ...$false:1 =0x1p84=> (
|
||||
ifthenelse (...$cond) (...$true) (...$false)
|
||||
)
|
||||
92
src/stl/bool.rs
Normal file
92
src/stl/bool.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::foreign::Atom;
|
||||
use crate::interner::Interner;
|
||||
use crate::pipeline::ConstTree;
|
||||
use crate::representations::interpreted::{Clause, ExprInst};
|
||||
use crate::representations::Primitive;
|
||||
use crate::stl::litconv::with_lit;
|
||||
use crate::stl::AssertionError;
|
||||
use crate::{atomic_inert, define_fn, Literal, PathSet};
|
||||
|
||||
/// Booleans exposed to Orchid
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Boolean(pub bool);
|
||||
atomic_inert!(Boolean);
|
||||
|
||||
impl From<bool> for Boolean {
|
||||
fn from(value: bool) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ExprInst> for Boolean {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: &ExprInst) -> Result<Self, Self::Error> {
|
||||
let expr = value.expr();
|
||||
if let Clause::P(Primitive::Atom(Atom(a))) = &expr.clause {
|
||||
if let Some(b) = a.as_any().downcast_ref::<Boolean>() {
|
||||
return Ok(*b);
|
||||
}
|
||||
}
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
define_fn! {expr=x in
|
||||
/// Compares the inner values if
|
||||
///
|
||||
/// - both values are char,
|
||||
/// - both are string,
|
||||
/// - both are either uint or num
|
||||
Equals {
|
||||
a: Literal as with_lit(x, |l| Ok(l.clone())),
|
||||
b: Literal as with_lit(x, |l| Ok(l.clone()))
|
||||
} => Ok(Boolean::from(match (a, b) {
|
||||
(Literal::Char(c1), Literal::Char(c2)) => c1 == c2,
|
||||
(Literal::Num(n1), Literal::Num(n2)) => n1 == n2,
|
||||
(Literal::Str(s1), Literal::Str(s2)) => s1 == s2,
|
||||
(Literal::Uint(i1), Literal::Uint(i2)) => i1 == i2,
|
||||
(Literal::Num(n1), Literal::Uint(u1)) => *n1 == (*u1 as f64),
|
||||
(Literal::Uint(u1), Literal::Num(n1)) => *n1 == (*u1 as f64),
|
||||
(..) => AssertionError::fail(
|
||||
b.clone().into(),
|
||||
"the expected type"
|
||||
)?,
|
||||
}).to_atom_cls())
|
||||
}
|
||||
|
||||
// Even though it's a ternary function, IfThenElse is implemented as an unary
|
||||
// foreign function, as the rest of the logic can be defined in Orchid.
|
||||
define_fn! {
|
||||
/// Takes a boolean and two branches, runs the first if the bool is true, the
|
||||
/// second if it's false.
|
||||
IfThenElse = |x: &ExprInst| x.try_into()
|
||||
.map_err(|_| AssertionError::ext(x.clone(), "a boolean"))
|
||||
.map(|b: Boolean| if b.0 {Clause::Lambda {
|
||||
args: Some(PathSet { steps: Rc::new(vec![]), next: None }),
|
||||
body: Clause::Lambda {
|
||||
args: None,
|
||||
body: Clause::LambdaArg.wrap()
|
||||
}.wrap(),
|
||||
}} else {Clause::Lambda {
|
||||
args: None,
|
||||
body: Clause::Lambda {
|
||||
args: Some(PathSet { steps: Rc::new(vec![]), next: None }),
|
||||
body: Clause::LambdaArg.wrap(),
|
||||
}.wrap(),
|
||||
}})
|
||||
}
|
||||
|
||||
pub fn bool(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([(
|
||||
i.i("bool"),
|
||||
ConstTree::tree([
|
||||
(i.i("ifthenelse"), ConstTree::xfn(IfThenElse)),
|
||||
(i.i("equals"), ConstTree::xfn(Equals)),
|
||||
(i.i("true"), ConstTree::atom(Boolean(true))),
|
||||
(i.i("false"), ConstTree::atom(Boolean(false))),
|
||||
]),
|
||||
)])
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
use crate::atomic_inert;
|
||||
use crate::foreign::Atom;
|
||||
use crate::representations::interpreted::{Clause, ExprInst};
|
||||
use crate::representations::Primitive;
|
||||
|
||||
/// Booleans exposed to Orchid
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Boolean(pub bool);
|
||||
atomic_inert!(Boolean);
|
||||
|
||||
impl From<bool> for Boolean {
|
||||
fn from(value: bool) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ExprInst> for Boolean {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: ExprInst) -> Result<Self, Self::Error> {
|
||||
let expr = value.expr();
|
||||
if let Clause::P(Primitive::Atom(Atom(a))) = &expr.clause {
|
||||
if let Some(b) = a.as_any().downcast_ref::<Boolean>() {
|
||||
return Ok(*b);
|
||||
}
|
||||
}
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::super::assertion_error::AssertionError;
|
||||
use super::super::litconv::with_lit;
|
||||
use super::boolean::Boolean;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::representations::Literal;
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Compares the inner values if
|
||||
///
|
||||
/// - both values are char,
|
||||
/// - both are string,
|
||||
/// - both are either uint or num
|
||||
///
|
||||
/// Next state: [Equals1]
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Equals2;
|
||||
externfn_impl!(Equals2, |_: &Self, x: ExprInst| Ok(Equals1 { x }));
|
||||
|
||||
/// Prev state: [Equals2]; Next state: [Equals0]
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Equals1 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Equals1, x);
|
||||
atomic_impl!(Equals1);
|
||||
externfn_impl!(Equals1, |this: &Self, x: ExprInst| {
|
||||
with_lit(&this.x, |l| Ok(Equals0 { a: l.clone(), x }))
|
||||
});
|
||||
|
||||
/// Prev state: [Equals1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Equals0 {
|
||||
a: Literal,
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Equals0, x);
|
||||
atomic_impl!(Equals0, |Self { a, x }: &Self, _| {
|
||||
let eqls = with_lit(x, |l| {
|
||||
Ok(match (a, l) {
|
||||
(Literal::Char(c1), Literal::Char(c2)) => c1 == c2,
|
||||
(Literal::Num(n1), Literal::Num(n2)) => n1 == n2,
|
||||
(Literal::Str(s1), Literal::Str(s2)) => s1 == s2,
|
||||
(Literal::Uint(i1), Literal::Uint(i2)) => i1 == i2,
|
||||
(Literal::Num(n1), Literal::Uint(u1)) => *n1 == (*u1 as f64),
|
||||
(Literal::Uint(u1), Literal::Num(n1)) => *n1 == (*u1 as f64),
|
||||
(..) => AssertionError::fail(x.clone(), "the expected type")?,
|
||||
})
|
||||
})?;
|
||||
Ok(Boolean::from(eqls).to_atom_cls())
|
||||
});
|
||||
@@ -1,46 +0,0 @@
|
||||
use std::fmt::Debug;
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::super::assertion_error::AssertionError;
|
||||
use super::Boolean;
|
||||
use crate::representations::interpreted::{Clause, ExprInst};
|
||||
use crate::representations::PathSet;
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Takes a boolean and two branches, runs the first if the bool is true, the
|
||||
/// second if it's false.
|
||||
///
|
||||
/// Next state: [IfThenElse0]
|
||||
#[derive(Clone)]
|
||||
pub struct IfThenElse1;
|
||||
externfn_impl!(IfThenElse1, |_: &Self, x: ExprInst| Ok(IfThenElse0 { x }));
|
||||
|
||||
/// Prev state: [IfThenElse1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IfThenElse0 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(IfThenElse0, x);
|
||||
atomic_impl!(IfThenElse0, |this: &Self, _| {
|
||||
let Boolean(b) = this
|
||||
.x
|
||||
.clone()
|
||||
.try_into()
|
||||
.map_err(|_| AssertionError::ext(this.x.clone(), "a boolean"))?;
|
||||
Ok(if b {
|
||||
Clause::Lambda {
|
||||
args: Some(PathSet { steps: Rc::new(vec![]), next: None }),
|
||||
body: Clause::Lambda { args: None, body: Clause::LambdaArg.wrap() }
|
||||
.wrap(),
|
||||
}
|
||||
} else {
|
||||
Clause::Lambda {
|
||||
args: None,
|
||||
body: Clause::Lambda {
|
||||
args: Some(PathSet { steps: Rc::new(vec![]), next: None }),
|
||||
body: Clause::LambdaArg.wrap(),
|
||||
}
|
||||
.wrap(),
|
||||
}
|
||||
})
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
mod boolean;
|
||||
mod equals;
|
||||
mod ifthenelse;
|
||||
pub use boolean::Boolean;
|
||||
|
||||
use crate::interner::Interner;
|
||||
use crate::pipeline::ConstTree;
|
||||
|
||||
pub fn bool(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([
|
||||
(i.i("ifthenelse"), ConstTree::xfn(ifthenelse::IfThenElse1)),
|
||||
(i.i("equals"), ConstTree::xfn(equals::Equals2)),
|
||||
(i.i("true"), ConstTree::atom(Boolean(true))),
|
||||
(i.i("false"), ConstTree::atom(Boolean(false))),
|
||||
])
|
||||
}
|
||||
67
src/stl/conv.rs
Normal file
67
src/stl/conv.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
use chumsky::Parser;
|
||||
use ordered_float::NotNan;
|
||||
|
||||
use super::litconv::with_lit;
|
||||
use super::{ArithmeticError, AssertionError};
|
||||
use crate::foreign::ExternError;
|
||||
use crate::interner::Interner;
|
||||
use crate::parse::{float_parser, int_parser};
|
||||
use crate::pipeline::ConstTree;
|
||||
use crate::{define_fn, Literal};
|
||||
|
||||
define_fn! {
|
||||
/// parse a number. Accepts the same syntax Orchid does.
|
||||
ToFloat = |x| with_lit(x, |l| match l {
|
||||
Literal::Str(s) => float_parser()
|
||||
.parse(s.as_str())
|
||||
.map_err(|_| AssertionError::ext(
|
||||
x.clone(),
|
||||
"cannot be parsed into a float"
|
||||
)),
|
||||
Literal::Num(n) => Ok(*n),
|
||||
Literal::Uint(i) => NotNan::new(*i as f64)
|
||||
.map_err(|_| ArithmeticError::NaN.into_extern()),
|
||||
Literal::Char(char) => char
|
||||
.to_digit(10)
|
||||
.map(|i| NotNan::new(i as f64).expect("u32 to f64 yielded NaN"))
|
||||
.ok_or_else(|| AssertionError::ext(x.clone(), "is not a decimal digit")),
|
||||
}).map(|nn| Literal::Num(nn).into())
|
||||
}
|
||||
|
||||
define_fn! {
|
||||
/// Parse an unsigned integer. Accepts the same formats Orchid does. If the
|
||||
/// input is a number, floors it.
|
||||
ToUint = |x| with_lit(x, |l| match l {
|
||||
Literal::Str(s) => int_parser()
|
||||
.parse(s.as_str())
|
||||
.map_err(|_| AssertionError::ext(
|
||||
x.clone(),
|
||||
"cannot be parsed into an unsigned int",
|
||||
)),
|
||||
Literal::Num(n) => Ok(n.floor() as u64),
|
||||
Literal::Uint(i) => Ok(*i),
|
||||
Literal::Char(char) => char
|
||||
.to_digit(10)
|
||||
.map(u64::from)
|
||||
.ok_or(AssertionError::ext(x.clone(), "is not a decimal digit")),
|
||||
}).map(|u| Literal::Uint(u).into())
|
||||
}
|
||||
|
||||
define_fn! {
|
||||
/// Convert a literal to a string using Rust's conversions for floats, chars and
|
||||
/// uints respectively
|
||||
ToString = |x| with_lit(x, |l| Ok(match l {
|
||||
Literal::Char(c) => c.to_string(),
|
||||
Literal::Uint(i) => i.to_string(),
|
||||
Literal::Num(n) => n.to_string(),
|
||||
Literal::Str(s) => s.clone(),
|
||||
})).map(|s| Literal::Str(s).into())
|
||||
}
|
||||
|
||||
pub fn conv(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([
|
||||
(i.i("to_float"), ConstTree::xfn(ToFloat)),
|
||||
(i.i("to_uint"), ConstTree::xfn(ToUint)),
|
||||
(i.i("to_string"), ConstTree::xfn(ToString)),
|
||||
])
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
use crate::interner::Interner;
|
||||
use crate::pipeline::ConstTree;
|
||||
|
||||
mod parse_float;
|
||||
mod parse_uint;
|
||||
mod to_string;
|
||||
|
||||
pub fn conv(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([
|
||||
(i.i("parse_float"), ConstTree::xfn(parse_float::ParseFloat1)),
|
||||
(i.i("parse_uint"), ConstTree::xfn(parse_uint::ParseUint1)),
|
||||
(i.i("to_string"), ConstTree::xfn(to_string::ToString1)),
|
||||
])
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use chumsky::Parser;
|
||||
|
||||
use super::super::assertion_error::AssertionError;
|
||||
use super::super::litconv::with_lit;
|
||||
use crate::parse::float_parser;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::representations::Literal;
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// parse a number. Accepts the same syntax Orchid does
|
||||
///
|
||||
/// Next state: [ParseFloat0]
|
||||
#[derive(Clone)]
|
||||
pub struct ParseFloat1;
|
||||
externfn_impl!(ParseFloat1, |_: &Self, x: ExprInst| Ok(ParseFloat0 { x }));
|
||||
|
||||
/// Prev state: [ParseFloat1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ParseFloat0 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(ParseFloat0, x);
|
||||
atomic_impl!(ParseFloat0, |Self { x }: &Self, _| {
|
||||
let number = with_lit(x, |l| {
|
||||
Ok(match l {
|
||||
Literal::Str(s) => {
|
||||
let parser = float_parser();
|
||||
parser.parse(s.as_str()).map_err(|_| {
|
||||
AssertionError::ext(x.clone(), "cannot be parsed into a float")
|
||||
})?
|
||||
},
|
||||
Literal::Num(n) => *n,
|
||||
Literal::Uint(i) => (*i as u32).into(),
|
||||
Literal::Char(char) => char
|
||||
.to_digit(10)
|
||||
.ok_or(AssertionError::ext(x.clone(), "is not a decimal digit"))?
|
||||
.into(),
|
||||
})
|
||||
})?;
|
||||
Ok(number.into())
|
||||
});
|
||||
@@ -1,47 +0,0 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use chumsky::Parser;
|
||||
|
||||
use super::super::assertion_error::AssertionError;
|
||||
use super::super::litconv::with_lit;
|
||||
use crate::parse::int_parser;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::representations::Literal;
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Parse an unsigned integer. Accepts the same formats Orchid does. If the
|
||||
/// input is a number, floors it.
|
||||
///
|
||||
/// Next state: [ParseUint0]
|
||||
#[derive(Clone)]
|
||||
pub struct ParseUint1;
|
||||
externfn_impl!(ParseUint1, |_: &Self, x: ExprInst| Ok(ParseUint0 { x }));
|
||||
|
||||
/// Prev state: [ParseUint1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ParseUint0 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(ParseUint0, x);
|
||||
atomic_impl!(ParseUint0, |Self { x }: &Self, _| {
|
||||
let uint = with_lit(x, |l| {
|
||||
Ok(match l {
|
||||
Literal::Str(s) => {
|
||||
let parser = int_parser();
|
||||
parser.parse(s.as_str()).map_err(|_| {
|
||||
AssertionError::ext(
|
||||
x.clone(),
|
||||
"cannot be parsed into an unsigned int",
|
||||
)
|
||||
})?
|
||||
},
|
||||
Literal::Num(n) => n.floor() as u64,
|
||||
Literal::Uint(i) => *i,
|
||||
Literal::Char(char) => char
|
||||
.to_digit(10)
|
||||
.ok_or(AssertionError::ext(x.clone(), "is not a decimal digit"))?
|
||||
.into(),
|
||||
})
|
||||
})?;
|
||||
Ok(uint.into())
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::super::litconv::with_lit;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::representations::Literal;
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Convert a literal to a string using Rust's conversions for floats, chars and
|
||||
/// uints respectively
|
||||
///
|
||||
/// Next state: [ToString0]
|
||||
#[derive(Clone)]
|
||||
pub struct ToString1;
|
||||
externfn_impl!(ToString1, |_: &Self, x: ExprInst| Ok(ToString0 { x }));
|
||||
|
||||
/// Prev state: [ToString1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ToString0 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(ToString0, x);
|
||||
atomic_impl!(ToString0, |Self { x }: &Self, _| {
|
||||
let string = with_lit(x, |l| {
|
||||
Ok(match l {
|
||||
Literal::Char(c) => c.to_string(),
|
||||
Literal::Uint(i) => i.to_string(),
|
||||
Literal::Num(n) => n.to_string(),
|
||||
Literal::Str(s) => s.clone(),
|
||||
})
|
||||
})?;
|
||||
Ok(string.into())
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::foreign::{Atomic, AtomicReturn};
|
||||
use crate::interner::InternedDisplay;
|
||||
use crate::interpreter::Context;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::{atomic_defaults, externfn_impl};
|
||||
|
||||
/// Print and return whatever expression is in the argument without normalizing
|
||||
/// it.
|
||||
///
|
||||
/// Next state: [Debug1]
|
||||
#[derive(Clone)]
|
||||
pub struct Debug2;
|
||||
externfn_impl!(Debug2, |_: &Self, x: ExprInst| Ok(Debug1 { x }));
|
||||
|
||||
/// Prev state: [Debug2]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Debug1 {
|
||||
x: ExprInst,
|
||||
}
|
||||
impl Atomic for Debug1 {
|
||||
atomic_defaults!();
|
||||
fn run(&self, ctx: Context) -> crate::foreign::AtomicResult {
|
||||
println!("{}", self.x.bundle(ctx.interner));
|
||||
Ok(AtomicReturn {
|
||||
clause: self.x.expr().clause.clone(),
|
||||
gas: ctx.gas.map(|g| g - 1),
|
||||
inert: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
use crate::interner::Interner;
|
||||
use crate::pipeline::ConstTree;
|
||||
|
||||
mod debug;
|
||||
mod io;
|
||||
mod panic;
|
||||
mod print;
|
||||
mod readline;
|
||||
|
||||
pub use io::{handle, IO};
|
||||
|
||||
pub fn cpsio(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([
|
||||
(i.i("print"), ConstTree::xfn(print::Print2)),
|
||||
(i.i("readline"), ConstTree::xfn(readline::Readln2)),
|
||||
(i.i("debug"), ConstTree::xfn(debug::Debug2)),
|
||||
(i.i("panic"), ConstTree::xfn(panic::Panic1)),
|
||||
])
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use super::super::litconv::with_str;
|
||||
use crate::foreign::ExternError;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Takes a message, returns an [ExternError] unconditionally.
|
||||
///
|
||||
/// Next state: [Panic0]
|
||||
#[derive(Clone)]
|
||||
pub struct Panic1;
|
||||
externfn_impl!(Panic1, |_: &Self, x: ExprInst| Ok(Panic0 { x }));
|
||||
|
||||
/// Prev state: [Panic1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Panic0 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Panic0, x);
|
||||
atomic_impl!(Panic0, |Self { x }: &Self, _| {
|
||||
with_str(x, |s| Err(OrchidPanic(s.clone()).into_extern()))
|
||||
});
|
||||
|
||||
/// An unrecoverable error in Orchid land. Of course, because Orchid is lazy, it
|
||||
/// only applies to the expressions that use the one that generated it.
|
||||
pub struct OrchidPanic(String);
|
||||
|
||||
impl Display for OrchidPanic {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Orchid code panicked: {}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternError for OrchidPanic {}
|
||||
@@ -1,40 +0,0 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::super::litconv::with_str;
|
||||
use super::io::IO;
|
||||
use crate::foreign::{Atomic, AtomicResult, AtomicReturn};
|
||||
use crate::interpreter::Context;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::{atomic_defaults, atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Wrap a string and the continuation into an [IO] event to be evaluated by the
|
||||
/// embedder.
|
||||
///
|
||||
/// Next state: [Print1]
|
||||
#[derive(Clone)]
|
||||
pub struct Print2;
|
||||
externfn_impl!(Print2, |_: &Self, x: ExprInst| Ok(Print1 { x }));
|
||||
|
||||
/// Prev state: [Print2]; Next state: [Print0]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Print1 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Print1, x);
|
||||
atomic_impl!(Print1);
|
||||
externfn_impl!(Print1, |this: &Self, x: ExprInst| {
|
||||
with_str(&this.x, |s| Ok(Print0 { s: s.clone(), x }))
|
||||
});
|
||||
|
||||
/// Prev state: [Print1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Print0 {
|
||||
s: String,
|
||||
x: ExprInst,
|
||||
}
|
||||
impl Atomic for Print0 {
|
||||
atomic_defaults!();
|
||||
fn run(&self, ctx: Context) -> AtomicResult {
|
||||
Ok(AtomicReturn::from_data(IO::Print(self.s.clone(), self.x.clone()), ctx))
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::io::IO;
|
||||
use crate::foreign::{Atomic, AtomicResult, AtomicReturn};
|
||||
use crate::interpreter::Context;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::{atomic_defaults, externfn_impl};
|
||||
|
||||
/// Create an [IO] event that reads a line form standard input and calls the
|
||||
/// continuation with it.
|
||||
///
|
||||
/// Next state: [Readln1]
|
||||
#[derive(Clone)]
|
||||
pub struct Readln2;
|
||||
externfn_impl!(Readln2, |_: &Self, x: ExprInst| Ok(Readln1 { x }));
|
||||
|
||||
/// Prev state: [Readln2]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Readln1 {
|
||||
x: ExprInst,
|
||||
}
|
||||
impl Atomic for Readln1 {
|
||||
atomic_defaults!();
|
||||
fn run(&self, ctx: Context) -> AtomicResult {
|
||||
Ok(AtomicReturn::from_data(IO::Readline(self.x.clone()), ctx))
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ export loop $r on (..$parameters) with ...$tail =0x5p129=> Y (\$r.
|
||||
bind_names ($name ..$rest) $payload =0x1p250=> \$name. bind_names (..$rest) $payload
|
||||
bind_names () (...$payload) =0x1p250=> ...$payload
|
||||
|
||||
export ...$prefix $ ...$suffix:1 =0x1p39=> ...$prefix (...$suffix)
|
||||
export ...$prefix $ ...$suffix:1 =0x1p38=> ...$prefix (...$suffix)
|
||||
export ...$prefix |> $fn ..$suffix:1 =0x2p32=> $fn (...$prefix) ..$suffix
|
||||
|
||||
export (...$argv) => ...$body =0x2p129=> (bind_names (...$argv) (...$body))
|
||||
29
src/stl/io/inspect.rs
Normal file
29
src/stl/io/inspect.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::foreign::{Atomic, AtomicReturn};
|
||||
use crate::interner::InternedDisplay;
|
||||
use crate::interpreter::Context;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::{atomic_defaults, write_fn_step};
|
||||
|
||||
write_fn_step! {
|
||||
/// Print and return whatever expression is in the argument without
|
||||
/// normalizing it.
|
||||
pub Inspect > Inspect1
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Inspect1 {
|
||||
expr_inst: ExprInst,
|
||||
}
|
||||
impl Atomic for Inspect1 {
|
||||
atomic_defaults!();
|
||||
fn run(&self, ctx: Context) -> crate::foreign::AtomicResult {
|
||||
println!("{}", self.expr_inst.bundle(ctx.interner));
|
||||
Ok(AtomicReturn {
|
||||
clause: self.expr_inst.expr().clause.clone(),
|
||||
gas: ctx.gas.map(|g| g - 1),
|
||||
inert: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
30
src/stl/io/mod.rs
Normal file
30
src/stl/io/mod.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use crate::interner::Interner;
|
||||
use crate::pipeline::ConstTree;
|
||||
|
||||
mod command;
|
||||
mod inspect;
|
||||
mod panic;
|
||||
mod print;
|
||||
mod readline;
|
||||
|
||||
pub use command::{handle, IO};
|
||||
|
||||
pub fn io(i: &Interner, allow_impure: bool) -> ConstTree {
|
||||
let pure = ConstTree::tree([(
|
||||
i.i("io"),
|
||||
ConstTree::tree([
|
||||
(i.i("print"), ConstTree::xfn(print::Print)),
|
||||
(i.i("readline"), ConstTree::xfn(readline::ReadLn)),
|
||||
(i.i("panic"), ConstTree::xfn(panic::Panic)),
|
||||
]),
|
||||
)]);
|
||||
if !allow_impure {
|
||||
pure
|
||||
} else {
|
||||
pure
|
||||
+ ConstTree::tree([(
|
||||
i.i("io"),
|
||||
ConstTree::tree([(i.i("debug"), ConstTree::xfn(inspect::Inspect))]),
|
||||
)])
|
||||
}
|
||||
}
|
||||
21
src/stl/io/panic.rs
Normal file
21
src/stl/io/panic.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use super::super::litconv::with_str;
|
||||
use crate::define_fn;
|
||||
use crate::foreign::ExternError;
|
||||
|
||||
define_fn! {
|
||||
/// Takes a message, returns an [ExternError] unconditionally.
|
||||
pub Panic = |x| with_str(x, |s| Err(OrchidPanic(s.clone()).into_extern()))
|
||||
}
|
||||
/// An unrecoverable error in Orchid land. Because Orchid is lazy, this only
|
||||
/// invalidates expressions that reference the one that generated it.
|
||||
pub struct OrchidPanic(String);
|
||||
|
||||
impl Display for OrchidPanic {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Orchid code panicked: {}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternError for OrchidPanic {}
|
||||
31
src/stl/io/print.rs
Normal file
31
src/stl/io/print.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::super::litconv::with_str;
|
||||
use super::command::IO;
|
||||
use crate::foreign::{Atomic, AtomicResult, AtomicReturn};
|
||||
use crate::interpreter::Context;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::{atomic_defaults, write_fn_step};
|
||||
|
||||
write_fn_step! {
|
||||
/// Wrap a string and the continuation into an [IO] event to be evaluated by
|
||||
/// the embedder.
|
||||
pub Print > Print1
|
||||
}
|
||||
write_fn_step! {
|
||||
Print1 {}
|
||||
Print0 where message = x => with_str(x, |s| Ok(s.clone()));
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Print0 {
|
||||
message: String,
|
||||
expr_inst: ExprInst,
|
||||
}
|
||||
impl Atomic for Print0 {
|
||||
atomic_defaults!();
|
||||
fn run(&self, ctx: Context) -> AtomicResult {
|
||||
let command = IO::Print(self.message.clone(), self.expr_inst.clone());
|
||||
Ok(AtomicReturn::from_data(command, ctx))
|
||||
}
|
||||
}
|
||||
25
src/stl/io/readline.rs
Normal file
25
src/stl/io/readline.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::command::IO;
|
||||
use crate::foreign::{Atomic, AtomicResult, AtomicReturn};
|
||||
use crate::interpreter::Context;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::{atomic_defaults, write_fn_step};
|
||||
|
||||
write_fn_step! {
|
||||
/// Create an [IO] event that reads a line form standard input and calls the
|
||||
/// continuation with it.
|
||||
pub ReadLn > ReadLn1
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ReadLn1 {
|
||||
expr_inst: ExprInst,
|
||||
}
|
||||
impl Atomic for ReadLn1 {
|
||||
atomic_defaults!();
|
||||
fn run(&self, ctx: Context) -> AtomicResult {
|
||||
let command = IO::Readline(self.expr_inst.clone());
|
||||
Ok(AtomicReturn::from_data(command, ctx))
|
||||
}
|
||||
}
|
||||
1
src/stl/known.orc
Normal file
1
src/stl/known.orc
Normal file
@@ -0,0 +1 @@
|
||||
export ::(,)
|
||||
@@ -1,5 +1,4 @@
|
||||
import option
|
||||
import super::fn::*
|
||||
import super::(option, fn::*, bool::*, known::*, num::*,)
|
||||
|
||||
pair := \a.\b. \f. f a b
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import list
|
||||
import option
|
||||
import fn::*
|
||||
import std::to_string
|
||||
import std::debug
|
||||
import super::(bool::*, fn::*, known::*, list, option, proc::*)
|
||||
import std::io::panic
|
||||
|
||||
-- utilities for using lists as pairs
|
||||
|
||||
@@ -1,13 +1,68 @@
|
||||
use hashbrown::HashMap;
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
use super::bool::bool;
|
||||
use super::conv::conv;
|
||||
use super::cpsio::cpsio;
|
||||
use super::io::io;
|
||||
use super::num::num;
|
||||
use super::str::str;
|
||||
use crate::interner::Interner;
|
||||
use crate::pipeline::ConstTree;
|
||||
use crate::interner::{Interner, Sym};
|
||||
use crate::pipeline::file_loader::mk_embed_cache;
|
||||
use crate::pipeline::{from_const_tree, parse_layer, ProjectTree};
|
||||
use crate::sourcefile::{FileEntry, Import};
|
||||
|
||||
/// Feature flags for the STL.
|
||||
#[derive(Default)]
|
||||
pub struct StlOptions {
|
||||
/// Whether impure functions (such as io::debug) are allowed. An embedder
|
||||
/// would typically disable this flag
|
||||
pub impure: bool,
|
||||
}
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "src/stl"]
|
||||
#[prefix = "std/"]
|
||||
#[include = "*.orc"]
|
||||
struct StlEmbed;
|
||||
|
||||
// TODO: fix all orc modules to not rely on prelude
|
||||
|
||||
/// Build the standard library used by the interpreter by combining the other
|
||||
/// libraries
|
||||
pub fn mk_stl(i: &Interner) -> ConstTree {
|
||||
cpsio(i) + conv(i) + bool(i) + str(i) + num(i)
|
||||
pub fn mk_stl(i: &Interner, options: StlOptions) -> ProjectTree {
|
||||
let const_tree = from_const_tree(
|
||||
HashMap::from([(
|
||||
i.i("std"),
|
||||
io(i, options.impure) + conv(i) + bool(i) + str(i) + num(i),
|
||||
)]),
|
||||
&[i.i("std")],
|
||||
i,
|
||||
);
|
||||
let ld_cache = mk_embed_cache::<StlEmbed>(".orc", i);
|
||||
parse_layer(
|
||||
&StlEmbed::iter()
|
||||
.map(|path| -> Sym {
|
||||
let segtoks = path
|
||||
.strip_suffix(".orc")
|
||||
.expect("the embed is filtered for suffix")
|
||||
.split('/')
|
||||
.map(|segment| i.i(segment))
|
||||
.collect::<Vec<_>>();
|
||||
i.i(&segtoks[..])
|
||||
})
|
||||
.collect::<Vec<_>>()[..],
|
||||
&|p| ld_cache.find(&p),
|
||||
&const_tree,
|
||||
&[],
|
||||
i,
|
||||
)
|
||||
.expect("Parse error in STL")
|
||||
}
|
||||
|
||||
/// Generate prelude lines to be injected to every module compiled with the STL
|
||||
pub fn mk_prelude(i: &Interner) -> Vec<FileEntry> {
|
||||
vec![FileEntry::Import(vec![Import {
|
||||
path: i.i(&[i.i("std"), i.i("prelude")][..]),
|
||||
name: None,
|
||||
}])]
|
||||
}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
//! Constants exposed to usercode by the interpreter
|
||||
mod arithmetic_error;
|
||||
mod assertion_error;
|
||||
mod bool;
|
||||
mod conv;
|
||||
mod cpsio;
|
||||
mod io;
|
||||
pub mod litconv;
|
||||
mod mk_stl;
|
||||
mod num;
|
||||
mod runtime_error;
|
||||
mod str;
|
||||
|
||||
pub use arithmetic_error::ArithmeticError;
|
||||
pub use assertion_error::AssertionError;
|
||||
pub use cpsio::{handle as handleIO, IO};
|
||||
pub use mk_stl::mk_stl;
|
||||
pub use io::{handle as handleIO, IO};
|
||||
pub use mk_stl::{mk_prelude, mk_stl, StlOptions};
|
||||
pub use runtime_error::RuntimeError;
|
||||
|
||||
pub use self::bool::Boolean;
|
||||
|
||||
5
src/stl/num.orc
Normal file
5
src/stl/num.orc
Normal file
@@ -0,0 +1,5 @@
|
||||
export ...$a + ...$b =0x2p36=> (add (...$a) (...$b))
|
||||
export ...$a - ...$b:1 =0x2p36=> (subtract (...$a) (...$b))
|
||||
export ...$a * ...$b =0x1p36=> (multiply (...$a) (...$b))
|
||||
export ...$a % ...$b:1 =0x1p36=> (remainder (...$a) (...$b))
|
||||
export ...$a / ...$b:1 =0x1p36=> (divide (...$a) (...$b))
|
||||
149
src/stl/num.rs
Normal file
149
src/stl/num.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use ordered_float::NotNan;
|
||||
|
||||
use super::litconv::with_lit;
|
||||
use super::{ArithmeticError, AssertionError};
|
||||
use crate::define_fn;
|
||||
use crate::foreign::ExternError;
|
||||
use crate::interner::Interner;
|
||||
use crate::pipeline::ConstTree;
|
||||
use crate::representations::interpreted::{Clause, ExprInst};
|
||||
use crate::representations::{Literal, Primitive};
|
||||
|
||||
// region: Numeric, type to handle floats and uints together
|
||||
|
||||
/// A number, either floating point or unsigned int, visible to Orchid.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Numeric {
|
||||
/// A nonnegative integer such as a size, index or count
|
||||
Uint(u64),
|
||||
/// A float other than NaN. Orchid has no silent errors
|
||||
Num(NotNan<f64>),
|
||||
}
|
||||
|
||||
impl Numeric {
|
||||
fn as_f64(&self) -> f64 {
|
||||
match self {
|
||||
Numeric::Num(n) => **n,
|
||||
Numeric::Uint(i) => *i as f64,
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap a f64 in a Numeric
|
||||
fn num(value: f64) -> Result<Self, Rc<dyn ExternError>> {
|
||||
if value.is_finite() {
|
||||
NotNan::new(value)
|
||||
.map(Self::Num)
|
||||
.map_err(|_| ArithmeticError::NaN.into_extern())
|
||||
} else {
|
||||
Err(ArithmeticError::Infinity.into_extern())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ExprInst> for Numeric {
|
||||
type Error = Rc<dyn ExternError>;
|
||||
fn try_from(value: &ExprInst) -> Result<Self, Self::Error> {
|
||||
with_lit(value, |l| match l {
|
||||
Literal::Uint(i) => Ok(Numeric::Uint(*i)),
|
||||
Literal::Num(n) => Ok(Numeric::Num(*n)),
|
||||
_ => AssertionError::fail(value.clone(), "an integer or number")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Numeric> for Clause {
|
||||
fn from(value: Numeric) -> Self {
|
||||
Clause::P(Primitive::Literal(match value {
|
||||
Numeric::Uint(i) => Literal::Uint(i),
|
||||
Numeric::Num(n) => Literal::Num(n),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region: operations
|
||||
|
||||
define_fn! {
|
||||
/// Add two numbers. If they're both uint, the output is uint. If either is
|
||||
/// number, the output is number.
|
||||
Add { a: Numeric, b: Numeric } => match (a, b) {
|
||||
(Numeric::Uint(a), Numeric::Uint(b)) => {
|
||||
a.checked_add(*b)
|
||||
.map(Numeric::Uint)
|
||||
.ok_or_else(|| ArithmeticError::Overflow.into_extern())
|
||||
}
|
||||
(Numeric::Num(a), Numeric::Num(b)) => Numeric::num(*(a + b)),
|
||||
(Numeric::Num(a), Numeric::Uint(b)) | (Numeric::Uint(b), Numeric::Num(a))
|
||||
=> Numeric::num(a.into_inner() + *b as f64),
|
||||
}.map(Numeric::into)
|
||||
}
|
||||
|
||||
define_fn! {
|
||||
/// Subtract a number from another. Always returns Number.
|
||||
Subtract { a: Numeric, b: Numeric } => match (a, b) {
|
||||
(Numeric::Uint(a), Numeric::Uint(b)) => Numeric::num(*a as f64 - *b as f64),
|
||||
(Numeric::Num(a), Numeric::Num(b)) => Numeric::num(*(a - b)),
|
||||
(Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(**a - *b as f64),
|
||||
(Numeric::Uint(a), Numeric::Num(b)) => Numeric::num(*a as f64 - **b),
|
||||
}.map(Numeric::into)
|
||||
}
|
||||
|
||||
define_fn! {
|
||||
/// Multiply two numbers. If they're both uint, the output is uint. If either
|
||||
/// is number, the output is number.
|
||||
Multiply { a: Numeric, b: Numeric } => match (a, b) {
|
||||
(Numeric::Uint(a), Numeric::Uint(b)) => {
|
||||
a.checked_mul(*b)
|
||||
.map(Numeric::Uint)
|
||||
.ok_or_else(|| ArithmeticError::Overflow.into_extern())
|
||||
}
|
||||
(Numeric::Num(a), Numeric::Num(b)) => Numeric::num(*(a * b)),
|
||||
(Numeric::Uint(a), Numeric::Num(b)) | (Numeric::Num(b), Numeric::Uint(a))
|
||||
=> Numeric::num(*a as f64 * **b),
|
||||
}.map(Numeric::into)
|
||||
}
|
||||
|
||||
define_fn! {
|
||||
/// Divide a number by another. Always returns Number.
|
||||
Divide { a: Numeric, b: Numeric } => {
|
||||
let a: f64 = a.as_f64();
|
||||
let b: f64 = b.as_f64();
|
||||
if b == 0.0 {
|
||||
return Err(ArithmeticError::DivByZero.into_extern())
|
||||
}
|
||||
Numeric::num(a / b).map(Numeric::into)
|
||||
}
|
||||
}
|
||||
|
||||
define_fn! {
|
||||
/// Take the remainder of two numbers. If they're both uint, the output is
|
||||
/// uint. If either is number, the output is number.
|
||||
Remainder { a: Numeric, b: Numeric } => match (a, b) {
|
||||
(Numeric::Uint(a), Numeric::Uint(b)) => {
|
||||
a.checked_rem(*b)
|
||||
.map(Numeric::Uint)
|
||||
.ok_or_else(|| ArithmeticError::DivByZero.into_extern())
|
||||
}
|
||||
(Numeric::Num(a), Numeric::Num(b)) => Numeric::num(*(a % b)),
|
||||
(Numeric::Uint(a), Numeric::Num(b)) => Numeric::num(*a as f64 % **b),
|
||||
(Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(**a % *b as f64),
|
||||
}.map(Numeric::into)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
pub fn num(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([(
|
||||
i.i("num"),
|
||||
ConstTree::tree([
|
||||
(i.i("add"), ConstTree::xfn(Add)),
|
||||
(i.i("subtract"), ConstTree::xfn(Subtract)),
|
||||
(i.i("multiply"), ConstTree::xfn(Multiply)),
|
||||
(i.i("divide"), ConstTree::xfn(Divide)),
|
||||
(i.i("remainder"), ConstTree::xfn(Remainder)),
|
||||
]),
|
||||
)])
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
mod numeric;
|
||||
pub mod operators;
|
||||
pub use numeric::Numeric;
|
||||
|
||||
use crate::interner::Interner;
|
||||
use crate::pipeline::ConstTree;
|
||||
|
||||
pub fn num(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([
|
||||
(i.i("add"), ConstTree::xfn(operators::add::Add2)),
|
||||
(i.i("subtract"), ConstTree::xfn(operators::subtract::Subtract2)),
|
||||
(i.i("multiply"), ConstTree::xfn(operators::multiply::Multiply2)),
|
||||
(i.i("divide"), ConstTree::xfn(operators::divide::Divide2)),
|
||||
(i.i("remainder"), ConstTree::xfn(operators::remainder::Remainder2)),
|
||||
])
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
use std::ops::{Add, Div, Mul, Rem, Sub};
|
||||
use std::rc::Rc;
|
||||
|
||||
use ordered_float::NotNan;
|
||||
|
||||
use super::super::assertion_error::AssertionError;
|
||||
use super::super::litconv::with_lit;
|
||||
use crate::foreign::ExternError;
|
||||
use crate::representations::interpreted::{Clause, ExprInst};
|
||||
use crate::representations::{Literal, Primitive};
|
||||
|
||||
/// A number, either floating point or unsigned int, visible to Orchid.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Numeric {
|
||||
/// A nonnegative integer such as a size, index or count
|
||||
Uint(u64),
|
||||
/// A float other than NaN. Orchid has no silent errors
|
||||
Num(NotNan<f64>),
|
||||
}
|
||||
|
||||
impl Numeric {
|
||||
/// Wrap a f64 in a Numeric
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// if the value is NaN or Infinity.try_into()
|
||||
fn num<T: Into<f64>>(value: T) -> Self {
|
||||
let f = value.into();
|
||||
assert!(f.is_finite(), "unrepresentable number");
|
||||
NotNan::try_from(f).map(Self::Num).expect("not a number")
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Numeric {
|
||||
type Output = Numeric;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
match (self, rhs) {
|
||||
(Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a + b),
|
||||
(Numeric::Num(a), Numeric::Num(b)) => Numeric::num(a + b),
|
||||
(Numeric::Uint(a), Numeric::Num(b))
|
||||
| (Numeric::Num(b), Numeric::Uint(a)) =>
|
||||
Numeric::num::<f64>(a as f64 + *b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Numeric {
|
||||
type Output = Numeric;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
match (self, rhs) {
|
||||
(Numeric::Uint(a), Numeric::Uint(b)) if b <= a => Numeric::Uint(a - b),
|
||||
(Numeric::Uint(a), Numeric::Uint(b)) => Numeric::num(a as f64 - b as f64),
|
||||
(Numeric::Num(a), Numeric::Num(b)) => Numeric::num(a - b),
|
||||
(Numeric::Uint(a), Numeric::Num(b)) => Numeric::num(a as f64 - *b),
|
||||
(Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(*a - b as f64),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul for Numeric {
|
||||
type Output = Numeric;
|
||||
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
match (self, rhs) {
|
||||
(Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a * b),
|
||||
(Numeric::Num(a), Numeric::Num(b)) => Numeric::num(a * b),
|
||||
(Numeric::Uint(a), Numeric::Num(b))
|
||||
| (Numeric::Num(b), Numeric::Uint(a)) =>
|
||||
Numeric::Num(NotNan::new(a as f64).unwrap() * b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Div for Numeric {
|
||||
type Output = Numeric;
|
||||
|
||||
fn div(self, rhs: Self) -> Self::Output {
|
||||
let a: f64 = self.into();
|
||||
let b: f64 = rhs.into();
|
||||
Numeric::num(a / b)
|
||||
}
|
||||
}
|
||||
|
||||
impl Rem for Numeric {
|
||||
type Output = Numeric;
|
||||
|
||||
fn rem(self, rhs: Self) -> Self::Output {
|
||||
match (self, rhs) {
|
||||
(Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a % b),
|
||||
(Numeric::Num(a), Numeric::Num(b)) => Numeric::num(a % b),
|
||||
(Numeric::Uint(a), Numeric::Num(b)) => Numeric::num(a as f64 % *b),
|
||||
(Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(*a % b as f64),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ExprInst> for Numeric {
|
||||
type Error = Rc<dyn ExternError>;
|
||||
fn try_from(value: ExprInst) -> Result<Self, Self::Error> {
|
||||
with_lit(&value.clone(), |l| match l {
|
||||
Literal::Uint(i) => Ok(Numeric::Uint(*i)),
|
||||
Literal::Num(n) => Ok(Numeric::Num(*n)),
|
||||
_ => AssertionError::fail(value, "an integer or number")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Numeric> for Clause {
|
||||
fn from(value: Numeric) -> Self {
|
||||
Clause::P(Primitive::Literal(match value {
|
||||
Numeric::Uint(i) => Literal::Uint(i),
|
||||
Numeric::Num(n) => Literal::Num(n),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Numeric> for String {
|
||||
fn from(value: Numeric) -> Self {
|
||||
match value {
|
||||
Numeric::Uint(i) => i.to_string(),
|
||||
Numeric::Num(n) => n.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Numeric> for f64 {
|
||||
fn from(val: Numeric) -> Self {
|
||||
match val {
|
||||
Numeric::Num(n) => *n,
|
||||
Numeric::Uint(i) => i as f64,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::super::Numeric;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Add2;
|
||||
externfn_impl!(Add2, |_: &Self, x: ExprInst| Ok(Add1 { x }));
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Add1 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Add1, x);
|
||||
atomic_impl!(Add1);
|
||||
externfn_impl!(Add1, |this: &Self, x: ExprInst| {
|
||||
let a: Numeric = this.x.clone().try_into()?;
|
||||
Ok(Add0 { a, x })
|
||||
});
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Add0 {
|
||||
a: Numeric,
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Add0, x);
|
||||
atomic_impl!(Add0, |Self { a, x }: &Self, _| {
|
||||
let b: Numeric = x.clone().try_into()?;
|
||||
Ok((*a + b).into())
|
||||
});
|
||||
@@ -1,37 +0,0 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::super::Numeric;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Divides two numbers
|
||||
///
|
||||
/// Next state: [Divide1]
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Divide2;
|
||||
externfn_impl!(Divide2, |_: &Self, x: ExprInst| Ok(Divide1 { x }));
|
||||
|
||||
/// Prev state: [Divide2]; Next state: [Divide0]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Divide1 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Divide1, x);
|
||||
atomic_impl!(Divide1);
|
||||
externfn_impl!(Divide1, |this: &Self, x: ExprInst| {
|
||||
let a: Numeric = this.x.clone().try_into()?;
|
||||
Ok(Divide0 { a, x })
|
||||
});
|
||||
|
||||
/// Prev state: [Divide1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Divide0 {
|
||||
a: Numeric,
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Divide0, x);
|
||||
atomic_impl!(Divide0, |Self { a, x }: &Self, _| {
|
||||
let b: Numeric = x.clone().try_into()?;
|
||||
Ok((*a / b).into())
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
pub mod add;
|
||||
pub mod divide;
|
||||
pub mod multiply;
|
||||
pub mod remainder;
|
||||
pub mod subtract;
|
||||
@@ -1,36 +0,0 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::super::Numeric;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Multiplies two numbers
|
||||
///
|
||||
/// Next state: [Multiply1]
|
||||
#[derive(Clone)]
|
||||
pub struct Multiply2;
|
||||
externfn_impl!(Multiply2, |_: &Self, x: ExprInst| Ok(Multiply1 { x }));
|
||||
|
||||
/// Prev state: [Multiply2]; Next state: [Multiply0]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Multiply1 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Multiply1, x);
|
||||
atomic_impl!(Multiply1);
|
||||
externfn_impl!(Multiply1, |this: &Self, x: ExprInst| {
|
||||
let a: Numeric = this.x.clone().try_into()?;
|
||||
Ok(Multiply0 { a, x })
|
||||
});
|
||||
|
||||
/// Prev state: [Multiply1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Multiply0 {
|
||||
a: Numeric,
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Multiply0, x);
|
||||
atomic_impl!(Multiply0, |Self { a, x }: &Self, _| {
|
||||
let b: Numeric = x.clone().try_into()?;
|
||||
Ok((*a * b).into())
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::super::Numeric;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Takes the modulus of two numbers.
|
||||
///
|
||||
/// Next state: [Remainder1]
|
||||
#[derive(Clone)]
|
||||
pub struct Remainder2;
|
||||
externfn_impl!(Remainder2, |_: &Self, x: ExprInst| Ok(Remainder1 { x }));
|
||||
|
||||
/// Prev state: [Remainder2]; Next state: [Remainder0]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Remainder1 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Remainder1, x);
|
||||
atomic_impl!(Remainder1);
|
||||
externfn_impl!(Remainder1, |this: &Self, x: ExprInst| {
|
||||
let a: Numeric = this.x.clone().try_into()?;
|
||||
Ok(Remainder0 { a, x })
|
||||
});
|
||||
|
||||
/// Prev state: [Remainder1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Remainder0 {
|
||||
a: Numeric,
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Remainder0, x);
|
||||
atomic_impl!(Remainder0, |Self { a, x }: &Self, _| {
|
||||
let b: Numeric = x.clone().try_into()?;
|
||||
Ok((*a % b).into())
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::super::Numeric;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Subtracts two numbers
|
||||
///
|
||||
/// Next state: [Subtract1]
|
||||
#[derive(Clone)]
|
||||
pub struct Subtract2;
|
||||
externfn_impl!(Subtract2, |_: &Self, x: ExprInst| Ok(Subtract1 { x }));
|
||||
|
||||
/// Prev state: [Subtract2]; Next state: [Subtract0]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Subtract1 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Subtract1, x);
|
||||
atomic_impl!(Subtract1);
|
||||
externfn_impl!(Subtract1, |this: &Self, x: ExprInst| {
|
||||
let a: Numeric = this.x.clone().try_into()?;
|
||||
Ok(Subtract0 { a, x })
|
||||
});
|
||||
|
||||
/// Prev state: [Subtract1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Subtract0 {
|
||||
a: Numeric,
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Subtract0, x);
|
||||
atomic_impl!(Subtract0, |Self { a, x }: &Self, _| {
|
||||
let b: Numeric = x.clone().try_into()?;
|
||||
Ok((*a - b).into())
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import std::panic
|
||||
import std::io::panic
|
||||
|
||||
export some := \v. \d.\f. f v
|
||||
export none := \d.\f. d
|
||||
15
src/stl/prelude.orc
Normal file
15
src/stl/prelude.orc
Normal file
@@ -0,0 +1,15 @@
|
||||
import std::num::*
|
||||
export ::(+, -, *, /, %)
|
||||
import std::str::*
|
||||
export ::(++)
|
||||
import std::bool::*
|
||||
export ::(==, if, then, else)
|
||||
import std::fn::*
|
||||
export ::(loop, on, with, $, |>, =>)
|
||||
import std::list
|
||||
import std::map
|
||||
import std::option
|
||||
export ::(list, map, option)
|
||||
|
||||
import std::known::*
|
||||
export ::(,)
|
||||
12
src/stl/proc.orc
Normal file
12
src/stl/proc.orc
Normal file
@@ -0,0 +1,12 @@
|
||||
export do { ...$statement ; ...$rest:1 } =0x2p130=> statement (...$statement) do { ...$rest }
|
||||
export do { ...$return } =0x1p130=> ...$return
|
||||
|
||||
export statement (let $name = ...$value) ...$next =0x1p230=> (
|
||||
(\$name. ...$next) (...$value)
|
||||
)
|
||||
export statement (cps $name = ...$operation) ...$next =0x2p230=> (
|
||||
(...$operation) \$name. ...$next
|
||||
)
|
||||
export statement (cps ...$operation) ...$next =0x1p230=> (
|
||||
(...$operation) (...$next)
|
||||
)
|
||||
1
src/stl/str.orc
Normal file
1
src/stl/str.orc
Normal file
@@ -0,0 +1 @@
|
||||
export ...$a ++ ...$b =0x4p36=> (concatenate (...$a) (...$b))
|
||||
39
src/stl/str.rs
Normal file
39
src/stl/str.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use super::litconv::{with_str, with_uint};
|
||||
use super::RuntimeError;
|
||||
use crate::interner::Interner;
|
||||
use crate::pipeline::ConstTree;
|
||||
use crate::{define_fn, Literal};
|
||||
|
||||
define_fn! {expr=x in
|
||||
/// Append a string to another
|
||||
pub Concatenate {
|
||||
a: String as with_str(x, |s| Ok(s.clone())),
|
||||
b: String as with_str(x, |s| Ok(s.clone()))
|
||||
} => Ok(Literal::Str(a.to_owned() + b).into())
|
||||
}
|
||||
|
||||
define_fn! {expr=x in
|
||||
pub CharAt {
|
||||
s: String as with_str(x, |s| Ok(s.clone())),
|
||||
i: u64 as with_uint(x, Ok)
|
||||
} => {
|
||||
if let Some(c) = s.chars().nth(*i as usize) {
|
||||
Ok(Literal::Char(c).into())
|
||||
} else {
|
||||
RuntimeError::fail(
|
||||
"Character index out of bounds".to_string(),
|
||||
"indexing string",
|
||||
)?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn str(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([(
|
||||
i.i("str"),
|
||||
ConstTree::tree([
|
||||
(i.i("concatenate"), ConstTree::xfn(Concatenate)),
|
||||
(i.i("char_at"), ConstTree::xfn(CharAt)),
|
||||
]),
|
||||
)])
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
use super::super::litconv::{with_str, with_uint};
|
||||
use super::super::runtime_error::RuntimeError;
|
||||
use crate::interpreted::Clause;
|
||||
use crate::{write_fn_step, Literal, Primitive};
|
||||
|
||||
write_fn_step!(pub CharAt2 > CharAt1);
|
||||
write_fn_step!(
|
||||
CharAt1 {}
|
||||
CharAt0 where s = x => with_str(x, |s| Ok(s.clone())) ;
|
||||
);
|
||||
write_fn_step!(
|
||||
CharAt0 { s: String }
|
||||
i = x => with_uint(x, Ok);
|
||||
{
|
||||
if let Some(c) = s.chars().nth(i as usize) {
|
||||
Ok(Clause::P(Primitive::Literal(Literal::Char(c))))
|
||||
} else {
|
||||
RuntimeError::fail(
|
||||
"Character index out of bounds".to_string(),
|
||||
"indexing string",
|
||||
)?
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -1,14 +0,0 @@
|
||||
use super::super::litconv::with_str;
|
||||
use crate::define_fn;
|
||||
use crate::representations::interpreted::Clause;
|
||||
use crate::representations::{Literal, Primitive};
|
||||
|
||||
define_fn! {expr=x in
|
||||
/// Append a string to another
|
||||
pub Concatenate {
|
||||
a: String as with_str(x, |s| Ok(s.clone())),
|
||||
b: String as with_str(x, |s| Ok(s.clone()))
|
||||
} => {
|
||||
Ok(Clause::P(Primitive::Literal(Literal::Str(a.to_owned() + &b))))
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
mod char_at;
|
||||
mod concatenate;
|
||||
|
||||
use crate::interner::Interner;
|
||||
use crate::pipeline::ConstTree;
|
||||
|
||||
pub fn str(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([(
|
||||
i.i("concatenate"),
|
||||
ConstTree::xfn(concatenate::Concatenate),
|
||||
)])
|
||||
}
|
||||
@@ -3,6 +3,7 @@ mod print_nname;
|
||||
mod pushed;
|
||||
mod replace_first;
|
||||
mod side;
|
||||
mod split_max_prefix;
|
||||
mod string_from_charset;
|
||||
mod substack;
|
||||
mod unwrap_or;
|
||||
@@ -12,6 +13,7 @@ pub use print_nname::sym2string;
|
||||
pub use pushed::pushed;
|
||||
pub use replace_first::replace_first;
|
||||
pub use side::Side;
|
||||
pub use split_max_prefix::split_max_prefix;
|
||||
pub use substack::{Stackframe, Substack, SubstackIterator};
|
||||
pub(crate) use unwrap_or::unwrap_or;
|
||||
pub mod iter;
|
||||
|
||||
14
src/utils/split_max_prefix.rs
Normal file
14
src/utils/split_max_prefix.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
/// Split off the longest prefix accepted by the validator
|
||||
#[allow(clippy::type_complexity)] // FIXME couldn't find a good factoring
|
||||
pub fn split_max_prefix<'a, T>(
|
||||
path: &'a [T],
|
||||
is_valid: &impl Fn(&[T]) -> bool,
|
||||
) -> Option<(&'a [T], &'a [T])> {
|
||||
for split in (0..=path.len()).rev() {
|
||||
let (filename, subpath) = path.split_at(split);
|
||||
if is_valid(filename) {
|
||||
return Some((filename, subpath));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -41,17 +41,13 @@ impl<'a, T> Substack<'a, T> {
|
||||
}
|
||||
/// Create a new frame on top of this substack
|
||||
pub fn new_frame(&'a self, item: T) -> Stackframe<'a, T> {
|
||||
Stackframe { item, prev: self, len: self.opt().map_or(1, |s| s.len) }
|
||||
Stackframe { item, prev: self, len: self.opt().map_or(1, |s| s.len + 1) }
|
||||
}
|
||||
/// obtain the previous stackframe if one exists
|
||||
/// TODO: this should return a [Substack]
|
||||
pub fn pop(&'a self, count: usize) -> Option<&'a Stackframe<'a, T>> {
|
||||
if let Self::Frame(p) = self {
|
||||
if count == 0 {
|
||||
Some(p)
|
||||
} else {
|
||||
p.prev.pop(count - 1)
|
||||
}
|
||||
if count == 0 { Some(p) } else { p.prev.pop(count - 1) }
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
/// A macro version of [Option::unwrap_or_else] which supports flow
|
||||
/// control statements such as `return` and `break` in the "else" branch.
|
||||
/// It also supports unwrapping concrete variants of other enums
|
||||
macro_rules! unwrap_or {
|
||||
($m:expr; $fail:expr) => {{
|
||||
if let Some(res) = ($m) { res } else { $fail }
|
||||
}};
|
||||
($m:expr => $pattern:path; $fail:expr) => {{
|
||||
if let $pattern(res) = ($m) { res } else { $fail }
|
||||
}};
|
||||
}
|
||||
|
||||
pub(crate) use unwrap_or;
|
||||
|
||||
Reference in New Issue
Block a user