bug fixes and performance improvements

This commit is contained in:
2023-05-07 22:35:38 +01:00
parent f3ce910f66
commit a604e40bad
167 changed files with 5965 additions and 4229 deletions

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
*.jpg filter=lfs diff=lfs merge=lfs -text
*.pdf filter=lfs diff=lfs merge=lfs -text

1
.gitignore vendored
View File

@@ -8,3 +8,4 @@
*.out
*.pdf
*.gz
!notes/papers/report/template/Figures/**

440
Cargo.lock generated
View File

@@ -24,12 +24,73 @@ dependencies = [
"version_check",
]
[[package]]
name = "anstream"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is-terminal",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d"
[[package]]
name = "anstyle-parse"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "base64"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cc"
version = "1.0.79"
@@ -42,6 +103,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "chumsky"
version = "0.9.2"
@@ -52,6 +119,54 @@ dependencies = [
"stacker",
]
[[package]]
name = "clap"
version = "4.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938"
dependencies = [
"clap_builder",
"clap_derive",
"once_cell",
]
[[package]]
name = "clap_builder"
version = "4.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd"
dependencies = [
"anstream",
"anstyle",
"bitflags",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.13",
]
[[package]]
name = "clap_lex"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "dashmap"
version = "4.0.2"
@@ -70,15 +185,36 @@ checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30"
[[package]]
name = "either"
version = "1.6.1"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "errno"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "getrandom"
version = "0.2.6"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
@@ -112,6 +248,12 @@ dependencies = [
"ahash 0.8.3",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.2.6"
@@ -122,10 +264,48 @@ dependencies = [
]
[[package]]
name = "itertools"
version = "0.10.3"
name = "hermit-abi"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "io-lifetimes"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
dependencies = [
"hermit-abi 0.3.1",
"libc",
"windows-sys",
]
[[package]]
name = "is-terminal"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
dependencies = [
"hermit-abi 0.3.1",
"io-lifetimes",
"rustix",
"windows-sys",
]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
@@ -142,15 +322,37 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.126"
version = "0.2.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
[[package]]
name = "linux-raw-sys"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f"
[[package]]
name = "lock_api"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "mappable-rc"
version = "0.1.0"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b65e7f462b4fbfe1a3c857747c9d027dd55faffaeffbca68f70d0becfe7e93c5"
checksum = "204651f31b0a6a7b2128d2b92c372cd94607b210c3a6b6e542c57a8cfd4db996"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "num-traits"
@@ -167,7 +369,7 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi",
"hermit-abi 0.2.6",
"libc",
]
@@ -179,9 +381,11 @@ checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "orchid"
version = "0.1.0"
version = "0.2.0"
dependencies = [
"base64",
"chumsky",
"clap",
"dyn-clone",
"hashbrown 0.13.2",
"itertools",
@@ -189,23 +393,49 @@ dependencies = [
"mappable-rc",
"ordered-float",
"smallvec",
"static_init",
"thiserror",
]
[[package]]
name = "ordered-float"
version = "3.0.0"
version = "3.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96bcbab4bfea7a59c2c0fe47211a1ac4e3e96bea6eb446d704f310bc5c732ae2"
checksum = "13a384337e997e6860ffbaa83708b2ef329fd8c54cb67a5f64d421e0f943254f"
dependencies = [
"num-traits",
]
[[package]]
name = "proc-macro2"
version = "1.0.39"
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall",
"smallvec",
"winapi",
]
[[package]]
name = "proc-macro2"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
dependencies = [
"unicode-ident",
]
@@ -221,13 +451,42 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.18"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "rustix"
version = "0.37.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "smallvec"
version = "1.10.0"
@@ -248,10 +507,55 @@ dependencies = [
]
[[package]]
name = "syn"
version = "1.0.95"
name = "static_init"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942"
checksum = "8a2a1c578e98c1c16fc3b8ec1328f7659a500737d7a0c6d625e73e830ff9c1f6"
dependencies = [
"bitflags",
"cfg_aliases",
"libc",
"parking_lot",
"parking_lot_core",
"static_init_macro",
"winapi",
]
[[package]]
name = "static_init_macro"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70a2595fc3aa78f2d0e45dd425b22282dd863273761cc77780914b2cf3003acf"
dependencies = [
"cfg_aliases",
"memchr",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec"
dependencies = [
"proc-macro2",
"quote",
@@ -260,29 +564,35 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.31"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.31"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.13",
]
[[package]]
name = "unicode-ident"
version = "1.0.0"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "version_check"
@@ -292,9 +602,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
@@ -317,3 +627,69 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"

View File

@@ -1,7 +1,10 @@
[package]
name = "orchid"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
authors = [
"Lawrence Bethlenfalvy <lbfalvy@protonmail.com>"
]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -15,3 +18,6 @@ itertools = "0.10"
smallvec = { version = "1.10.0", features = ['const_generics'] }
dyn-clone = "1.0.11"
lasso = { version = "0.6.0", features = ['multi-threaded'] }
base64 = "0.21.0"
static_init = "1.0.3"
clap = { version = "4.2.7", features = ["derive"] }

View File

@@ -1,7 +1,7 @@
import prelude::*
import std::conv::(parse_float, to_string)
import std::cpsio::(readline, print)
import std::str::(concatenate)
import std::(parse_float, to_string)
import std::(readline, print)
import std::(concatenate)
export main := do{
cps data = readline;
@@ -20,3 +20,5 @@ export main := do{
cps print (to_string result ++ "\n");
0
}
-- export main := 1 do { 1 ; 2 } 3

View File

@@ -5,7 +5,7 @@
}
],
"settings": {
"[markdown]": {
"[markdown][latex]": {
"editor.unicodeHighlight.ambiguousCharacters": false,
"editor.unicodeHighlight.invisibleCharacters": false,
"diffEditor.ignoreTrimWhitespace": false,
@@ -23,13 +23,43 @@
},
"[rust]": {
"editor.rulers": [74]
}
},
"rust-analyzer.showUnlinkedFileNotification": false,
"files.associations": {
"*.mjsd": "markdown"
},
},
"extensions": {
"recommendations": [
"tomoki1207.pdf",
"james-yu.latex-workshop",
"bungcip.better-toml"
"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"
]
},
"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": []
}
]
}
}

19
src/cli.rs Normal file
View File

@@ -0,0 +1,19 @@
use std::{fmt::Display, io::{stdin, BufRead, stdout, Write}};
pub fn prompt<T: Display, E: Display>(
prompt: &str,
default: T,
mut try_cast: impl FnMut(String) -> Result<T, E>
) -> T {
loop {
print!("{prompt} ({default}): ");
stdout().lock().flush().unwrap();
let mut input = String::with_capacity(100);
stdin().lock().read_line(&mut input).unwrap();
if input.len() == 0 {return default}
match try_cast(input) {
Ok(t) => return t,
Err(e) => println!("Error: {e}")
}
}
}

View File

@@ -1,89 +0,0 @@
use itertools::Itertools;
use mappable_rc::Mrc;
use crate::utils::{collect_to_mrc, to_mrc_slice};
use crate::representations::typed::{Clause, Expr};
#[derive(Clone)]
struct Application<'a> {
id: u64,
value: &'a Expr,
types: bool
}
// pub fn apply_lambda(app: Application, body: Expr) -> Expr {
// apply_lambda_expr_rec(id, value, body)
// .unwrap_or(body)
// }
fn apply_lambda_expr_rec(
app@Application{ id, types, value }: Application, expr: &Expr
) -> Option<Expr> {
let Expr(clause, typ) = expr;
match clause {
Clause::LambdaArg(arg_id) | Clause::AutoArg(arg_id) if *arg_id == id => {
let full_typ =
value.1.iter()
.chain(typ.iter())
.cloned().collect_vec();
Some(Expr(value.0.to_owned(), full_typ))
}
cl => {
let new_cl = apply_lambda_clause_rec(app, cl);
let new_typ = if !types {None} else {
typ.
}
}
}
}
fn apply_lambda_clause_rec(
app: Application, clause: &Clause
) -> Option<Clause> {
match clause {
// Only element actually manipulated
Clause::LambdaArg(_) | Clause::AutoArg(_) => None,
// Traverse, yield Some if either had changed.
Clause::Apply(f, x) => {
let new_f = apply_lambda_expr_rec(app, f.as_ref());
let new_x = apply_lambda_expr_rec(app, x.as_ref());
match (new_f, new_x) { // Mind the shadows
(None, None) => None,
(None, Some(x)) => Some(Clause::Apply(f.clone(), Box::new(x))),
(Some(f), None) => Some(Clause::Apply(Box::new(f), x.clone())),
(Some(f), Some(x)) => Some(Clause::Apply(Box::new(f), Box::new(x)))
}
},
Clause::Lambda(own_id, t, b) => apply_lambda__traverse_param(id, value, own_id, t, b, Clause::Lambda),
Clause::Auto(own_id, t, b) => apply_lambda__traverse_param(id, value, own_id, t, b, Clause::Auto),
// Leaf nodes
Clause::Atom(_) | Clause::ExternFn(_) | Clause::Literal(_) => None
}
}
fn apply_lambda__traverse_param(
id: u64, value: Mrc<Expr>,
own_id: u64, typ: Mrc<[Clause]>, b: Mrc<Expr>,
wrap: impl Fn(u64, Mrc<[Clause]>, Mrc<Expr>) -> Clause
) -> Option<Clause> {
let any_t = false;
let mut t_acc = vec![];
for t in typ.iter() {
let newt = apply_lambda_clause_rec(id, Mrc::clone(&value), t.clone());
any_t |= newt.is_some();
t_acc.push(newt.unwrap_or_else(|| t.clone()))
}
// Respect shadowing
let new_b = if own_id == id {None} else {
apply_lambda_expr_rec(id, value, Mrc::clone(&b))
};
if any_t { // mind the shadows
let typ = to_mrc_slice(t_acc);
if let Some(b) = new_b {
Some(wrap(own_id, typ, b))
} else {Some(wrap(own_id, typ, b))}
} else if let Some(b) = new_b {
Some(wrap(own_id, typ, b))
} else {Some(wrap(own_id, typ, b))}
}

View File

@@ -1,6 +0,0 @@
mod normalize;
mod partial_hash;
mod reduction_tree;
mod apply_lambda;
pub use apply_lambda::apply_lambda;
mod syntax_eq;

View File

@@ -1,30 +0,0 @@
use mappable_rc::Mrc;
use crate::utils::collect_to_mrc;
use super::super::representations::typed::{Clause, Expr};
fn normalize(Expr(clause, typ): Expr) -> Expr {
todo!()
}
fn collect_autos(
Expr(clause, typ): Expr,
arg_types: Vec<Mrc<[Clause]>>,
indirect_argt_trees: Vec<Mrc<[Clause]>>,
sunk_types: &mut dyn Iterator<Item = Clause>
) -> (Vec<Mrc<[Clause]>>, Expr) {
if let Clause::Auto(argt, body) = clause {
}
else {(
arg_types,
Expr(
clause,
collect_to_mrc(
typ.iter().cloned()
.chain(sunk_types)
)
)
)}
}

View File

@@ -1,48 +0,0 @@
use std::hash::{Hasher, Hash};
use itertools::Itertools;
use crate::utils::ProtoMap;
use super::super::representations::typed::{Clause, Expr};
use super::super::utils::Stackframe;
const PARAMETRICS_INLINE_COUNT:usize = 5;
// type Parametrics<'a> = ProtoMap<'a, u64, bool, PARAMETRICS_INLINE_COUNT>;
/// Hash the parts of an expression that are required to be equal for syntactic equality.
pub fn partial_hash_rec<H: Hasher>(
Expr(clause, _): &Expr, state: &mut H,
parametrics: Option<&Stackframe<u64>>
) {
match clause {
// Skip autos
Clause::Auto(id, _, body) => {
partial_hash_rec(body, state, parametrics)
}
// Annotate everything else with a prefix
// - Recurse into the tree of lambdas and calls - classic lambda calc
Clause::Lambda(id, _, body) => {
state.write_u8(0);
partial_hash_rec(body, state, Some(&Stackframe::opush(parametrics, *id)))
}
Clause::Apply(f, x) => {
state.write_u8(1);
partial_hash_rec(f, state, parametrics.clone());
partial_hash_rec(x, state, parametrics);
}
Clause::AutoArg(..) => state.write_u8(2),
// - Only recognize the depth of an argument if it refers to a non-auto parameter
Clause::LambdaArg(own_id) => {
let pos = parametrics
.and_then(|sf| sf.iter().position(|id| id == own_id))
.unwrap_or(usize::MAX);
state.write_u8(3);
state.write_usize(pos)
}
// - Hash leaves like normal
Clause::Literal(lit) => { state.write_u8(4); lit.hash(state) }
Clause::Atom(at) => { state.write_u8(5); at.hash(state) }
Clause::ExternFn(f) => { state.write_u8(6); f.hash(state) }
}
}

View File

@@ -1,102 +0,0 @@
use mappable_rc::Mrc;
use crate::box_chain;
use crate::utils::BoxedIter;
use crate::utils::iter::{box_once, box_empty};
use super::apply_lambda::apply_lambda;
use super::super::representations::typed::{Clause, Expr};
/// Call the function with the first Expression that isn't an Auto,
/// wrap all elements in the returned iterator back in the original sequence of Autos.
pub fn skip_autos<'a,
F: 'a + FnOnce(Mrc<Expr>) -> I,
I: Iterator<Item = Mrc<Expr>> + 'static
>(
expr: Mrc<Expr>, function: F
) -> BoxedIter<'static, Mrc<Expr>> {
if let Expr(Clause::Auto(id, arg, body), typ) = expr.as_ref() {
return Box::new(skip_autos(Mrc::clone(body), function).map({
let arg = Mrc::clone(arg);
let typ = Mrc::clone(typ);
move |body| {
Mrc::new(Expr(Clause::Auto(
*id,
Mrc::clone(&arg),
body
), Mrc::clone(&typ)))
}
})) as BoxedIter<'static, Mrc<Expr>>
}
Box::new(function(expr))
}
/// Produces an iterator of every expression that can be produced from this one through B-reduction.
fn direct_reductions(ex: Mrc<Expr>) -> impl Iterator<Item = Mrc<Expr>> {
skip_autos(ex, |mexpr| {
let Expr(clause, typ_ref) = mexpr.as_ref();
match clause {
Clause::Apply(f, x) => box_chain!(
skip_autos(Mrc::clone(f), |mexpr| {
let Expr(f, _) = mexpr.as_ref();
match f {
Clause::Lambda(id, _, body) => box_once(
apply_lambda(*id, Mrc::clone(x), Mrc::clone(body))
),
Clause::ExternFn(xfn) => {
let Expr(xval, xtyp) = x.as_ref();
xfn.apply(xval.clone())
.map(|ret| box_once(Mrc::new(Expr(ret, Mrc::clone(xtyp)))))
.unwrap_or(box_empty())
},
// Parametric newtypes are atoms of function type
Clause::Atom(..) | Clause::LambdaArg(..) | Clause::AutoArg(..) | Clause::Apply(..) => box_empty(),
Clause::Literal(lit) =>
panic!("Literal expression {lit:?} can't be applied as function"),
Clause::Auto(..) => unreachable!("skip_autos should have filtered this"),
}
}),
direct_reductions(Mrc::clone(f)).map({
let typ = Mrc::clone(typ_ref);
let x = Mrc::clone(x);
move |f| Mrc::new(Expr(Clause::Apply(
f,
Mrc::clone(&x)
), Mrc::clone(&typ)))
}),
direct_reductions(Mrc::clone(x)).map({
let typ = Mrc::clone(typ_ref);
let f = Mrc::clone(f);
move |x| Mrc::new(Expr(Clause::Apply(
Mrc::clone(&f),
x
), Mrc::clone(&typ)))
})
),
Clause::Lambda(id, argt, body) => {
let id = *id;
let typ = Mrc::clone(typ_ref);
let argt = Mrc::clone(argt);
let body = Mrc::clone(body);
let body_reductions = direct_reductions(body)
.map(move |body| {
let argt = Mrc::clone(&argt);
Mrc::new(Expr(
Clause::Lambda(id, argt, body),
Mrc::clone(&typ)
))
});
Box::new(body_reductions)
},
Clause::Auto(..) => unreachable!("skip_autos should have filtered this"),
Clause::Literal(..) | Clause::ExternFn(..) | Clause::Atom(..)
| Clause::LambdaArg(..) | Clause::AutoArg(..) => box_empty(),
}
})
}
/*
*/

View File

@@ -1,206 +0,0 @@
use std::collections::HashMap;
use itertools::Itertools;
use mappable_rc::Mrc;
use crate::utils::{ProtoMap, Side, mrc_empty_slice, collect_to_mrc, Stackframe, mrc_concat, Product2};
use super::super::representations::typed::{Clause, Expr};
pub fn swap<T, U>((t, u): (T, U)) -> (U, T) { (u, t) }
// @ @ (0, (foo 1)) ~ @ (0, 0)
// TODO:
// - get rid of leftovers from Explicit
// - adapt to new index-based system
enum UnifError {
Conflict,
}
type LambdaMap<'a> = Option<&'a Stackframe<'a, (u64, u64)>>;
/// The context associates a given variable (by absolute index) on a given side to
/// an expression on the opposite side rooted at the specified depth.
/// The root depths are used to translate betwee de Brujin arguments and absolute indices.
struct Context(HashMap<u64, Mrc<Expr>>);
impl Context {
fn set(&mut self, id: u64, value: &Mrc<Expr>, lambdas: LambdaMap) -> Result<Option<Mrc<Expr>>, UnifError> {
Ok(
if let Some(local) = self.0.get(&id) {
Some(
self.unify_expr(local, value, lambdas)?
.pick(Mrc::clone(local), Mrc::clone(value))
)
} else { None }
)
}
fn unify_expr(&mut self,
left: &Mrc<Expr>, right: &Mrc<Expr>, lambdas: LambdaMap
) -> Result<Product2<Mrc<Expr>>, UnifError> {
let Expr(left_val, left_typs) = left.as_ref();
let Expr(right_val, right_typs) = right.as_ref();
let val = match (left_val, right_val) {
(Clause::AutoArg(l), Clause::AutoArg(r)) if l == r => Product2::Either,
(Clause::AutoArg(id), _) => self.set(*id, left, lambdas)?.as_ref()
.map_or(Product2::Left, |e| Product2::New(e.0.clone())),
(_, Clause::AutoArg(id)) => self.set(*id, right, lambdas)?.as_ref()
.map_or(Product2::Right, |e| Product2::New(e.0.clone())),
_ => self.unify_clause(left_val, right_val, lambdas)?
};
Ok(match val {
Product2::Either if right_typs.is_empty() && left_typs.is_empty() => Product2::Either,
Product2::Left | Product2::Either if right_typs.is_empty() => Product2::Left,
Product2::Right | Product2::Either if left_typs.is_empty() => Product2::Right,
product => {
let all_types = mrc_concat(left_typs, right_typs);
Product2::New(Mrc::new(Expr(
product.pick(left_val.clone(), right_val.clone()),
all_types
)))
}
})
}
fn unify_clauses(&mut self,
left: &Mrc<[Clause]>, right: &Mrc<[Clause]>, lambdas: LambdaMap
) -> Result<Product2<Clause>, UnifError> {
if left.len() != right.len() {return Err(UnifError::Conflict)}
}
fn unify_clause(&mut self,
left: &Clause, right: &Clause, lambdas: LambdaMap
) -> Result<Product2<Clause>, UnifError> {
Ok(match (left, right) {
(Clause::Literal(l), Clause::Literal(r)) if l == r => Product2::Either,
(Clause::Atom(l), Clause::Atom(r)) if l == r => Product2::Either,
(Clause::ExternFn(l), Clause::ExternFn(r)) if l == r => Product2::Either,
(Clause::LambdaArg(l), Clause::LambdaArg(r)) => if l == r {Product2::Either} else {
let is_equal = Stackframe::o_into_iter(lambdas)
.first_some(|(l_candidate, r_candidate)| {
if l_candidate == l && r_candidate == r {Some(true)} // match
else if l_candidate == l || r_candidate == r {Some(false)} // shadow
else {None} // irrelevant
}).unwrap_or(false);
// Reference:
if is_equal {Product2::Left} else {return Err(UnifError::Conflict)}
}
(Clause::AutoArg(_), _) | (_, Clause::AutoArg(_)) => {
unreachable!("unify_expr should have handled this")
}
(Clause::Lambda(l_id, l_arg, l_body), Clause::Lambda(r_id, r_arg, r_body)) => {
let lambdas = Stackframe::opush(lambdas, (*l_id, *r_id));
self.unify_expr(l_body, r_body, Some(&lambdas))?
.map(|ex| Clause::Lambda(*l_id, mrc_empty_slice(), ex))
}
(Clause::Apply(l_f, l_x), Clause::Apply(r_f, r_x)) => {
self.unify_expr(l_f, r_f, lambdas)?.join((Mrc::clone(l_f), Mrc::clone(r_f)),
self.unify_expr(l_x, r_x, lambdas)?, (Mrc::clone(l_x), Mrc::clone(r_x))
).map(|(f, x)| Clause::Apply(f, x))
}
(Clause::Auto(l_id, l_arg, l_body), Clause::Auto(r_id, r_arg, r_body)) => {
let typ = self.unify(l_arg, r_arg, lambdas)?;
let body = self.unify_expr(l_body, r_body, lambdas)?;
typ.join((l_arg, r_arg), )
}
})
}
}
const IS_AUTO_INLINE:usize = 5;
// All data to be forwarded during recursion about one half of a unification task
#[derive(Clone)]
struct UnifHalfTask<'a> {
/// The expression to be unified
expr: &'a Expr,
/// Stores whether a given uid is auto or lambda
is_auto: ProtoMap<'a, usize, bool, IS_AUTO_INLINE>
}
impl<'a> UnifHalfTask<'a> {
fn push_auto(&mut self, body: &Expr, key: usize) {
self.expr = body;
self.is_auto.set(&key, true);
}
fn push_lambda(&mut self, body: &Expr, key: usize) {
self.expr = body;
self.is_auto.set(&key, false);
}
}
type Ctx = HashMap<usize, Mrc<Expr>>;
/// Ascertain syntactic equality. Syntactic equality means that
/// - lambda elements are verbatim equal
/// - auto constraints are pairwise syntactically equal after sorting
///
/// Context associates variables with subtrees resolved on the opposite side
pub fn unify_syntax_rec( // the stacks store true for autos, false for lambdas
ctx: &mut HashMap<(Side, usize), (usize, Mrc<Expr>)>,
ltask@UnifHalfTask{ expr: lexpr@Expr(lclause, _), .. }: UnifHalfTask,
rtask@UnifHalfTask{ expr: rexpr@Expr(rclause, _), .. }: UnifHalfTask
) -> Option<(UnifResult, UnifResult)> {
// Ensure that ex1 is a value-level construct
match lclause {
Clause::Auto(id, _, body) => {
let res = unify_syntax_rec(ltask.push_auto(body).0, rtask);
return if ltask.explicits.is_some() {
res.map(|(r1, r2)| (r1.useExplicit(), r2))
} else {res}
}
_ => ()
};
// Reduce ex2's auto handling to ex1's. In the optimizer we trust
if let Clause::Auto(..) | Clause::Explicit(..) = rclause {
return unify_syntax_rec(rtask, ltask).map(swap);
}
// Neither ex1 nor ex2 can be Auto or Explicit
match (lclause, rclause) {
// recurse into both
(Clause::Lambda(_, lbody), Clause::Lambda(_, rbody)) => unify_syntax_rec(
ltask.push_lambda(lbody),
rtask.push_lambda(rbody)
),
(Clause::Apply(lf, lx), Clause::Apply(rf, rx)) => {
let (lpart, rpart) = unify_syntax_rec(
ltask.push_expr(lf),
rtask.push_expr(rf)
)?;
lpart.dropUsedExplicits(&mut ltask);
rpart.dropUsedExplicits(&mut rtask);
unify_syntax_rec(ltask.push_expr(lx), rtask.push_expr(rx))
}
(Clause::Atom(latom), Clause::Atom(ratom)) => {
if latom != ratom { None }
else { Some((UnifResult::default(), UnifResult::default())) }
}
(Clause::ExternFn(lf), Clause::ExternFn(rf)) => {
if lf != rf { None }
else { Some((UnifResult::default(), UnifResult::default())) }
}
(Clause::Literal(llit), Clause::Literal(rlit)) => {
if llit != rlit { None }
else { Some((UnifResult::default(), UnifResult::default())) }
}
// TODO Select a representative
(Clause::Argument(depth1), Clause::Argument(depth2)) => {
!*stack1.iter().nth(*depth1).unwrap_or(&false)
&& !*stack2.iter().nth(*depth2).unwrap_or(&false)
&& stack1.iter().count() - depth1 == stack2.iter().count() - depth2
}
// TODO Assign a substitute
(Clause::Argument(placeholder), _) => {
}
}
}
// Tricky unifications
// @A. A A 1 ~ @B. 2 B B = fails if left-authoritative
// @A. 1 A A ~ @B. B B 2
// @A. A 1 A ~ @B. B B 2
// @ 0 X 0 ~ @ 0 0 Y

View File

@@ -2,21 +2,21 @@ use std::rc::Rc;
use std::fmt::Display;
use crate::foreign::ExternError;
use crate::representations::interpreted::Clause;
use crate::representations::interpreted::ExprInst;
#[derive(Clone)]
pub struct AssertionError{
pub value: Clause,
pub value: ExprInst,
pub assertion: &'static str,
}
impl AssertionError {
pub fn fail(value: Clause, assertion: &'static str) -> Result<!, Rc<dyn ExternError>> {
pub fn fail(value: ExprInst, assertion: &'static str) -> Result<!, Rc<dyn ExternError>> {
return Err(Self { value, assertion }.into_extern())
}
pub fn ext(value: Clause, assertion: &'static str) -> Rc<dyn ExternError> {
pub fn ext(value: ExprInst, assertion: &'static str) -> Rc<dyn ExternError> {
return Self { value, assertion }.into_extern()
}
}

View File

@@ -1,5 +1,6 @@
use crate::{atomic_inert, representations::{interpreted::Clause, Primitive}, foreign::Atom};
use crate::foreign::Atom;
use crate::representations::{interpreted::{Clause, ExprInst}, Primitive};
use crate::atomic_inert;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Boolean(pub bool);
@@ -7,11 +8,12 @@ atomic_inert!(Boolean);
impl From<bool> for Boolean { fn from(value: bool) -> Self { Self(value) } }
impl<'a> TryFrom<&'a Clause> for Boolean {
impl TryFrom<ExprInst> for Boolean {
type Error = ();
fn try_from(value: &'a Clause) -> Result<Self, Self::Error> {
if let Clause::P(Primitive::Atom(Atom(a))) = value {
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)
}

View File

@@ -1,10 +1,8 @@
use std::fmt::Debug;
use std::hash::Hash;
use crate::external::litconv::with_lit;
use crate::representations::{interpreted::ExprInst, Literal};
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::foreign::Atom;
use crate::representations::{Primitive, Literal};
use crate::representations::interpreted::Clause;
use super::super::assertion_error::AssertionError;
use super::boolean::Boolean;
@@ -15,38 +13,34 @@ use super::boolean::Boolean;
#[derive(Clone)]
pub struct Equals2;
externfn_impl!(Equals2, |_: &Self, c: Clause| {Ok(Equals1{c})});
externfn_impl!(Equals2, |_: &Self, x: ExprInst| {Ok(Equals1{x})});
/// Partially applied Equals function
///
/// Prev state: [Equals2]; Next state: [Equals0]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Equals1{ c: Clause }
atomic_redirect!(Equals1, c);
#[derive(Debug, Clone)]
pub struct Equals1{ x: ExprInst }
atomic_redirect!(Equals1, x);
atomic_impl!(Equals1);
externfn_impl!(Equals1, |this: &Self, c: Clause| {
let a: Literal = this.c.clone().try_into()
.map_err(|_| AssertionError::ext(this.c.clone(), "a primitive"))?;
Ok(Equals0{ a, c })
externfn_impl!(Equals1, |this: &Self, x: ExprInst| {
with_lit(&this.x, |l| Ok(Equals0{ a: l.clone(), x }))
});
/// Fully applied Equals function.
///
/// Prev state: [Equals1]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Equals0 { a: Literal, c: Clause }
atomic_redirect!(Equals0, c);
atomic_impl!(Equals0, |Self{ a, c }: &Self| {
let b: Literal = c.clone().try_into()
.map_err(|_| AssertionError::ext(c.clone(), "a literal value"))?;
let eqls = 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,
(_, _) => AssertionError::fail(c.clone(), "the expected type")?,
};
Ok(Clause::P(Primitive::Atom(Atom::new(Boolean::from(eqls)))))
#[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,
(_, _) => AssertionError::fail(x.clone(), "the expected type")?,
}))?;
Ok(Boolean::from(eqls).to_atom_cls())
});

View File

@@ -1,11 +1,9 @@
use std::fmt::Debug;
use std::hash::Hash;
use std::rc::Rc;
use crate::external::assertion_error::AssertionError;
use crate::representations::PathSet;
use crate::representations::{PathSet, interpreted::{Clause, ExprInst}};
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::representations::interpreted::Clause;
use super::Boolean;
@@ -15,29 +13,29 @@ use super::Boolean;
#[derive(Clone)]
pub struct IfThenElse1;
externfn_impl!(IfThenElse1, |_: &Self, c: Clause| {Ok(IfThenElse0{c})});
externfn_impl!(IfThenElse1, |_: &Self, x: ExprInst| {Ok(IfThenElse0{x})});
/// Partially applied IfThenElse function
///
/// Prev state: [IfThenElse1]; Next state: [IfThenElse0]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct IfThenElse0{ c: Clause }
atomic_redirect!(IfThenElse0, c);
#[derive(Debug, Clone)]
pub struct IfThenElse0{ x: ExprInst }
atomic_redirect!(IfThenElse0, x);
atomic_impl!(IfThenElse0, |this: &Self| {
let Boolean(b) = (&this.c).try_into()
.map_err(|_| AssertionError::ext(this.c.clone(), "a boolean"))?;
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: Rc::new(Clause::Lambda {
body: Clause::Lambda {
args: None,
body: Rc::new(Clause::LambdaArg)
})
body: Clause::LambdaArg.wrap()
}.wrap()
}} else { Clause::Lambda {
args: None,
body: Rc::new(Clause::Lambda {
body: Clause::Lambda {
args: Some(PathSet { steps: Rc::new(vec![]), next: None }),
body: Rc::new(Clause::LambdaArg)
})
body: Clause::LambdaArg.wrap()
}.wrap()
}})
});

View File

@@ -3,11 +3,12 @@ mod boolean;
mod ifthenelse;
pub use boolean::Boolean;
use crate::project::{Loader, extlib_loader};
use crate::{pipeline::ConstTree, interner::Interner};
pub fn bool() -> impl Loader {
extlib_loader(vec![
("ifthenelse", Box::new(ifthenelse::IfThenElse1)),
("equals", Box::new(equals::Equals2))
pub fn bool(i: &Interner) -> ConstTree {
ConstTree::tree([
(i.i("ifthenelse"), ConstTree::xfn(ifthenelse::IfThenElse1)),
(i.i("equals"), ConstTree::xfn(equals::Equals2))
])
}

View File

@@ -1,13 +1,13 @@
use crate::project::{extlib_loader, Loader};
use crate::{interner::Interner, pipeline::ConstTree};
mod to_string;
mod parse_float;
mod parse_uint;
pub fn conv() -> impl Loader {
extlib_loader(vec![
("parse_float", Box::new(parse_float::ParseFloat1)),
("parse_uint", Box::new(parse_uint::ParseUint1)),
("to_string", Box::new(to_string::ToString1))
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))
])
}

View File

@@ -2,14 +2,12 @@
use chumsky::Parser;
use std::fmt::Debug;
use std::hash::Hash;
use super::super::assertion_error::AssertionError;
use crate::external::litconv::with_lit;
use crate::parse::float_parser;
use crate::representations::{interpreted::ExprInst, Literal};
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::foreign::ExternError;
use crate::representations::{Primitive, Literal};
use crate::representations::interpreted::Clause;
/// ParseFloat a number
///
@@ -17,30 +15,27 @@ use crate::representations::interpreted::Clause;
#[derive(Clone)]
pub struct ParseFloat1;
externfn_impl!(ParseFloat1, |_: &Self, c: Clause| {Ok(ParseFloat0{c})});
externfn_impl!(ParseFloat1, |_: &Self, x: ExprInst| {Ok(ParseFloat0{x})});
/// Applied to_string function
///
/// Prev state: [ParseFloat1]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct ParseFloat0{ c: Clause }
atomic_redirect!(ParseFloat0, c);
atomic_impl!(ParseFloat0, |Self{ c }: &Self| {
let literal: &Literal = c.try_into()
.map_err(|_| AssertionError::ext(c.clone(), "a literal value"))?;
let number = match literal {
#[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{
value: c.clone(), assertion: "cannot be parsed into a float"
}.into_extern())?
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{
value: c.clone(), assertion: "is not a decimal digit"
}.into_extern())?.into()
};
Ok(Clause::P(Primitive::Literal(Literal::Num(number))))
Literal::Char(char) => char.to_digit(10)
.ok_or(AssertionError::ext(x.clone(), "is not a decimal digit"))?
.into()
}))?;
Ok(number.into())
});

View File

@@ -2,14 +2,11 @@
use chumsky::Parser;
use std::fmt::Debug;
use std::hash::Hash;
use super::super::assertion_error::AssertionError;
use crate::parse::int_parser;
use crate::external::{litconv::with_lit, assertion_error::AssertionError};
use crate::representations::{interpreted::ExprInst, Literal};
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::foreign::ExternError;
use crate::representations::{Primitive, Literal};
use crate::representations::interpreted::Clause;
use crate::parse::int_parser;
/// Parse a number
///
@@ -17,30 +14,27 @@ use crate::representations::interpreted::Clause;
#[derive(Clone)]
pub struct ParseUint1;
externfn_impl!(ParseUint1, |_: &Self, c: Clause| {Ok(ParseUint0{c})});
externfn_impl!(ParseUint1, |_: &Self, x: ExprInst| {Ok(ParseUint0{x})});
/// Applied ParseUint function
///
/// Prev state: [ParseUint1]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct ParseUint0{ c: Clause }
atomic_redirect!(ParseUint0, c);
atomic_impl!(ParseUint0, |Self{ c }: &Self| {
let literal: &Literal = c.try_into()
.map_err(|_| AssertionError::ext(c.clone(), "a literal value"))?;
let uint = match literal {
#[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{
value: c.clone(), assertion: "cannot be parsed into an unsigned int"
}.into_extern())?
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{
value: c.clone(), assertion: "is not a decimal digit"
}.into_extern())? as u64
};
Ok(Clause::P(Primitive::Literal(Literal::Uint(uint))))
Literal::Char(char) => char.to_digit(10)
.ok_or(AssertionError::ext(x.clone(), "is not a decimal digit"))?
.into()
}))?;
Ok(uint.into())
});

View File

@@ -1,11 +1,9 @@
use std::fmt::Debug;
use std::hash::Hash;
use crate::external::assertion_error::AssertionError;
use crate::external::litconv::with_lit;
use crate::representations::{interpreted::ExprInst, Literal};
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::representations::{Primitive, Literal};
use crate::representations::interpreted::Clause;
/// ToString a clause
///
@@ -13,23 +11,21 @@ use crate::representations::interpreted::Clause;
#[derive(Clone)]
pub struct ToString1;
externfn_impl!(ToString1, |_: &Self, c: Clause| {Ok(ToString0{c})});
externfn_impl!(ToString1, |_: &Self, x: ExprInst| {Ok(ToString0{x})});
/// Applied ToString function
///
/// Prev state: [ToString1]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct ToString0{ c: Clause }
atomic_redirect!(ToString0, c);
atomic_impl!(ToString0, |Self{ c }: &Self| {
let literal: &Literal = c.try_into()
.map_err(|_| AssertionError::ext(c.clone(), "a literal value"))?;
let string = match literal {
#[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(Clause::P(Primitive::Literal(Literal::Str(string))))
}))?;
Ok(string.into())
});

View File

@@ -1,11 +1,11 @@
use crate::project::{Loader, extlib_loader};
use crate::{interner::Interner, pipeline::ConstTree};
mod print;
mod readline;
pub fn cpsio() -> impl Loader {
extlib_loader(vec![
("print", Box::new(print::Print2)),
("readline", Box::new(readline::Readln2))
pub fn cpsio(i: &Interner) -> ConstTree {
ConstTree::tree([
(i.i("print"), ConstTree::xfn(print::Print2)),
(i.i("readline"), ConstTree::xfn(readline::Readln2))
])
}

View File

@@ -1,11 +1,10 @@
use std::fmt::Debug;
use std::hash::Hash;
use std::rc::Rc;
use crate::external::str::cls2str;
use crate::external::litconv::with_str;
use crate::representations::PathSet;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::representations::interpreted::Clause;
use crate::representations::interpreted::{Clause, ExprInst};
/// Print function
///
@@ -13,20 +12,21 @@ use crate::representations::interpreted::Clause;
#[derive(Clone)]
pub struct Print2;
externfn_impl!(Print2, |_: &Self, c: Clause| {Ok(Print1{c})});
externfn_impl!(Print2, |_: &Self, x: ExprInst| {Ok(Print1{x})});
/// Partially applied Print function
///
/// Prev state: [Print2]; Next state: [Print0]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Print1{ c: Clause }
atomic_redirect!(Print1, c);
atomic_impl!(Print1, |Self{ c }: &Self| {
let message = cls2str(&c)?;
print!("{}", message);
Ok(Clause::Lambda {
args: Some(PathSet{ steps: Rc::new(vec![]), next: None }),
body: Rc::new(Clause::LambdaArg)
#[derive(Debug, Clone)]
pub struct Print1{ x: ExprInst }
atomic_redirect!(Print1, x);
atomic_impl!(Print1, |Self{ x }: &Self| {
with_str(x, |s| {
print!("{}", s);
Ok(Clause::Lambda {
args: Some(PathSet{ steps: Rc::new(vec![]), next: None }),
body: Clause::LambdaArg.wrap()
})
})
});

View File

@@ -1,12 +1,10 @@
use std::fmt::Debug;
use std::io::stdin;
use std::rc::Rc;
use std::hash::Hash;
use crate::external::runtime_error::RuntimeError;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::representations::{Primitive, Literal};
use crate::representations::interpreted::Clause;
use crate::representations::interpreted::{Clause, ExprInst};
/// Readln function
///
@@ -14,22 +12,21 @@ use crate::representations::interpreted::Clause;
#[derive(Clone)]
pub struct Readln2;
externfn_impl!(Readln2, |_: &Self, c: Clause| {Ok(Readln1{c})});
externfn_impl!(Readln2, |_: &Self, x: ExprInst| {Ok(Readln1{x})});
/// Partially applied Readln function
///
/// Prev state: [Readln2]; Next state: [Readln0]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Readln1{ c: Clause }
atomic_redirect!(Readln1, c);
atomic_impl!(Readln1, |Self{ c }: &Self| {
#[derive(Debug, Clone)]
pub struct Readln1{ x: ExprInst }
atomic_redirect!(Readln1, x);
atomic_impl!(Readln1, |Self{ x }: &Self| {
let mut buf = String::new();
stdin().read_line(&mut buf).map_err(|e| RuntimeError::ext(e.to_string(), "reading from stdin"))?;
buf.pop();
Ok(Clause::Apply {
f: Rc::new(c.clone()),
x: Rc::new(Clause::P(Primitive::Literal(Literal::Str(buf)))),
id: 0
f: x.clone(),
x: Clause::P(Primitive::Literal(Literal::Str(buf))).wrap()
})
});

34
src/external/litconv.rs vendored Normal file
View File

@@ -0,0 +1,34 @@
use std::rc::Rc;
use crate::foreign::ExternError;
use crate::external::assertion_error::AssertionError;
use crate::representations::interpreted::ExprInst;
use crate::representations::Literal;
pub fn with_lit<T>(x: &ExprInst,
predicate: impl FnOnce(&Literal) -> Result<T, Rc<dyn ExternError>>
) -> Result<T, Rc<dyn ExternError>> {
x.with_literal(predicate)
.map_err(|()| AssertionError::ext(x.clone(), "a literal value"))
.and_then(|r| r)
}
pub fn with_str<T>(x: &ExprInst,
predicate: impl FnOnce(&String) -> Result<T, Rc<dyn ExternError>>
) -> Result<T, Rc<dyn ExternError>> {
with_lit(x, |l| {
if let Literal::Str(s) = l {predicate(&s)} else {
AssertionError::fail(x.clone(), "a string")?
}
})
}
pub fn with_uint<T>(x: &ExprInst,
predicate: impl FnOnce(u64) -> Result<T, Rc<dyn ExternError>>
) -> Result<T, Rc<dyn ExternError>> {
with_lit(x, |l| {
if let Literal::Uint(u) = l {predicate(*u)} else {
AssertionError::fail(x.clone(), "an uint")?
}
})
}

1
src/external/mod.rs vendored
View File

@@ -6,3 +6,4 @@ mod str;
mod cpsio;
mod runtime_error;
mod bool;
mod litconv;

View File

@@ -2,14 +2,14 @@ mod numeric;
pub mod operators;
pub use numeric::Numeric;
use crate::project::{extlib_loader, Loader};
use crate::{interner::Interner, pipeline::ConstTree};
pub fn num() -> impl Loader {
extlib_loader(vec![
("add", Box::new(operators::add::Add2)),
("subtract", Box::new(operators::subtract::Subtract2)),
("multiply", Box::new(operators::multiply::Multiply2)),
("divide", Box::new(operators::divide::Divide2)),
("remainder", Box::new(operators::remainder::Remainder2))
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))
])
}

View File

@@ -4,9 +4,11 @@ use std::rc::Rc;
use ordered_float::NotNan;
use crate::external::assertion_error::AssertionError;
use crate::external::litconv::with_lit;
use crate::foreign::ExternError;
use crate::representations::{Primitive, Literal};
use crate::representations::interpreted::Clause;
use crate::representations::Literal;
use crate::representations::Primitive;
use crate::representations::interpreted::{Clause, ExprInst};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Numeric {
@@ -93,17 +95,14 @@ impl Rem for Numeric {
}
}
impl TryFrom<Clause> for Numeric {
impl TryFrom<ExprInst> for Numeric {
type Error = Rc<dyn ExternError>;
fn try_from(value: Clause) -> Result<Self, Self::Error> {
let l = if let Clause::P(Primitive::Literal(l)) = value.clone() {l} else {
AssertionError::fail(value, "a literal value")?
};
match l {
Literal::Uint(i) => Ok(Numeric::Uint(i)),
Literal::Num(n) => Ok(Numeric::Num(n)),
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")?
}
})
}
}

View File

@@ -2,10 +2,9 @@
use super::super::Numeric;
use std::fmt::Debug;
use std::hash::Hash;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::representations::interpreted::Clause;
use crate::representations::interpreted::ExprInst;
/// Add function
///
@@ -13,29 +12,29 @@ use crate::representations::interpreted::Clause;
#[derive(Clone)]
pub struct Add2;
externfn_impl!(Add2, |_: &Self, c: Clause| {Ok(Add1{c})});
externfn_impl!(Add2, |_: &Self, x: ExprInst| {Ok(Add1{x})});
/// Partially applied Add function
///
/// Prev state: [Add2]; Next state: [Add0]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Add1{ c: Clause }
atomic_redirect!(Add1, c);
#[derive(Debug, Clone)]
pub struct Add1{ x: ExprInst }
atomic_redirect!(Add1, x);
atomic_impl!(Add1);
externfn_impl!(Add1, |this: &Self, c: Clause| {
let a: Numeric = this.c.clone().try_into()?;
Ok(Add0{ a, c })
externfn_impl!(Add1, |this: &Self, x: ExprInst| {
let a: Numeric = this.x.clone().try_into()?;
Ok(Add0{ a, x })
});
/// Fully applied Add function.
///
/// Prev state: [Add1]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Add0 { a: Numeric, c: Clause }
atomic_redirect!(Add0, c);
atomic_impl!(Add0, |Self{ a, c }: &Self| {
let b: Numeric = c.clone().try_into()?;
#[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())
});

View File

@@ -2,10 +2,9 @@
use super::super::Numeric;
use std::fmt::Debug;
use std::hash::Hash;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::representations::interpreted::Clause;
use crate::representations::interpreted::ExprInst;
/// Divide function
///
@@ -13,29 +12,29 @@ use crate::representations::interpreted::Clause;
#[derive(Clone)]
pub struct Divide2;
externfn_impl!(Divide2, |_: &Self, c: Clause| {Ok(Divide1{c})});
externfn_impl!(Divide2, |_: &Self, x: ExprInst| {Ok(Divide1{x})});
/// Partially applied Divide function
///
/// Prev state: [Divide2]; Next state: [Divide0]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Divide1{ c: Clause }
atomic_redirect!(Divide1, c);
#[derive(Debug, Clone)]
pub struct Divide1{ x: ExprInst }
atomic_redirect!(Divide1, x);
atomic_impl!(Divide1);
externfn_impl!(Divide1, |this: &Self, c: Clause| {
let a: Numeric = this.c.clone().try_into()?;
Ok(Divide0{ a, c })
externfn_impl!(Divide1, |this: &Self, x: ExprInst| {
let a: Numeric = this.x.clone().try_into()?;
Ok(Divide0{ a, x })
});
/// Fully applied Divide function.
///
/// Prev state: [Divide1]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Divide0 { a: Numeric, c: Clause }
atomic_redirect!(Divide0, c);
atomic_impl!(Divide0, |Self{ a, c }: &Self| {
let b: Numeric = c.clone().try_into()?;
#[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())
});

View File

@@ -2,10 +2,9 @@
use super::super::Numeric;
use std::fmt::Debug;
use std::hash::Hash;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::representations::interpreted::Clause;
use crate::representations::interpreted::ExprInst;
/// Multiply function
///
@@ -13,29 +12,29 @@ use crate::representations::interpreted::Clause;
#[derive(Clone)]
pub struct Multiply2;
externfn_impl!(Multiply2, |_: &Self, c: Clause| {Ok(Multiply1{c})});
externfn_impl!(Multiply2, |_: &Self, x: ExprInst| {Ok(Multiply1{x})});
/// Partially applied Multiply function
///
/// Prev state: [Multiply2]; Next state: [Multiply0]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Multiply1{ c: Clause }
atomic_redirect!(Multiply1, c);
#[derive(Debug, Clone)]
pub struct Multiply1{ x: ExprInst }
atomic_redirect!(Multiply1, x);
atomic_impl!(Multiply1);
externfn_impl!(Multiply1, |this: &Self, c: Clause| {
let a: Numeric = this.c.clone().try_into()?;
Ok(Multiply0{ a, c })
externfn_impl!(Multiply1, |this: &Self, x: ExprInst| {
let a: Numeric = this.x.clone().try_into()?;
Ok(Multiply0{ a, x })
});
/// Fully applied Multiply function.
///
/// Prev state: [Multiply1]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Multiply0 { a: Numeric, c: Clause }
atomic_redirect!(Multiply0, c);
atomic_impl!(Multiply0, |Self{ a, c }: &Self| {
let b: Numeric = c.clone().try_into()?;
#[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())
});

View File

@@ -2,10 +2,9 @@
use super::super::Numeric;
use std::fmt::Debug;
use std::hash::Hash;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::representations::interpreted::Clause;
use crate::representations::interpreted::ExprInst;
/// Remainder function
///
@@ -13,29 +12,29 @@ use crate::representations::interpreted::Clause;
#[derive(Clone)]
pub struct Remainder2;
externfn_impl!(Remainder2, |_: &Self, c: Clause| {Ok(Remainder1{c})});
externfn_impl!(Remainder2, |_: &Self, x: ExprInst| {Ok(Remainder1{x})});
/// Partially applied Remainder function
///
/// Prev state: [Remainder2]; Next state: [Remainder0]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Remainder1{ c: Clause }
atomic_redirect!(Remainder1, c);
#[derive(Debug, Clone)]
pub struct Remainder1{ x: ExprInst }
atomic_redirect!(Remainder1, x);
atomic_impl!(Remainder1);
externfn_impl!(Remainder1, |this: &Self, c: Clause| {
let a: Numeric = this.c.clone().try_into()?;
Ok(Remainder0{ a, c })
externfn_impl!(Remainder1, |this: &Self, x: ExprInst| {
let a: Numeric = this.x.clone().try_into()?;
Ok(Remainder0{ a, x })
});
/// Fully applied Remainder function.
///
/// Prev state: [Remainder1]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Remainder0 { a: Numeric, c: Clause }
atomic_redirect!(Remainder0, c);
atomic_impl!(Remainder0, |Self{ a, c }: &Self| {
let b: Numeric = c.clone().try_into()?;
#[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())
});

View File

@@ -2,10 +2,9 @@
use super::super::Numeric;
use std::fmt::Debug;
use std::hash::Hash;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::representations::interpreted::{Clause};
use crate::representations::interpreted::ExprInst;
/// Subtract function
///
@@ -13,29 +12,29 @@ use crate::representations::interpreted::{Clause};
#[derive(Clone)]
pub struct Subtract2;
externfn_impl!(Subtract2, |_: &Self, c: Clause| {Ok(Subtract1{c})});
externfn_impl!(Subtract2, |_: &Self, x: ExprInst| {Ok(Subtract1{x})});
/// Partially applied Subtract function
///
/// Prev state: [Subtract2]; Next state: [Subtract0]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Subtract1{ c: Clause }
atomic_redirect!(Subtract1, c);
#[derive(Debug, Clone)]
pub struct Subtract1{ x: ExprInst }
atomic_redirect!(Subtract1, x);
atomic_impl!(Subtract1);
externfn_impl!(Subtract1, |this: &Self, c: Clause| {
let a: Numeric = this.c.clone().try_into()?;
Ok(Subtract0{ a, c })
externfn_impl!(Subtract1, |this: &Self, x: ExprInst| {
let a: Numeric = this.x.clone().try_into()?;
Ok(Subtract0{ a, x })
});
/// Fully applied Subtract function.
///
/// Prev state: [Subtract1]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Subtract0 { a: Numeric, c: Clause }
atomic_redirect!(Subtract0, c);
atomic_impl!(Subtract0, |Self{ a, c }: &Self| {
let b: Numeric = c.clone().try_into()?;
#[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())
});

19
src/external/std.rs vendored
View File

@@ -1,6 +1,5 @@
use std::collections::HashMap;
use crate::project::{map_loader, Loader};
use crate::pipeline::ConstTree;
use crate::interner::Interner;
use super::bool::bool;
use super::cpsio::cpsio;
@@ -8,12 +7,10 @@ use super::conv::conv;
use super::str::str;
use super::num::num;
pub fn std() -> impl Loader {
map_loader(HashMap::from([
("cpsio", cpsio().boxed()),
("conv", conv().boxed()),
("bool", bool().boxed()),
("str", str().boxed()),
("num", num().boxed()),
]))
pub fn std(i: &Interner) -> ConstTree {
cpsio(i)
+ conv(i)
+ bool(i)
+ str(i)
+ num(i)
}

View File

@@ -1,11 +1,10 @@
use std::fmt::Debug;
use std::hash::Hash;
use crate::external::assertion_error::AssertionError;
use crate::external::litconv::{with_str, with_uint};
use crate::external::runtime_error::RuntimeError;
use crate::representations::{Literal, Primitive};
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::representations::interpreted::Clause;
use crate::representations::interpreted::{Clause, ExprInst};
/// CharAt function
///
@@ -13,35 +12,31 @@ use crate::representations::interpreted::Clause;
#[derive(Clone)]
pub struct CharAt2;
externfn_impl!(CharAt2, |_: &Self, c: Clause| {Ok(CharAt1{c})});
externfn_impl!(CharAt2, |_: &Self, x: ExprInst| {Ok(CharAt1{x})});
/// Partially applied CharAt function
///
/// Prev state: [CharAt2]; Next state: [CharAt0]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct CharAt1{ c: Clause }
atomic_redirect!(CharAt1, c);
#[derive(Debug, Clone)]
pub struct CharAt1{ x: ExprInst }
atomic_redirect!(CharAt1, x);
atomic_impl!(CharAt1);
externfn_impl!(CharAt1, |this: &Self, c: Clause| {
let s = if let Ok(Literal::Str(s)) = this.c.clone().try_into() {s}
else {AssertionError::fail(this.c.clone(), "a string")?};
Ok(CharAt0{ s, c })
externfn_impl!(CharAt1, |this: &Self, x: ExprInst| {
with_str(&this.x, |s| Ok(CharAt0{ s: s.clone(), x }))
});
/// Fully applied CharAt function.
///
/// Prev state: [CharAt1]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct CharAt0 { s: String, c: Clause }
atomic_redirect!(CharAt0, c);
atomic_impl!(CharAt0, |Self{ s, c }: &Self| {
let i = if let Ok(Literal::Uint(i)) = c.clone().try_into() {i}
else {AssertionError::fail(c.clone(), "an uint")?};
if let Some(c) = s.chars().nth(i as usize) {
#[derive(Debug, Clone)]
pub struct CharAt0 { s: String, x: ExprInst }
atomic_redirect!(CharAt0, x);
atomic_impl!(CharAt0, |Self{ s, x }: &Self| {
with_uint(x, |i| 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")?
}
})
});

View File

@@ -1,13 +0,0 @@
use std::rc::Rc;
use crate::foreign::ExternError;
use crate::external::assertion_error::AssertionError;
use crate::representations::{interpreted::Clause, Literal};
pub fn cls2str(c: &Clause) -> Result<&String, Rc<dyn ExternError>> {
let literal: &Literal = c.try_into()
.map_err(|_| AssertionError::ext(c.clone(), "a literal value"))?;
if let Literal::Str(s) = literal {Ok(s)} else {
AssertionError::fail(c.clone(), "a string")?
}
}

View File

@@ -1,11 +1,9 @@
use super::cls2str;
use std::fmt::Debug;
use std::hash::Hash;
use crate::external::litconv::with_str;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::representations::{Primitive, Literal};
use crate::representations::interpreted::Clause;
use crate::representations::interpreted::{Clause, ExprInst};
/// Concatenate function
///
@@ -13,29 +11,29 @@ use crate::representations::interpreted::Clause;
#[derive(Clone)]
pub struct Concatenate2;
externfn_impl!(Concatenate2, |_: &Self, c: Clause| {Ok(Concatenate1{c})});
externfn_impl!(Concatenate2, |_: &Self, c: ExprInst| {Ok(Concatenate1{c})});
/// Partially applied Concatenate function
///
/// Prev state: [Concatenate2]; Next state: [Concatenate0]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Concatenate1{ c: Clause }
#[derive(Debug, Clone)]
pub struct Concatenate1{ c: ExprInst }
atomic_redirect!(Concatenate1, c);
atomic_impl!(Concatenate1);
externfn_impl!(Concatenate1, |this: &Self, c: Clause| {
let a: String = cls2str(&this.c)?.clone();
Ok(Concatenate0{ a, c })
externfn_impl!(Concatenate1, |this: &Self, c: ExprInst| {
with_str(&this.c, |a| Ok(Concatenate0{ a: a.clone(), c }))
});
/// Fully applied Concatenate function.
///
/// Prev state: [Concatenate1]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Concatenate0 { a: String, c: Clause }
#[derive(Debug, Clone)]
pub struct Concatenate0 { a: String, c: ExprInst }
atomic_redirect!(Concatenate0, c);
atomic_impl!(Concatenate0, |Self{ a, c }: &Self| {
let b: &String = cls2str(c)?;
Ok(Clause::P(Primitive::Literal(Literal::Str(a.to_owned() + b))))
with_str(c, |b| Ok(Clause::P(Primitive::Literal(
Literal::Str(a.to_owned() + b)
))))
});

View File

@@ -1,11 +1,10 @@
mod concatenate;
mod cls2str;
mod char_at;
pub use cls2str::cls2str;
use crate::project::{Loader, extlib_loader};
pub fn str() -> impl Loader {
extlib_loader(vec![
("concatenate", Box::new(concatenate::Concatenate2))
use crate::{pipeline::ConstTree, interner::Interner};
pub fn str(i: &Interner) -> ConstTree {
ConstTree::tree([
(i.i("concatenate"), ConstTree::xfn(concatenate::Concatenate2))
])
}

View File

@@ -5,9 +5,17 @@ use std::rc::Rc;
use dyn_clone::DynClone;
use crate::representations::interpreted::{
Clause, RuntimeError, InternalError
};
use crate::interpreter::{RuntimeError, Context};
use crate::representations::Primitive;
pub use crate::representations::interpreted::Clause;
use crate::representations::interpreted::ExprInst;
// Aliases for concise macros
pub type RcError = Rc<dyn ExternError>;
pub type AtomicResult = Result<(Clause, Option<usize>), RuntimeError>;
pub type XfnResult = Result<(Clause, Option<usize>), RcError>;
pub type RcExpr = ExprInst;
pub trait ExternError: Display {
fn into_extern(self) -> Rc<dyn ExternError>
@@ -21,10 +29,13 @@ pub trait ExternError: Display {
/// these are also external functions.
pub trait ExternFn: DynClone {
fn name(&self) -> &str;
fn apply(&self, arg: Clause) -> Result<Clause, Rc<dyn ExternError>>;
fn apply(&self, arg: ExprInst, ctx: Context) -> XfnResult;
fn hash(&self, state: &mut dyn std::hash::Hasher) {
state.write_str(self.name())
}
fn to_xfn_cls(self) -> Clause where Self: Sized + 'static {
Clause::P(Primitive::ExternFn(Box::new(self)))
}
}
impl Eq for dyn ExternFn {}
@@ -44,11 +55,10 @@ impl Debug for dyn ExternFn {
pub trait Atomic: Any + Debug + DynClone where Self: 'static {
fn as_any(&self) -> &dyn Any;
fn definitely_eq(&self, _other: &dyn Any) -> bool;
fn hash(&self, hasher: &mut dyn std::hash::Hasher);
fn run_once(&self) -> Result<Clause, InternalError>;
fn run_n_times(&self, n: usize) -> Result<(Clause, usize), RuntimeError>;
fn run_to_completion(&self) -> Result<Clause, RuntimeError>;
fn run(&self, ctx: Context) -> AtomicResult;
fn to_atom_cls(self) -> Clause where Self: Sized {
Clause::P(Primitive::Atom(Atom(Box::new(self))))
}
}
/// Represents a black box unit of code with its own normalization steps.
@@ -83,19 +93,8 @@ impl Clone for Atom {
}
}
impl Hash for Atom {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state)
}
}
impl Debug for Atom {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "##ATOM[{:?}]##", self.data())
}
}
impl Eq for Atom {}
impl PartialEq for Atom {
fn eq(&self, other: &Self) -> bool {
self.data().definitely_eq(other.data().as_any())
}
}

View File

@@ -9,11 +9,5 @@ use crate::foreign::Atomic;
macro_rules! atomic_defaults {
() => {
fn as_any(&self) -> &dyn std::any::Any { self }
fn definitely_eq(&self, _other: &dyn std::any::Any) -> bool {
_other.downcast_ref().map(|o| self == o).unwrap_or(false)
}
fn hash(&self, mut hasher: &mut dyn std::hash::Hasher) {
<Self as std::hash::Hash>::hash(self, &mut hasher)
}
};
}

View File

@@ -39,66 +39,40 @@ use std::fmt::Debug;
#[macro_export]
macro_rules! atomic_impl {
($typ:ident) => {
atomic_impl!{$typ, |this: &Self| Ok(Clause::P(
$crate::representations::Primitive::ExternFn(Box::new(this.clone()))
))}
atomic_impl!{$typ, |this: &Self| {
use $crate::foreign::ExternFn;
Ok(this.clone().to_xfn_cls())
}}
};
($typ:ident, $next_phase:expr) => {
impl $crate::foreign::Atomic for $typ {
$crate::atomic_defaults!{}
fn run_once(&self) -> Result<
$crate::representations::interpreted::Clause,
$crate::representations::interpreted::InternalError
> {
match <Self as AsRef<$crate::representations::interpreted::Clause>>::as_ref(self).run_once() {
Err($crate::representations::interpreted::InternalError::NonReducible) => {
($next_phase)(self)
.map_err($crate::representations::interpreted::RuntimeError::Extern)
.map_err($crate::representations::interpreted::InternalError::Runtime)
}
Ok(arg) => Ok($crate::representations::interpreted::Clause::P(
$crate::representations::Primitive::Atom(
$crate::foreign::Atom::new(
<Self as From<(&Self, Clause)>>::from((self, arg))
)
fn run(&self, ctx: $crate::interpreter::Context)
-> $crate::foreign::AtomicResult
{
// extract the expression
let expr = <Self as
AsRef<$crate::foreign::RcExpr>
>::as_ref(self).clone();
// run the expression
let ret = $crate::interpreter::run(expr, ctx)?;
let $crate::interpreter::Return{ gas, state } = ret;
// rebuild the atomic
let next_self = <Self as
From<(&Self, $crate::foreign::RcExpr)>
>::from((self, state));
// branch off or wrap up
let next_clause = if gas.map(|g| g > 0).unwrap_or(true) {
match ($next_phase)(&next_self) {
Ok(r) => r,
Err(e) => return Err(
$crate::interpreter::RuntimeError::Extern(e)
)
)),
Err(e) => Err(e),
}
}
fn run_n_times(&self, n: usize) -> Result<
(
$crate::representations::interpreted::Clause,
usize
),
$crate::representations::interpreted::RuntimeError
> {
match <Self as AsRef<Clause>>::as_ref(self).run_n_times(n) {
Ok((arg, k)) if k == n => Ok((Clause::P(
$crate::representations::Primitive::Atom(
$crate::foreign::Atom::new(
<Self as From<(&Self, Clause)>>::from((self, arg))
)
)
), k)),
Ok((arg, k)) => {
let intermediate = <Self as From<(&Self, Clause)>>::from((self, arg));
($next_phase)(&intermediate)
.map(|cls| (cls, k))
.map_err($crate::representations::interpreted::RuntimeError::Extern)
}
Err(e) => Err(e),
}
}
fn run_to_completion(&self) -> Result<Clause, $crate::representations::interpreted::RuntimeError> {
match <Self as AsRef<Clause>>::as_ref(self).run_to_completion() {
Ok(arg) => {
let intermediate = <Self as From<(&Self, Clause)>>::from((self, arg));
($next_phase)(&intermediate)
.map_err($crate::representations::interpreted::RuntimeError::Extern)
},
Err(e) => Err(e)
}
} else { next_self.to_atom_cls() };
// package and return
Ok((next_clause, gas))
}
}
};

View File

@@ -14,34 +14,11 @@ macro_rules! atomic_inert {
($typ:ident) => {
impl $crate::foreign::Atomic for $typ {
$crate::atomic_defaults!{}
fn run_once(&self) -> Result<
$crate::representations::interpreted::Clause,
$crate::representations::interpreted::InternalError
> {
Err($crate::representations::interpreted::InternalError::NonReducible)
}
fn run_n_times(&self, _: usize) -> Result<
(
$crate::representations::interpreted::Clause,
usize
),
$crate::representations::interpreted::RuntimeError
> {
Ok(($crate::representations::interpreted::Clause::P(
$crate::representations::Primitive::Atom(
$crate::foreign::Atom::new(self.clone())
)
), 0))
}
fn run_to_completion(&self) -> Result<
$crate::representations::interpreted::Clause,
$crate::representations::interpreted::RuntimeError
> {
Ok($crate::representations::interpreted::Clause::P(
$crate::representations::Primitive::Atom(
$crate::foreign::Atom::new(self.clone())
)
))
fn run(&self, ctx: $crate::interpreter::Context)
-> $crate::foreign::AtomicResult
{
Ok((self.clone().to_atom_cls(), ctx.gas))
}
}
};

View File

@@ -6,21 +6,23 @@ use super::atomic_impl;
#[macro_export]
macro_rules! atomic_redirect {
($typ:ident) => {
impl AsRef<Clause> for $typ {
impl AsRef<$crate::foreign::RcExpr> for $typ {
fn as_ref(&self) -> &Clause { &self.0 }
}
impl From<(&Self, Clause)> for $typ {
impl From<(&Self, $crate::foreign::RcExpr)> for $typ {
fn from((old, clause): (&Self, Clause)) -> Self {
Self{ 0: clause, ..old.clone() }
}
}
};
($typ:ident, $field:ident) => {
impl AsRef<Clause> for $typ {
fn as_ref(&self) -> &Clause { &self.$field }
impl AsRef<$crate::foreign::RcExpr>
for $typ {
fn as_ref(&self) -> &$crate::foreign::RcExpr { &self.$field }
}
impl From<(&Self, Clause)> for $typ {
fn from((old, $field): (&Self, Clause)) -> Self {
impl From<(&Self, $crate::foreign::RcExpr)>
for $typ {
fn from((old, $field): (&Self, $crate::foreign::RcExpr)) -> Self {
Self{ $field, ..old.clone() }
}
}

View File

@@ -22,19 +22,18 @@ macro_rules! externfn_impl {
impl $crate::foreign::ExternFn for $typ {
fn name(&self) -> &str {stringify!($typ)}
fn apply(&self,
c: $crate::representations::interpreted::Clause
) -> Result<
$crate::representations::interpreted::Clause,
std::rc::Rc<dyn $crate::foreign::ExternError>
> {
match ($next_atomic)(self, c) { // ? casts the result but we want to strictly forward it
Ok(r) => Ok(
arg: $crate::foreign::RcExpr,
ctx: $crate::interpreter::Context
) -> $crate::foreign::XfnResult {
match ($next_atomic)(self, arg) { // ? casts the result but we want to strictly forward it
Ok(r) => Ok((
$crate::representations::interpreted::Clause::P(
$crate::representations::Primitive::Atom(
$crate::foreign::Atom::new(r)
)
)
),
),
ctx.gas.map(|g| g - 1)
)),
Err(e) => Err(e)
}
}

53
src/interner/display.rs Normal file
View File

@@ -0,0 +1,53 @@
use core::fmt::Formatter;
use std::fmt::Display;
use crate::interner::Interner;
/// A variant of [std::fmt::Display] for objects that contain interned
/// strings and therefore can only be stringified in the presence of a
/// string interner
///
/// The functions defined here are suffixed to distinguish them from
/// the ones in Display and ToString respectively, because Rust can't
/// identify functions based on arity
pub trait InternedDisplay {
/// formats the value using the given formatter and string interner
fn fmt_i(&self,
f: &mut std::fmt::Formatter<'_>,
i: &Interner,
) -> std::fmt::Result;
/// Converts the value to a string to be displayed
fn to_string_i(&self, i: &Interner) -> String {
// Copied from <https://doc.rust-lang.org/src/alloc/string.rs.html#2526>
let mut buf = String::new();
let mut formatter = Formatter::new(&mut buf);
// Bypass format_args!() to avoid write_str with zero-length strs
Self::fmt_i(self, &mut formatter, i)
.expect("a Display implementation returned an error unexpectedly");
buf
}
fn bundle<'a>(&'a self, interner: &'a Interner)
-> DisplayBundle<'a, Self>
{
DisplayBundle { interner, data: self }
}
}
impl<T> InternedDisplay for T where T: Display {
fn fmt_i(&self, f: &mut std::fmt::Formatter<'_>, _i: &Interner) -> std::fmt::Result {
<Self as Display>::fmt(&self, f)
}
}
pub struct DisplayBundle<'a, T: InternedDisplay + ?Sized> {
interner: &'a Interner,
data: &'a T
}
impl<'a, T: InternedDisplay> Display for DisplayBundle<'a, T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.data.fmt_i(f, self.interner)
}
}

9
src/interner/mod.rs Normal file
View File

@@ -0,0 +1,9 @@
mod monotype;
mod multitype;
mod token;
mod display;
pub use monotype::TypedInterner;
pub use multitype::Interner;
pub use token::Token;
pub use display::{DisplayBundle, InternedDisplay};

120
src/interner/monotype.rs Normal file
View File

@@ -0,0 +1,120 @@
use std::num::NonZeroU32;
use std::cell::RefCell;
use std::borrow::Borrow;
use std::hash::{Hash, BuildHasher};
use hashbrown::HashMap;
use super::token::Token;
pub struct TypedInterner<T: 'static + Eq + Hash + Clone>{
tokens: RefCell<HashMap<&'static T, Token<T>>>,
values: RefCell<Vec<(&'static T, bool)>>
}
impl<T: Eq + Hash + Clone> TypedInterner<T> {
/// Create a fresh interner instance
pub fn new() -> Self {
Self {
tokens: RefCell::new(HashMap::new()),
values: RefCell::new(Vec::new())
}
}
/// Intern an object, returning a token
pub fn i<Q: ?Sized + Eq + Hash + ToOwned<Owned = T>>(&self, q: &Q)
-> Token<T> where T: Borrow<Q>
{
let mut tokens = self.tokens.borrow_mut();
let hash = compute_hash(tokens.hasher(), q);
let raw_entry = tokens.raw_entry_mut().from_hash(hash, |k| {
<T as Borrow<Q>>::borrow(k) == q
});
let kv = raw_entry.or_insert_with(|| {
let mut values = self.values.borrow_mut();
let uniq_key: NonZeroU32 = (values.len() as u32 + 1u32)
.try_into().expect("can never be zero");
let keybox = Box::new(q.to_owned());
let keyref = Box::leak(keybox);
values.push((keyref, true));
let token = Token::<T>::from_id(uniq_key);
(keyref, token)
});
*kv.1
}
/// Resolve a token, obtaining an object
/// It is illegal to use a token obtained from one interner with another.
pub fn r(&self, t: Token<T>) -> &T {
let values = self.values.borrow();
let key = t.into_usize() - 1;
values[key].0
}
/// Intern a static reference without allocating the data on the heap
#[allow(unused)]
pub fn intern_static(&self, tref: &'static T) -> Token<T> {
let mut tokens = self.tokens.borrow_mut();
let token = *tokens.raw_entry_mut().from_key(tref)
.or_insert_with(|| {
let mut values = self.values.borrow_mut();
let uniq_key: NonZeroU32 = (values.len() as u32 + 1u32)
.try_into().expect("can never be zero");
values.push((tref, false));
let token = Token::<T>::from_id(uniq_key);
(tref, token)
}).1;
token
}
}
// impl<T: Eq + Hash + Clone> TypedInterner<Vec<T>> {
// pub fn iv<Q>(&self, qs: &[Q]) -> Token<Vec<T>>
// where
// Q: Eq + Hash + ToOwned<Owned = T>,
// T: Borrow<Q>
// {
// let mut tokens = self.tokens.borrow_mut();
// let hash = compute_hash(tokens.hasher(), qs);
// let raw_entry = tokens.raw_entry_mut().from_hash(hash, |k| {
// k.iter().zip(qs.iter()).all(|(t, q)| t.borrow() == q)
// });
// let kv = raw_entry.or_insert_with(|| {
// let mut values = self.values.borrow_mut();
// let uniq_key: NonZeroU32 = (values.len() as u32 + 1u32)
// .try_into().expect("can never be zero");
// let tv = qs.iter().map(Q::to_owned).collect::<Vec<_>>();
// let keybox = Box::new(tv);
// let keyref = Box::leak(keybox);
// values.push((keyref, true));
// let token = Token::<Vec<T>>::from_id(uniq_key);
// (keyref, token)
// });
// *kv.1
// }
// }
impl<T: Eq + Hash + Clone> Drop for TypedInterner<T> {
fn drop(&mut self) {
// make sure all values leaked by us are dropped
// FIXME: with the new hashmap logic we can actually store Rc-s
// which negates the need for unsafe here
let mut values = self.values.borrow_mut();
for (item, owned) in values.drain(..) {
if !owned {continue}
unsafe {
Box::from_raw((item as *const T).cast_mut())
};
}
}
}
/// Helper function to compute hashes outside a hashmap
fn compute_hash(
hash_builder: &impl BuildHasher,
key: &(impl Hash + ?Sized)
) -> u64 {
use core::hash::Hasher;
let mut state = hash_builder.build_hasher();
key.hash(&mut state);
state.finish()
}

102
src/interner/multitype.rs Normal file
View File

@@ -0,0 +1,102 @@
use std::borrow::Borrow;
use std::cell::{RefCell, RefMut};
use std::any::{TypeId, Any};
use std::hash::Hash;
use std::rc::Rc;
use hashbrown::HashMap;
use super::monotype::TypedInterner;
use super::token::Token;
pub struct Interner {
interners: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
}
impl Interner {
pub fn new() -> Self {
Self { interners: RefCell::new(HashMap::new()) }
}
pub fn i<Q: ?Sized>(&self, q: &Q) -> Token<Q::Owned>
where Q: Eq + Hash + ToOwned,
Q::Owned: 'static + Eq + Hash + Clone,
Q::Owned: Borrow<Q>
{
let mut interners = self.interners.borrow_mut();
let interner = get_interner(&mut interners);
interner.i(q)
}
pub fn r<T: 'static + Eq + Hash + Clone>(&self, t: Token<T>) -> &T {
let mut interners = self.interners.borrow_mut();
let interner = get_interner(&mut interners);
// TODO: figure this out
unsafe{ (interner.r(t) as *const T).as_ref().unwrap() }
}
/// Fully resolve
/// TODO: make this generic over containers
pub fn extern_vec<T: 'static + Eq + Hash + Clone>(&self,
t: Token<Vec<Token<T>>>
) -> Vec<T> {
let mut interners = self.interners.borrow_mut();
let v_int = get_interner(&mut interners);
let t_int = get_interner(&mut interners);
let v = v_int.r(t);
v.iter()
.map(|t| t_int.r(*t))
.cloned()
.collect()
}
pub fn extern_all<T: 'static + Eq + Hash + Clone>(&self,
s: &[Token<T>]
) -> Vec<T> {
s.iter()
.map(|t| self.r(*t))
.cloned()
.collect()
}
}
/// Get or create an interner for a given type.
fn get_interner<T: 'static + Eq + Hash + Clone>(
interners: &mut RefMut<HashMap<TypeId, Rc<dyn Any>>>
) -> Rc<TypedInterner<T>> {
let boxed = interners.raw_entry_mut().from_key(&TypeId::of::<T>())
.or_insert_with(|| (
TypeId::of::<T>(),
Rc::new(TypedInterner::<T>::new())
)).1.clone();
boxed.downcast().expect("the typeid is supposed to protect from this")
}
#[cfg(test)]
mod test {
use super::*;
#[test]
pub fn test_string() {
let interner = Interner::new();
let key1 = interner.i("foo");
let key2 = interner.i(&"foo".to_string());
assert_eq!(key1, key2)
}
#[test]
pub fn test_slice() {
let interner = Interner::new();
let key1 = interner.i(&vec![1, 2, 3]);
let key2 = interner.i(&[1, 2, 3][..]);
assert_eq!(key1, key2);
}
// #[test]
#[allow(unused)]
pub fn test_str_slice() {
let interner = Interner::new();
let key1 = interner.i(&vec!["a".to_string(), "b".to_string(), "c".to_string()]);
let key2 = interner.i(&["a", "b", "c"][..]);
// assert_eq!(key1, key2);
}
}

57
src/interner/token.rs Normal file
View File

@@ -0,0 +1,57 @@
use std::{num::NonZeroU32, marker::PhantomData};
use std::fmt::Debug;
use std::hash::Hash;
use std::cmp::PartialEq;
pub struct Token<T>{
id: NonZeroU32,
phantom_data: PhantomData<T>
}
impl<T> Token<T> {
pub fn from_id(id: NonZeroU32) -> Self {
Self { id, phantom_data: PhantomData }
}
pub fn into_id(self) -> NonZeroU32 {
self.id
}
pub fn into_usize(self) -> usize {
let zero: u32 = self.id.into();
zero as usize
}
}
impl<T> Debug for Token<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Token({})", self.id)
}
}
impl<T> Copy for Token<T> {}
impl<T> Clone for Token<T> {
fn clone(&self) -> Self {
Self{ id: self.id, phantom_data: PhantomData }
}
}
impl<T> Eq for Token<T> {}
impl<T> PartialEq for Token<T> {
fn eq(&self, other: &Self) -> bool { self.id == other.id }
}
impl<T> Ord for Token<T> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.id.cmp(&other.id)
}
}
impl<T> PartialOrd for Token<T> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(&other))
}
}
impl<T> Hash for Token<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_u32(self.id.into())
}
}

104
src/interpreter/apply.rs Normal file
View File

@@ -0,0 +1,104 @@
use crate::foreign::Atom;
use crate::representations::Primitive;
use crate::representations::PathSet;
use crate::representations::interpreted::{ExprInst, Clause};
use crate::utils::Side;
use super::Return;
use super::error::RuntimeError;
use super::context::Context;
/// Process the clause at the end of the provided path.
/// Note that paths always point to at least one target.
/// Note also that this is not cached as a normalization step in the
/// intermediate expressions.
fn map_at<E>(
path: &[Side], source: ExprInst,
mapper: &mut impl FnMut(&Clause) -> Result<Clause, E>
) -> Result<ExprInst, E> {
source.try_update(|value| {
// Pass right through lambdas
if let Clause::Lambda { args, body } = value {
return Ok(Clause::Lambda {
args: args.clone(),
body: map_at(path, body.clone(), mapper)?
})
}
// If the path ends here, process the next (non-lambda) node
let (head, tail) = if let Some(sf) = path.split_first() {sf} else {
return Ok(mapper(value)?)
};
// If it's an Apply, execute the next step in the path
if let Clause::Apply { f, x } = value {
return Ok(match head {
Side::Left => Clause::Apply {
f: map_at(tail, f.clone(), mapper)?,
x: x.clone(),
},
Side::Right => Clause::Apply {
f: f.clone(),
x: map_at(tail, x.clone(), mapper)?,
}
})
}
panic!("Invalid path")
})
}
fn substitute(paths: &PathSet, value: Clause, body: ExprInst) -> ExprInst {
let PathSet{ steps, next } = paths;
map_at(&steps, body, &mut |checkpoint| -> Result<Clause, !> {
match (checkpoint, next) {
(Clause::Lambda{..}, _) => unreachable!("Handled by map_at"),
(Clause::Apply { f, x }, Some((left, right))) => Ok(Clause::Apply {
f: substitute(&left, value.clone(), f.clone()),
x: substitute(&right, value.clone(), x.clone()),
}),
(Clause::LambdaArg, None) => Ok(value.clone()),
(_, None) => panic!("Substitution path ends in something other than LambdaArg"),
(_, Some(_)) => panic!("Substitution path leads into something other than Apply"),
}
}).into_ok()
}
/// Apply a function-like expression to a parameter.
/// If any work is being done, gas will be deducted.
pub fn apply(
f: ExprInst, x: ExprInst, mut ctx: Context
) -> Result<Return, RuntimeError> {
let state = f.clone().try_update(|clause| match clause {
// apply an ExternFn or an internal function
Clause::P(Primitive::ExternFn(f)) => {
let (clause, gas) = f.apply(x, ctx.clone())
.map_err(|e| RuntimeError::Extern(e))?;
ctx.gas = gas.map(|g| g - 1); // cost of extern call
Ok(clause)
}
Clause::Lambda{args, body} => Ok(if let Some(args) = args {
let x_cls = x.expr().clause.clone();
let new_xpr_inst = substitute(args, x_cls, body.clone());
let new_xpr = new_xpr_inst.expr();
// cost of substitution
// XXX: should this be the number of occurrences instead?
ctx.gas = ctx.gas.map(|x| x - 1);
new_xpr.clause.clone()
} else {body.expr().clause.clone()}),
Clause::Constant(name) => {
let symval = ctx.symbols.get(name).expect("missing symbol for function").clone();
ctx.gas = ctx.gas.map(|x| x - 1); // cost of lookup
Ok(Clause::Apply { f: symval, x, })
}
Clause::P(Primitive::Atom(Atom(atom))) => { // take a step in expanding atom
let (clause, gas) = atom.run(ctx.clone())?;
ctx.gas = gas.map(|x| x - 1); // cost of dispatch
Ok(Clause::Apply { f: clause.wrap(), x })
},
Clause::Apply{ f: fun, x: arg } => { // take a step in resolving pre-function
let res = apply(fun.clone(), arg.clone(), ctx.clone())?;
ctx.gas = res.gas; // if work has been done, it has been paid
Ok(Clause::Apply{ f: res.state, x })
},
_ => Err(RuntimeError::NonFunctionApplication(f.clone()))
})?;
Ok(Return { state, gas: ctx.gas })
}

View File

@@ -0,0 +1,27 @@
use hashbrown::HashMap;
use crate::representations::interpreted::ExprInst;
use crate::interner::Token;
#[derive(Clone)]
pub struct Context<'a> {
pub symbols: &'a HashMap<Token<Vec<Token<String>>>, ExprInst>,
pub gas: Option<usize>,
}
impl Context<'_> {
pub fn is_stuck(&self, res: Option<usize>) -> bool {
match (res, self.gas) {
(Some(a), Some(b)) => a == b,
(None, None) => false,
(None, Some(_)) => panic!("gas not tracked despite limit"),
(Some(_), None) => panic!("gas tracked without request"),
}
}
}
#[derive(Clone)]
pub struct Return {
pub state: ExprInst,
pub gas: Option<usize>,
}

27
src/interpreter/error.rs Normal file
View File

@@ -0,0 +1,27 @@
use std::fmt::Display;
use std::rc::Rc;
use crate::representations::interpreted::ExprInst;
use crate::foreign::ExternError;
/// Problems in the process of execution
#[derive(Clone)]
pub enum RuntimeError {
Extern(Rc<dyn ExternError>),
NonFunctionApplication(ExprInst),
}
impl From<Rc<dyn ExternError>> for RuntimeError {
fn from(value: Rc<dyn ExternError>) -> Self {
Self::Extern(value)
}
}
impl Display for RuntimeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Extern(e) => write!(f, "Error in external function: {e}"),
Self::NonFunctionApplication(loc) => write!(f, "Primitive applied as function at {loc:?}")
}
}
}

8
src/interpreter/mod.rs Normal file
View File

@@ -0,0 +1,8 @@
mod apply;
mod error;
mod context;
mod run;
pub use context::{Context, Return};
pub use error::RuntimeError;
pub use run::{run};

39
src/interpreter/run.rs Normal file
View File

@@ -0,0 +1,39 @@
use crate::foreign::Atom;
use crate::representations::Primitive;
use crate::representations::interpreted::{Clause, ExprInst};
use super::apply::apply;
use super::error::RuntimeError;
use super::context::{Context, Return};
pub fn run(expr: ExprInst, mut ctx: Context)
-> Result<Return, RuntimeError>
{
let state = expr.try_normalize(|cls| -> Result<Clause, RuntimeError> {
let mut i = cls.clone();
while ctx.gas.map(|g| g > 0).unwrap_or(true) {
match &i {
Clause::Apply { f, x } => {
let res = apply(f.clone(), x.clone(), ctx.clone())?;
if ctx.is_stuck(res.gas) {return Ok(i)}
ctx.gas = res.gas;
i = res.state.expr().clause.clone();
}
Clause::P(Primitive::Atom(Atom(data))) => {
let (clause, gas) = data.run(ctx.clone())?;
if ctx.is_stuck(gas) {return Ok(i)}
ctx.gas = gas;
i = clause.clone();
}
Clause::Constant(c) => {
let symval = ctx.symbols.get(c).expect("missing symbol for value");
ctx.gas = ctx.gas.map(|g| g - 1); // cost of lookup
i = symval.expr().clause.clone();
}
_ => return Ok(i)
}
}
Ok(i)
})?;
Ok(Return { state, gas: ctx.gas })
}

View File

@@ -1,6 +1,3 @@
#![feature(specialization)]
#![feature(adt_const_params)]
#![feature(generic_const_exprs)]
#![feature(generators, generator_trait)]
#![feature(never_type)]
#![feature(unwrap_infallible)]
@@ -8,124 +5,51 @@
#![feature(hasher_prefixfree_extras)]
#![feature(closure_lifetime_binder)]
#![feature(generic_arg_infer)]
use std::{env::current_dir, collections::HashMap};
#![feature(array_chunks)]
#![feature(fmt_internals)]
#![feature(map_try_insert)]
#![feature(slice_group_by)]
#![feature(trait_alias)]
// mod executor;
mod parse;
pub(crate) mod project;
mod interner;
mod interpreter;
mod utils;
mod representations;
mod rule;
mod scheduler;
pub(crate) mod foreign;
mod external;
mod foreign_macros;
use lasso::Rodeo;
mod pipeline;
mod run_dir;
mod cli;
use std::{path::PathBuf, fs::File};
use clap::Parser;
use cli::prompt;
pub use representations::ast;
use ast::{Expr, Clause};
// use representations::typed as t;
use mappable_rc::Mrc;
use project::{rule_collector, file_loader};
use rule::Repository;
use utils::to_mrc_slice;
use run_dir::run_dir;
use crate::external::std::std;
use crate::project::{map_loader, string_loader, Loader, ModuleError};
use crate::representations::{ast_to_postmacro, postmacro_to_interpreted};
fn literal(orig: &[&str]) -> Mrc<[String]> {
to_mrc_slice(vliteral(orig))
}
fn vliteral(orig: &[&str]) -> Vec<String> {
orig.iter().map(|&s| s.to_owned()).collect()
}
static PRELUDE:&str = r#"
import std::(
num::(add, subtract, multiply, remainder, divide),
bool::(equals, ifthenelse),
str::concatenate
)
export (...$a + ...$b) =1001=> (add (...$a) (...$b))
export (...$a - ...$b:1) =1001=> (subtract (...$a) (...$b))
export (...$a * ...$b) =1000=> (multiply (...$a) (...$b))
export (...$a % ...$b:1) =1000=> (remainder (...$a) (...$b))
export (...$a / ...$b:1) =1000=> (divide (...$a) (...$b))
export (...$a == ...$b) =1002=> (equals (...$a) (...$b))
export (...$a ++ ...$b) =1003=> (concatenate (...$a) (...$b))
export do { ...$statement ; ...$rest:1 } =10_001=> (
statement (...$statement) do { ...$rest }
)
export do { ...$return } =10_000=> (...$return)
export statement (let $_name = ...$value) ...$next =10_000=> (
(\$_name. ...$next) (...$value)
)
export statement (cps $_name = ...$operation) ...$next =10_001=> (
(...$operation) \$_name. ...$next
)
export statement (cps ...$operation) ...$next =10_000=> (
(...$operation) (...$next)
)
export if ...$cond then ...$true else ...$false:1 =5_000=> (
ifthenelse (...$cond) (...$true) (...$false)
)
"#;
fn initial_tree() -> Mrc<[Expr]> {
to_mrc_slice(vec![Expr(Clause::Name {
local: None,
qualified: literal(&["mod", "main", "main"])
}, to_mrc_slice(vec![]))])
}
#[allow(unused)]
fn load_project() {
let mut rodeo = Rodeo::default();
let collect_rules = rule_collector(
rodeo,
map_loader(HashMap::from([
("std", std().boxed()),
("prelude", string_loader(PRELUDE).boxed()),
("mod", file_loader(current_dir().expect("Missing CWD!")).boxed())
]))
);
let rules = match collect_rules.try_find(&literal(&["mod", "main"])) {
Ok(rules) => rules,
Err(err) => if let ModuleError::Syntax(pe) = err {
panic!("{}", pe);
} else {panic!("{:#?}", err)}
};
let mut tree = initial_tree();
println!("Start processing {tree:?}");
let repo = Repository::new(rules.as_ref().to_owned());
println!("Ruleset: {repo:?}");
xloop!(let mut i = 0; i < 100; i += 1; {
match repo.step(Mrc::clone(&tree)) {
Ok(Some(phase)) => {
//println!("Step {i}: {phase:?}");
tree = phase;
},
Ok(None) => {
println!("Execution complete");
break
},
Err(e) => panic!("Rule error: {e:?}")
}
}; panic!("Macro execution didn't halt"));
let pmtree = ast_to_postmacro::exprv(tree.as_ref())
.unwrap_or_else(|e| panic!("Postmacro conversion error: {e}"));
let runtree = postmacro_to_interpreted::expr_rec(&pmtree)
.unwrap_or_else(|e| panic!("Interpreted conversion error: {e}"));
let stable = runtree.run_to_completion()
.unwrap_or_else(|e| panic!("Runtime error {e}"));
println!("Settled at {stable:?}")
/// Orchid interpreter
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Folder containing main.orc
#[arg(short, long)]
pub project: Option<String>
}
fn main() {
load_project();
let args = Args::parse();
let path = args.project.unwrap_or_else(|| {
prompt("Enter a project root", ".".to_string(), |p| {
let mut path: PathBuf = p.trim().into();
path.push("main.orc");
match File::open(&path) {
Ok(_) => Ok(p),
Err(e) => Err(format!("{}: {e}", path.display()))
}
})
});
run_dir(&PathBuf::try_from(path).unwrap());
}

48
src/parse/context.rs Normal file
View File

@@ -0,0 +1,48 @@
use std::rc::Rc;
use crate::interner::Interner;
/// Trait enclosing all context features
///
/// Hiding type parameters in associated types allows for simpler
/// parser definitions
pub trait Context: Clone {
type Op: AsRef<str>;
fn ops<'a>(&'a self) -> &'a [Self::Op];
fn file(&self) -> Rc<Vec<String>>;
fn interner<'a>(&'a self) -> &'a Interner;
}
/// Struct implementing context
///
/// Hiding type parameters in associated types allows for simpler
/// parser definitions
pub struct ParsingContext<'a, Op> {
pub ops: &'a [Op],
pub interner: &'a Interner,
pub file: Rc<Vec<String>>
}
impl<'a, Op> ParsingContext<'a, Op> {
pub fn new(ops: &'a [Op], interner: &'a Interner, file: Rc<Vec<String>>)
-> Self { Self { ops, interner, file } }
}
impl<'a, Op> Clone for ParsingContext<'a, Op> {
fn clone(&self) -> Self {
Self {
ops: self.ops,
interner: self.interner,
file: self.file.clone()
}
}
}
impl<Op: AsRef<str>> Context for ParsingContext<'_, Op> {
type Op = Op;
fn interner<'a>(&'a self) -> &'a Interner { self.interner }
fn file(&self) -> Rc<Vec<String>> {self.file.clone()}
fn ops<'a>(&'a self) -> &'a [Self::Op] { self.ops }
}

46
src/parse/enum_filter.rs Normal file
View File

@@ -0,0 +1,46 @@
/// Produces filter_mapping functions for enum types:
/// ```rs
/// enum_parser!(Foo::Bar | "Some error!") // Accepts Foo::Bar(T) into T
/// enum_parser!(Foo::Bar) // same as above but with the default error "Expected Foo::Bar"
/// enum_parser!(Foo >> Quz; Bar, Baz) // Parses Foo::Bar(T) into Quz::Bar(T) and Foo::Baz(U) into Quz::Baz(U)
/// ```
#[macro_export]
macro_rules! enum_filter {
($p:path | $m:tt) => {
{
|l| {
if let $p(x) = l { Ok(x) }
else { Err($m) }
}
}
};
($p:path >> $q:path; $i:ident | $m:tt) => {
{
use $p as srcpath;
use $q as tgtpath;
let base = enum_filter!(srcpath::$i | $m);
move |l| base(l).map(tgtpath::$i)
}
};
($p:path >> $q:path; $i:ident) => {
enum_filter!($p >> $q; $i | {concat!("Expected ", stringify!($i))})
};
($p:path >> $q:path; $($i:ident),+ | $m:tt) => {
{
use $p as srcpath;
use $q as tgtpath;
|l| match l {
$( srcpath::$i(x) => Ok(tgtpath::$i(x)), )+
_ => Err($m)
}
}
};
($p:path >> $q:path; $($i:ident),+) => {
enum_filter!($p >> $q; $($i),+ | {
concat!("Expected one of ", $(stringify!($i), " "),+)
})
};
($p:path) => {
enum_filter!($p | {concat!("Expected ", stringify!($p))})
};
}

View File

@@ -1,32 +0,0 @@
/// Produces parsers for tokenized sequences of enum types:
/// ```rs
/// enum_parser!(Foo::Bar | "Some error!") // Parses Foo::Bar(T) into T
/// enum_parser!(Foo::Bar) // same as above but with the default error "Expected Foo::Bar"
/// enum_parser!(Foo >> Quz; Bar, Baz) // Parses Foo::Bar(T) into Quz::Bar(T) and Foo::Baz(U) into Quz::Baz(U)
/// ```
#[macro_export]
macro_rules! enum_parser {
($p:path | $m:tt) => {
{
::chumsky::prelude::filter_map(|s, l| {
if let $p(x) = l { Ok(x) }
else { Err(::chumsky::prelude::Simple::custom(s, $m))}
})
}
};
($p:path >> $q:path; $i:ident) => {
{
use $p as srcpath;
use $q as tgtpath;
enum_parser!(srcpath::$i | (concat!("Expected ", stringify!($i)))).map(tgtpath::$i)
}
};
($p:path >> $q:path; $($i:ident),+) => {
{
::chumsky::prelude::choice((
$( enum_parser!($p >> $q; $i) ),+
))
}
};
($p:path) => { enum_parser!($p | (concat!("Expected ", stringify!($p)))) };
}

View File

@@ -1,155 +1,107 @@
use std::ops::Range;
use std::rc::Rc;
use chumsky::{self, prelude::*, Parser};
use lasso::Spur;
use crate::enum_parser;
use crate::representations::Primitive;
use crate::representations::{Literal, ast::{Clause, Expr}};
use super::lexer::Lexeme;
use crate::enum_filter;
use crate::representations::Primitive;
use crate::representations::ast::{Clause, Expr};
use crate::representations::location::Location;
use crate::interner::Token;
use super::context::Context;
use super::lexer::{Lexeme, Entry, filter_map_lex};
/// Parses any number of expr wrapped in (), [] or {}
fn sexpr_parser<P>(
expr: P
) -> impl Parser<Lexeme, Clause, Error = Simple<Lexeme>> + Clone
where P: Parser<Lexeme, Expr, Error = Simple<Lexeme>> + Clone {
Lexeme::paren_parser(expr.repeated())
.map(|(del, b)| Clause::S(del, Rc::new(b)))
fn sexpr_parser(
expr: impl Parser<Entry, Expr, Error = Simple<Entry>> + Clone
) -> impl Parser<Entry, (Clause, Range<usize>), Error = Simple<Entry>> + Clone {
let body = expr.repeated();
choice((
Lexeme::LP('(').parser().then(body.clone())
.then(Lexeme::RP('(').parser()),
Lexeme::LP('[').parser().then(body.clone())
.then(Lexeme::RP('[').parser()),
Lexeme::LP('{').parser().then(body.clone())
.then(Lexeme::RP('{').parser()),
)).map(|((lp, body), rp)| {
let Entry{lexeme, range: Range{start, ..}} = lp;
let end = rp.range.end;
let char = if let Lexeme::LP(c) = lexeme {c}
else {unreachable!("The parser only matches Lexeme::LP")};
(Clause::S(char, Rc::new(body)), start..end)
}).labelled("S-expression")
}
/// Parses `\name.body` or `\name:type.body` where name is any valid name
/// and type and body are both expressions. Comments are allowed
/// and ignored everywhere in between the tokens
fn lambda_parser<'a, P, F>(
expr: P, intern: &'a F
) -> impl Parser<Lexeme, Clause, Error = Simple<Lexeme>> + Clone + 'a
where
P: Parser<Lexeme, Expr, Error = Simple<Lexeme>> + Clone + 'a,
F: Fn(&str) -> Spur + 'a {
just(Lexeme::BS)
.then_ignore(enum_parser!(Lexeme::Comment).repeated())
.ignore_then(namelike_parser(intern))
.then_ignore(enum_parser!(Lexeme::Comment).repeated())
.then(
just(Lexeme::Type)
.then_ignore(enum_parser!(Lexeme::Comment).repeated())
.ignore_then(expr.clone().repeated())
.then_ignore(enum_parser!(Lexeme::Comment).repeated())
.or_not().map(Option::unwrap_or_default)
)
.then_ignore(just(Lexeme::name(".")))
.then_ignore(enum_parser!(Lexeme::Comment).repeated())
fn lambda_parser<'a>(
expr: impl Parser<Entry, Expr, Error = Simple<Entry>> + Clone + 'a,
ctx: impl Context + 'a
) -> impl Parser<Entry, (Clause, Range<usize>), Error = Simple<Entry>> + Clone + 'a {
Lexeme::BS.parser()
.ignore_then(expr.clone())
.then_ignore(Lexeme::Name(ctx.interner().i(".")).parser())
.then(expr.repeated().at_least(1))
.map(|((name, typ), body): ((Clause, Vec<Expr>), Vec<Expr>)| {
Clause::Lambda(Rc::new(name), Rc::new(typ), Rc::new(body))
})
}
/// see [lambda_parser] but `@` instead of `\` and the name is optional
fn auto_parser<'a, P, F>(
expr: P, intern: &'a F
) -> impl Parser<Lexeme, Clause, Error = Simple<Lexeme>> + Clone + 'a
where
P: Parser<Lexeme, Expr, Error = Simple<Lexeme>> + Clone + 'a,
F: Fn(&str) -> Spur + 'a {
just(Lexeme::At)
.then_ignore(enum_parser!(Lexeme::Comment).repeated())
.ignore_then(namelike_parser(intern).or_not())
.then_ignore(enum_parser!(Lexeme::Comment).repeated())
.then(
just(Lexeme::Type)
.then_ignore(enum_parser!(Lexeme::Comment).repeated())
.ignore_then(expr.clone().repeated())
.then_ignore(enum_parser!(Lexeme::Comment).repeated())
.or_not().map(Option::unwrap_or_default)
)
.then_ignore(just(Lexeme::name(".")))
.then_ignore(enum_parser!(Lexeme::Comment).repeated())
.then(expr.repeated().at_least(1))
.try_map(|((name, typ), body): ((Option<Clause>, Vec<Expr>), Vec<Expr>), s| {
if name.is_none() && typ.is_empty() {
Err(Simple::custom(s, "Auto without name or type has no effect"))
} else {
Ok(Clause::Auto(name.map(Rc::new), Rc::new(typ), Rc::new(body)))
}
})
.map_with_span(move |(arg, body), span| {
(Clause::Lambda(Rc::new(arg), Rc::new(body)), span)
}).labelled("Lambda")
}
/// Parses a sequence of names separated by :: <br/>
/// Comments are allowed and ignored in between
pub fn ns_name_parser<'a, F>(intern: &'a F)
-> impl Parser<Lexeme, Vec<Spur>, Error = Simple<Lexeme>> + Clone + 'a
where F: Fn(&str) -> Spur + 'a {
enum_parser!(Lexeme::Name)
.map(|s| intern(&s))
.separated_by(
enum_parser!(Lexeme::Comment).repeated()
.then(just(Lexeme::NS))
.then(enum_parser!(Lexeme::Comment).repeated())
).at_least(1)
/// Comments and line breaks are allowed and ignored in between
pub fn ns_name_parser<'a>(ctx: impl Context + 'a)
-> impl Parser<Entry, (Token<Vec<Token<String>>>, Range<usize>), Error = Simple<Entry>> + Clone + 'a
{
filter_map_lex(enum_filter!(Lexeme::Name))
.separated_by(Lexeme::NS.parser()).at_least(1)
.map(move |elements| {
let start = elements.first().expect("can never be empty").1.start;
let end = elements.last().expect("can never be empty").1.end;
let tokens =
/*ctx.prefix().iter().copied().chain*/(
elements.iter().map(|(t, _)| *t)
).collect::<Vec<_>>();
(ctx.interner().i(&tokens), start..end)
}).labelled("Namespaced name")
}
/// Parse any legal argument name starting with a `$`
fn placeholder_parser() -> impl Parser<Lexeme, String, Error = Simple<Lexeme>> + Clone {
enum_parser!(Lexeme::Name).try_map(|name, span| {
name.strip_prefix('$').map(&str::to_string)
.ok_or_else(|| Simple::custom(span, "Not a placeholder"))
})
}
pub fn namelike_parser<'a, F>(intern: &'a F)
-> impl Parser<Lexeme, Clause, Error = Simple<Lexeme>> + Clone + 'a
where F: Fn(&str) -> Spur + 'a {
pub fn namelike_parser<'a>(ctx: impl Context + 'a)
-> impl Parser<Entry, (Clause, Range<usize>), Error = Simple<Entry>> + Clone + 'a
{
choice((
just(Lexeme::name("...")).to(true)
.or(just(Lexeme::name("..")).to(false))
.then(placeholder_parser())
.then(
just(Lexeme::Type)
.ignore_then(enum_parser!(Lexeme::Uint))
.or_not().map(Option::unwrap_or_default)
)
.map(|((nonzero, key), prio)| Clause::Placeh{key, vec: Some((
prio.try_into().unwrap(),
nonzero
))}),
ns_name_parser(intern)
.map(|qualified| Clause::Name(Rc::new(qualified))),
filter_map_lex(enum_filter!(Lexeme::PH))
.map(|(ph, range)| (Clause::Placeh(ph), range)),
ns_name_parser(ctx)
.map(|(token, range)| (Clause::Name(token), range)),
))
}
pub fn clause_parser<'a, P, F>(
expr: P, intern: &'a F
) -> impl Parser<Lexeme, Clause, Error = Simple<Lexeme>> + Clone + 'a
where
P: Parser<Lexeme, Expr, Error = Simple<Lexeme>> + Clone + 'a,
F: Fn(&str) -> Spur + 'a {
enum_parser!(Lexeme::Comment).repeated()
.ignore_then(choice((
enum_parser!(Lexeme >> Literal; Uint, Num, Char, Str)
.map(Primitive::Literal).map(Clause::P),
placeholder_parser().map(|key| Clause::Placeh{key, vec: None}),
namelike_parser(intern),
pub fn clause_parser<'a>(
expr: impl Parser<Entry, Expr, Error = Simple<Entry>> + Clone + 'a,
ctx: impl Context + 'a
) -> impl Parser<Entry, (Clause, Range<usize>), Error = Simple<Entry>> + Clone + 'a {
choice((
filter_map_lex(enum_filter!(Lexeme >> Primitive; Literal))
.map(|(p, s)| (Clause::P(p), s)).labelled("Literal"),
sexpr_parser(expr.clone()),
lambda_parser(expr.clone(), intern),
auto_parser(expr.clone(), intern),
just(Lexeme::At).ignore_then(expr.clone()).map(|arg| {
Clause::Explicit(Rc::new(arg))
})
))).then_ignore(enum_parser!(Lexeme::Comment).repeated())
lambda_parser(expr.clone(), ctx.clone()),
namelike_parser(ctx),
)).labelled("Clause")
}
/// Parse an expression
pub fn xpr_parser<'a, F>(intern: &'a F)
-> impl Parser<Lexeme, Expr, Error = Simple<Lexeme>> + 'a
where F: Fn(&str) -> Spur + 'a {
recursive(|expr| {
let clause = clause_parser(expr, intern);
clause.clone().then(
just(Lexeme::Type)
.ignore_then(clause.clone())
.repeated()
)
.map(|(val, typ)| Expr(val, Rc::new(typ)))
pub fn xpr_parser<'a>(ctx: impl Context + 'a)
-> impl Parser<Entry, Expr, Error = Simple<Entry>> + 'a
{
recursive(move |expr| {
clause_parser(expr, ctx.clone())
.map(move |(value, range)| {
Expr{
value: value.clone(),
location: Location::Range { file: ctx.file(), range }
}
})
}).labelled("Expression")
}

View File

@@ -1,16 +1,16 @@
use std::rc::Rc;
use chumsky::{Parser, prelude::*};
use itertools::Itertools;
use lasso::Spur;
use crate::representations::sourcefile::Import;
use crate::utils::iter::{box_once, box_flatten, into_boxed_iter, BoxedIterIter};
use crate::{enum_parser, box_chain};
use crate::interner::Token;
use crate::{box_chain, enum_filter};
use super::lexer::Lexeme;
use super::Entry;
use super::context::Context;
use super::lexer::{Lexeme, filter_map_lex};
/// initialize a BoxedIter<BoxedIter<String>> with a single element.
fn init_table(name: Spur) -> BoxedIterIter<'static, Spur> {
fn init_table(name: Token<String>) -> BoxedIterIter<'static, Token<String>> {
// I'm not at all confident that this is a good approach.
box_once(box_once(name))
}
@@ -21,51 +21,54 @@ fn init_table(name: Spur) -> BoxedIterIter<'static, Spur> {
/// preferably contain crossplatform filename-legal characters but the
/// symbols are explicitly allowed to go wild.
/// There's a blacklist in [name]
pub fn import_parser<'a, F>(intern: &'a F)
-> impl Parser<Lexeme, Vec<Import>, Error = Simple<Lexeme>> + 'a
where F: Fn(&str) -> Spur + 'a {
let globstar = intern("*");
pub fn import_parser<'a>(ctx: impl Context + 'a)
-> impl Parser<Entry, Vec<Import>, Error = Simple<Entry>> + 'a
{
// TODO: this algorithm isn't cache friendly and copies a lot
recursive(move |expr:Recursive<Lexeme, BoxedIterIter<Spur>, Simple<Lexeme>>| {
enum_parser!(Lexeme::Name).map(|s| intern(s.as_str()))
.separated_by(just(Lexeme::NS))
.then(
just(Lexeme::NS)
.ignore_then(
choice((
expr.clone()
.separated_by(just(Lexeme::name(",")))
.delimited_by(just(Lexeme::LP('(')), just(Lexeme::RP('(')))
.map(|v| box_flatten(v.into_iter()))
.labelled("import group"),
// Each expr returns a list of imports, flatten into common list
just(Lexeme::name("*")).map(move |_| init_table(globstar))
.labelled("wildcard import"), // Just a *, wrapped
enum_parser!(Lexeme::Name)
.map(|s| init_table(intern(s.as_str())))
.labelled("import terminal") // Just a name, wrapped
))
).or_not()
)
.map(|(name, opt_post): (Vec<Spur>, Option<BoxedIterIter<Spur>>)|
-> BoxedIterIter<Spur> {
if let Some(post) = opt_post {
Box::new(post.map(move |el| {
box_chain!(name.clone().into_iter(), el)
}))
} else {
box_once(into_boxed_iter(name))
}
})
recursive({
let ctx = ctx.clone();
move |expr:Recursive<Entry, BoxedIterIter<Token<String>>, Simple<Entry>>| {
filter_map_lex(enum_filter!(Lexeme::Name)).map(|(t, _)| t)
.separated_by(Lexeme::NS.parser())
.then(
Lexeme::NS.parser()
.ignore_then(
choice((
expr.clone()
.separated_by(Lexeme::Name(ctx.interner().i(",")).parser())
.delimited_by(Lexeme::LP('(').parser(), Lexeme::RP('(').parser())
.map(|v| box_flatten(v.into_iter()))
.labelled("import group"),
// Each expr returns a list of imports, flatten into common list
Lexeme::Name(ctx.interner().i("*")).parser()
.map(move |_| init_table(ctx.interner().i("*")))
.labelled("wildcard import"), // Just a *, wrapped
filter_map_lex(enum_filter!(Lexeme::Name))
.map(|(t, _)| init_table(t))
.labelled("import terminal") // Just a name, wrapped
))
).or_not()
)
.map(|(name, opt_post): (Vec<Token<String>>, Option<BoxedIterIter<Token<String>>>)|
-> BoxedIterIter<Token<String>> {
if let Some(post) = opt_post {
Box::new(post.map(move |el| {
box_chain!(name.clone().into_iter(), el)
}))
} else {
box_once(into_boxed_iter(name))
}
})
}
}).map(move |paths| {
paths.filter_map(|namespaces| {
let mut path = namespaces.collect_vec();
let name = path.pop()?;
Some(Import {
path: Rc::new(path),
path: ctx.interner().i(&path),
name: {
if name == globstar { None }
else { Some(name.to_owned()) }
if name == ctx.interner().i("*") { None }
else { Some(name) }
}
})
}).collect()

View File

@@ -1,53 +1,88 @@
use std::{ops::Range, iter, fmt};
use ordered_float::NotNan;
use chumsky::{Parser, prelude::*};
use std::fmt::Debug;
use crate::{utils::{BoxedIter, iter::{box_once, box_flatten}}, box_chain};
use std::fmt;
use std::ops::Range;
use ordered_float::NotNan;
use chumsky::{Parser, prelude::*, text::keyword, Span};
use crate::ast::{Placeholder, PHClass};
use crate::representations::Literal;
use crate::interner::{Token, InternedDisplay, Interner};
use super::context::Context;
use super::placeholder;
use super::{number, string, name, comment};
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Entry(pub Lexeme, pub Range<usize>);
impl Debug for Entry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.0)
// f.debug_tuple("Entry").field(&self.0).field(&self.1).finish()
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Entry{
pub lexeme: Lexeme,
pub range: Range<usize>
}
impl Entry {
pub fn is_filler(&self) -> bool {
matches!(self.lexeme, Lexeme::Comment(_))
|| matches!(self.lexeme, Lexeme::BR)
}
}
impl InternedDisplay for Entry {
fn fmt_i(&self, f: &mut std::fmt::Formatter<'_>, i: &Interner) -> std::fmt::Result {
self.lexeme.fmt_i(f, i)
}
}
impl From<Entry> for (Lexeme, Range<usize>) {
fn from(ent: Entry) -> Self {
(ent.0, ent.1)
(ent.lexeme, ent.range)
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
impl Span for Entry {
type Context = Lexeme;
type Offset = usize;
fn context(&self) -> Self::Context {self.lexeme.clone()}
fn start(&self) -> Self::Offset {self.range.start()}
fn end(&self) -> Self::Offset {self.range.end()}
fn new(context: Self::Context, range: Range<Self::Offset>) -> Self {
Self{
lexeme: context,
range
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Lexeme {
Num(NotNan<f64>),
Uint(u64),
Char(char),
Str(String),
Name(String),
Literal(Literal),
Name(Token<String>),
Rule(NotNan<f64>),
NS, // namespace separator
/// Walrus operator (formerly shorthand macro)
Const,
/// Line break
BR,
/// Namespace separator
NS,
/// Left paren
LP(char),
/// Right paren
RP(char),
BS, // Backslash
/// Backslash
BS,
At,
Type, // type operator
Comment(String),
Export,
Import,
Namespace,
PH(Placeholder)
}
impl Debug for Lexeme {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl InternedDisplay for Lexeme {
fn fmt_i(&self, f: &mut std::fmt::Formatter<'_>, i: &Interner) -> std::fmt::Result {
match self {
Self::Num(n) => write!(f, "{}", n),
Self::Uint(i) => write!(f, "{}", i),
Self::Char(c) => write!(f, "{:?}", c),
Self::Str(s) => write!(f, "{:?}", s),
Self::Name(name) => write!(f, "{}", name),
Self::Literal(l) => write!(f, "{:?}", l),
Self::Name(token) => write!(f, "{}", i.r(*token)),
Self::Const => write!(f, ":="),
Self::Rule(prio) => write!(f, "={}=>", prio),
Self::NS => write!(f, "::"),
Self::LP(l) => write!(f, "{}", l),
@@ -57,102 +92,114 @@ impl Debug for Lexeme {
'{' => write!(f, "}}"),
_ => f.debug_tuple("RP").field(l).finish()
},
Self::BR => write!(f, "\n"),
Self::BS => write!(f, "\\"),
Self::At => write!(f, "@"),
Self::Type => write!(f, ":"),
Self::Comment(text) => write!(f, "--[{}]--", text),
Self::Export => write!(f, "export"),
Self::Import => write!(f, "import"),
Self::Namespace => write!(f, "namespace"),
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, "..")}?;
write!(f, "${}", i.r(*name))?;
if prio != 0 {write!(f, ":{}", prio)?;};
Ok(())
}
}
}
}
}
impl Lexeme {
pub fn name<T: ToString>(n: T) -> Self {
Lexeme::Name(n.to_string())
pub fn rule(prio: impl Into<f64>) -> Self {
Lexeme::Rule(
NotNan::new(prio.into())
.expect("Rule priority cannot be NaN")
)
}
pub fn rule<T>(prio: T) -> Self where T: Into<f64> {
Lexeme::Rule(NotNan::new(prio.into()).expect("Rule priority cannot be NaN"))
}
pub fn paren_parser<T, P>(
expr: P
) -> impl Parser<Lexeme, (char, T), Error = Simple<Lexeme>> + Clone
where P: Parser<Lexeme, T, Error = Simple<Lexeme>> + Clone {
choice((
expr.clone().delimited_by(just(Lexeme::LP('(')), just(Lexeme::RP('(')))
.map(|t| ('(', t)),
expr.clone().delimited_by(just(Lexeme::LP('[')), just(Lexeme::RP('[')))
.map(|t| ('[', t)),
expr.delimited_by(just(Lexeme::LP('{')), just(Lexeme::RP('{')))
.map(|t| ('{', t)),
))
pub fn parser<E: chumsky::Error<Entry>>(self)
-> impl Parser<Entry, Entry, Error = E> + Clone {
filter(move |ent: &Entry| ent.lexeme == self)
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct LexedText(pub Vec<Vec<Entry>>);
pub struct LexedText(pub Vec<Entry>);
impl Debug for LexedText {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for row in &self.0 {
for tok in row {
tok.fmt(f)?;
f.write_str(" ")?
}
f.write_str("\n")?
impl InternedDisplay for LexedText {
fn fmt_i(&self, f: &mut fmt::Formatter<'_>, i: &Interner) -> fmt::Result {
for tok in self.0.iter() {
tok.fmt_i(f, i)?;
f.write_str(" ")?
}
Ok(())
}
}
type LexSubres<'a> = BoxedIter<'a, Entry>;
fn paren_parser<'a>(
expr: Recursive<'a, char, LexSubres<'a>, Simple<char>>,
lp: char, rp: char
) -> impl Parser<char, LexSubres<'a>, Error=Simple<char>> + 'a {
expr.padded().repeated()
.map(|x| box_flatten(x.into_iter()))
.delimited_by(just(lp), just(rp)).map_with_span(move |b, s| {
box_chain!(
iter::once(Entry(Lexeme::LP(lp), s.start..s.start+1)),
b,
iter::once(Entry(Lexeme::RP(lp), s.end-1..s.end))
)
})
fn paren_parser(lp: char, rp: char)
-> impl Parser<char, Lexeme, Error=Simple<char>>
{
just(lp).to(Lexeme::LP(lp))
.or(just(rp).to(Lexeme::RP(lp)))
}
pub fn lexer<'a, T: 'a>(ops: &[T]) -> impl Parser<char, Vec<Entry>, Error=Simple<char>> + 'a
where T: AsRef<str> + Clone {
let all_ops = ops.iter().map(|o| o.as_ref().to_string())
.chain([",", ".", "..", "..."].into_iter().map(str::to_string))
pub fn literal_parser() -> impl Parser<char, Literal, Error = Simple<char>> {
choice((
number::int_parser().map(Literal::Uint), // all ints are valid floats so it takes precedence
number::float_parser().map(Literal::Num),
string::char_parser().map(Literal::Char),
string::str_parser().map(Literal::Str),
))
}
pub static BASE_OPS: &[&str] = &[",", ".", "..", "..."];
pub fn lexer<'a>(ctx: impl Context + 'a)
-> impl Parser<char, Vec<Entry>, Error=Simple<char>> + 'a
{
let all_ops = ctx.ops().iter()
.map(|op| op.as_ref())
.chain(BASE_OPS.iter().cloned())
.map(str::to_string)
.collect::<Vec<_>>();
just("export").padded().to(Lexeme::Export)
.or(just("import").padded().to(Lexeme::Import))
.or_not().then(recursive(move |recurse: Recursive<char, LexSubres, Simple<char>>| {
choice((
paren_parser(recurse.clone(), '(', ')'),
paren_parser(recurse.clone(), '[', ']'),
paren_parser(recurse.clone(), '{', '}'),
choice((
just(":=").padded().to(Lexeme::rule(0f64)),
just("=").ignore_then(number::float_parser()).then_ignore(just("=>")).map(Lexeme::rule),
comment::comment_parser().map(Lexeme::Comment),
just("::").padded().to(Lexeme::NS),
just('\\').padded().to(Lexeme::BS),
just('@').padded().to(Lexeme::At),
just(':').to(Lexeme::Type),
number::int_parser().map(Lexeme::Uint), // all ints are valid floats so it takes precedence
number::float_parser().map(Lexeme::Num),
string::char_parser().map(Lexeme::Char),
string::str_parser().map(Lexeme::Str),
name::name_parser(&all_ops).map(Lexeme::Name), // includes namespacing
)).map_with_span(|lx, span| box_once(Entry(lx, span)) as LexSubres)
))
}).separated_by(one_of("\t ").repeated())
.flatten().collect())
.map(|(prefix, rest): (Option<Lexeme>, Vec<Entry>)| {
prefix.into_iter().map(|l| Entry(l, 0..6)).chain(rest.into_iter()).collect()
})
.then_ignore(text::whitespace()).then_ignore(end())
choice((
keyword("export").to(Lexeme::Export),
keyword("module").to(Lexeme::Namespace),
keyword("import").to(Lexeme::Import),
paren_parser('(', ')'),
paren_parser('[', ']'),
paren_parser('{', '}'),
just(":=").to(Lexeme::Const),
just("=").ignore_then(number::float_parser()).then_ignore(just("=>")).map(Lexeme::rule),
comment::comment_parser().map(Lexeme::Comment),
just("::").to(Lexeme::NS),
just('\\').to(Lexeme::BS),
just('@').to(Lexeme::At),
just(':').to(Lexeme::Type),
just('\n').to(Lexeme::BR),
placeholder::placeholder_parser(ctx.clone()).map(Lexeme::PH),
literal_parser().map(Lexeme::Literal),
name::name_parser(&all_ops).map(move |n| {
Lexeme::Name(ctx.interner().i(&n))
})
))
.map_with_span(|lexeme, range| Entry{ lexeme, range })
.padded_by(one_of(" \t").repeated())
.repeated()
.then_ignore(end())
}
pub fn filter_map_lex<'a, O, M: ToString>(
f: impl Fn(Lexeme) -> Result<O, M> + Clone + 'a
) -> impl Parser<Entry, (O, Range<usize>), Error = Simple<Entry>> + Clone + 'a {
filter_map(move |s: Range<usize>, e: Entry| {
let out = f(e.lexeme).map_err(|msg| Simple::custom(s.clone(), msg))?;
Ok((out, s))
})
}

View File

@@ -6,11 +6,14 @@ mod comment;
mod expression;
mod sourcefile;
mod import;
mod enum_parser;
mod parse;
mod enum_filter;
mod placeholder;
mod context;
pub use sourcefile::line_parser;
pub use lexer::{lexer, Lexeme, Entry as LexerEntry};
pub use lexer::{lexer, Lexeme, Entry};
pub use name::is_op;
pub use parse::{parse, reparse, ParseError};
pub use parse::{parse, ParseError};
pub use number::{float_parser, int_parser};
pub use context::ParsingContext;

View File

@@ -1,58 +1,69 @@
use chumsky::{self, prelude::*, Parser};
/// Matches any one of the passed operators, longest-first
fn op_parser<'a, T: AsRef<str> + Clone>(ops: &[T]) -> BoxedParser<'a, char, String, Simple<char>> {
let mut sorted_ops: Vec<String> = ops.iter().map(|t| t.as_ref().to_string()).collect();
/// Matches any one of the passed operators, preferring longer ones
fn op_parser<'a>(ops: &[impl AsRef<str> + Clone])
-> BoxedParser<'a, char, String, Simple<char>>
{
let mut sorted_ops: Vec<String> = ops.iter()
.map(|t| t.as_ref().to_string()).collect();
sorted_ops.sort_by_key(|op| -(op.len() as i64));
sorted_ops.into_iter()
.map(|op| just(op).boxed())
.reduce(|a, b| a.or(b).boxed())
.unwrap_or_else(|| empty().map(|()| panic!("Empty isn't meant to match")).boxed())
.labelled("operator").boxed()
.unwrap_or_else(|| {
empty().map(|()| panic!("Empty isn't meant to match")).boxed()
}).labelled("operator").boxed()
}
/// Characters that cannot be parsed as part of an operator
///
/// The initial operator list overrides this.
static NOT_NAME_CHAR: &[char] = &[
':', // used for namespacing and type annotations
'\\', '@', // parametric expression starters
'"', '\'', // parsed as primitives and therefore would never match
'(', ')', '[', ']', '{', '}', // must be strictly balanced
'.', // Argument-body separator in parametrics
',', // used in imports
];
/// Matches anything that's allowed as an operator
///
/// Blacklist rationale:
/// - `:` is used for namespacing and type annotations, both are distinguished from operators
/// - `\` and `@` are parametric expression starters
/// - `"` and `'` are read as primitives and would never match.
/// - `(` and `)` are strictly balanced and this must remain the case for automation and streaming.
/// - `.` is the discriminator for parametrics.
/// - ',' is always a standalone single operator, so it can never be part of a name
/// FIXME: `@name` without a dot should be parsed correctly for overrides.
/// Could be an operator but then parametrics should take precedence,
/// which might break stuff. investigate.
///
/// FIXME: `@name` without a dot should be parsed correctly for overrides. Could be an operator but
/// then parametrics should take precedence, which might break stuff. investigate.
/// TODO: `'` could work as an operator whenever it isn't closed.
/// It's common im maths so it's worth a try
///
/// TODO: `'` could work as an operator whenever it isn't closed. It's common im maths so it's
/// worth a try
///
/// TODO: `.` could possibly be parsed as an operator depending on context. This operator is very
/// common in maths so it's worth a try. Investigate.
pub fn modname_parser<'a>() -> impl Parser<char, String, Error = Simple<char>> + 'a {
let not_name_char: Vec<char> = vec![':', '\\', '@', '"', '\'', '(', ')', '[', ']', '{', '}', ',', '.'];
filter(move |c| !not_name_char.contains(c) && !c.is_whitespace())
/// TODO: `.` could possibly be parsed as an operator in some contexts.
/// This operator is very common in maths so it's worth a try.
/// Investigate.
pub fn modname_parser<'a>()
-> impl Parser<char, String, Error = Simple<char>> + 'a
{
filter(move |c| !NOT_NAME_CHAR.contains(c) && !c.is_whitespace())
.repeated().at_least(1)
.collect()
.labelled("modname")
}
/// Parse an operator or name. Failing both, parse everything up to the next whitespace or
/// blacklisted character as a new operator.
pub fn name_parser<'a, T: AsRef<str> + Clone>(
ops: &[T]
) -> impl Parser<char, String, Error = Simple<char>> + 'a {
/// Parse an operator or name. Failing both, parse everything up to
/// the next whitespace or blacklisted character as a new operator.
pub fn name_parser<'a>(ops: &[impl AsRef<str> + Clone])
-> impl Parser<char, String, Error = Simple<char>> + 'a
{
choice((
op_parser(ops), // First try to parse a known operator
text::ident().labelled("plain text"), // Failing that, parse plain text
modname_parser() // Finally parse everything until tne next terminal as a new operator
modname_parser() // Finally parse everything until tne next forbidden char
))
.labelled("name")
}
/// Decide if a string can be an operator. Operators can include digits and text, just not at the
/// start.
pub fn is_op<T: AsRef<str>>(s: T) -> bool {
/// Decide if a string can be an operator. Operators can include digits
/// and text, just not at the start.
pub fn is_op(s: impl AsRef<str>) -> bool {
return match s.as_ref().chars().next() {
Some(x) => !x.is_alphanumeric(),
None => false

View File

@@ -67,7 +67,7 @@ fn pow_uint_parser(base: u32) -> impl Parser<char, u64, Error = Simple<char>> {
/// parse an uint from a base determined by its prefix or lack thereof
///
/// Not to be convused with [uint_parser] which is a component of it.
/// Not to be confused with [uint_parser] which is a component of it.
pub fn int_parser() -> impl Parser<char, u64, Error = Simple<char>> {
choice((
just("0b").ignore_then(pow_uint_parser(2)),

View File

@@ -1,75 +1,58 @@
use std::{ops::Range, fmt::Debug};
use std::fmt::Debug;
use chumsky::{prelude::{Simple, end}, Stream, Parser};
use itertools::Itertools;
use lasso::Spur;
use chumsky::{prelude::*, Parser};
use thiserror::Error;
use crate::{ast::Rule, parse::{lexer::LexedText, sourcefile::split_lines}, representations::sourcefile::FileEntry};
use crate::representations::sourcefile::{FileEntry};
use crate::parse::sourcefile::split_lines;
use super::{Lexeme, lexer, line_parser, LexerEntry};
use super::context::Context;
use super::{lexer, line_parser, Entry};
#[derive(Error, Debug, Clone)]
pub enum ParseError {
#[error("Could not tokenize {0:?}")]
Lex(Vec<Simple<char>>),
#[error("Could not parse {0:#?}")]
Ast(Vec<Simple<Lexeme>>)
#[error("Could not parse {:?} on line {}", .0.first().unwrap().1.span(), .0.first().unwrap().0)]
Ast(Vec<(usize, Simple<Entry>)>)
}
pub fn parse<'a, Op, F>(
ops: &[Op], data: &str, intern: &F
) -> Result<Vec<FileEntry>, ParseError>
where
Op: 'a + AsRef<str> + Clone,
F: Fn(&str) -> Spur
/// All the data required for parsing
/// Parse a string of code into a collection of module elements;
/// imports, exports, comments, declarations, etc.
///
/// Notice that because the lexer splits operators based on the provided
/// list, the output will only be correct if operator list already
/// contains all operators defined or imported by this module.
pub fn parse<'a>(data: &str, ctx: impl Context)
-> Result<Vec<FileEntry>, ParseError>
{
let lexie = lexer(ops);
let token_batchv = split_lines(data).map(|line| {
lexie.parse(line).map_err(ParseError::Lex)
}).collect::<Result<Vec<_>, _>>()?;
println!("Lexed:\n{:?}", LexedText(token_batchv.clone()));
let parsr = line_parser(intern).then_ignore(end());
let (parsed_lines, errors_per_line) = token_batchv.into_iter().filter(|v| {
!v.is_empty()
}).map(|v| {
// Find the first invalid position for Stream::for_iter
let LexerEntry(_, Range{ end, .. }) = v.last().unwrap().clone();
// Stream expects tuples, lexer outputs structs
let tuples = v.into_iter().map_into::<(Lexeme, Range<usize>)>();
parsr.parse(Stream::from_iter(end..end+1, tuples))
// ^^^^^^^^^^
// I haven't the foggiest idea why this is needed, parsers are supposed to be lazy so the
// end of input should make little difference
}).map(|res| match res {
Ok(r) => (Some(r), vec![]),
Err(e) => (None, e)
}).unzip::<_, _, Vec<_>, Vec<_>>();
// TODO: wrap `i`, `ops` and `prefix` in a parsing context
let lexie = lexer(ctx.clone());
let token_batchv = lexie.parse(data).map_err(ParseError::Lex)?;
// println!("Lexed:\n{}", LexedText(token_batchv.clone()).bundle(ctx.interner()));
// println!("Lexed:\n{:?}", token_batchv.clone());
let parsr = line_parser(ctx).then_ignore(end());
let (parsed_lines, errors_per_line) = split_lines(&token_batchv)
.enumerate()
.map(|(i, entv)| (i,
entv.iter()
.filter(|e| !e.is_filler())
.cloned()
.collect::<Vec<_>>()
))
.filter(|(_, l)| l.len() > 0)
.map(|(i, l)| (i, parsr.parse(l)))
.map(|(i, res)| match res {
Ok(r) => (Some(r), (i, vec![])),
Err(e) => (None, (i, e))
}).unzip::<_, _, Vec<_>, Vec<_>>();
let total_err = errors_per_line.into_iter()
.flat_map(Vec::into_iter)
.flat_map(|(i, v)| v.into_iter().map(move |e| (i, e)))
.collect::<Vec<_>>();
if !total_err.is_empty() { Err(ParseError::Ast(total_err)) }
else { Ok(parsed_lines.into_iter().map(Option::unwrap).collect()) }
}
pub fn reparse<'a, Op, F>(
ops: &[Op], data: &str, pre: &[FileEntry], intern: &F
)
-> Result<Vec<FileEntry>, ParseError>
where
Op: 'a + AsRef<str> + Clone,
F: Fn(&str) -> Spur
{
let result = parse(ops, data, intern)?;
Ok(result.into_iter().zip(pre.iter()).map(|(mut output, donor)| {
if let FileEntry::Rule(Rule{source, ..}, _) = &mut output {
if let FileEntry::Rule(Rule{source: s2, ..}, _) = donor {
*source = s2.clone()
} else {
panic!("Preparse and reparse received different row types!")
}
}
output
}).collect())
}

30
src/parse/placeholder.rs Normal file
View File

@@ -0,0 +1,30 @@
use chumsky::{Parser, prelude::*};
use crate::ast::{Placeholder, PHClass};
use super::{number::int_parser, context::Context};
pub fn placeholder_parser<'a>(ctx: impl Context + 'a)
-> impl Parser<char, Placeholder, Error = Simple<char>> + 'a
{
choice((
just("...").to(Some(true)),
just("..").to(Some(false)),
empty().to(None)
))
.then(just("$").ignore_then(text::ident()))
.then(just(":").ignore_then(int_parser()).or_not())
.try_map(move |((vec_nonzero, name), vec_prio), span| {
let name = ctx.interner().i(&name);
if let Some(nonzero) = vec_nonzero {
let prio = vec_prio.unwrap_or_default();
Ok(Placeholder { name, class: PHClass::Vec { nonzero, prio } })
} else {
if vec_prio.is_some() {
Err(Simple::custom(span, "Scalar placeholders have no priority"))
} else {
Ok(Placeholder { name, class: PHClass::Scalar })
}
}
})
}

View File

@@ -1,81 +1,139 @@
use std::iter;
use std::rc::Rc;
use crate::representations::sourcefile::FileEntry;
use crate::enum_parser;
use crate::ast::{Expr, Rule};
use crate::representations::location::Location;
use crate::representations::sourcefile::{FileEntry, Member};
use crate::enum_filter;
use crate::ast::{Rule, Constant, Expr, Clause};
use crate::interner::Token;
use super::expression::{xpr_parser, ns_name_parser};
use super::Entry;
use super::context::Context;
use super::expression::xpr_parser;
use super::import::import_parser;
use super::lexer::Lexeme;
use chumsky::{Parser, prelude::*};
use lasso::Spur;
use ordered_float::NotNan;
use super::lexer::{Lexeme, filter_map_lex};
fn rule_parser<'a, F>(intern: &'a F) -> impl Parser<Lexeme, (
Vec<Expr>, NotNan<f64>, Vec<Expr>
), Error = Simple<Lexeme>> + 'a
where F: Fn(&str) -> Spur + 'a {
xpr_parser(intern).repeated()
.then(enum_parser!(Lexeme::Rule))
.then(xpr_parser(intern).repeated())
.map(|((a, b), c)| (a, b, c))
.labelled("Rule")
use chumsky::{Parser, prelude::*};
use itertools::Itertools;
fn rule_parser<'a>(ctx: impl Context + 'a)
-> impl Parser<Entry, Rule, Error = Simple<Entry>> + 'a
{
xpr_parser(ctx.clone()).repeated().at_least(1)
.then(filter_map_lex(enum_filter!(Lexeme::Rule)))
.then(xpr_parser(ctx).repeated().at_least(1))
.map(|((s, (prio, _)), t)| Rule{
source: Rc::new(s),
prio,
target: Rc::new(t)
}).labelled("Rule")
}
pub fn line_parser<'a, F>(intern: &'a F)
-> impl Parser<Lexeme, FileEntry, Error = Simple<Lexeme>> + 'a
where F: Fn(&str) -> Spur + 'a {
fn const_parser<'a>(ctx: impl Context + 'a)
-> impl Parser<Entry, Constant, Error = Simple<Entry>> + 'a
{
filter_map_lex(enum_filter!(Lexeme::Name))
.then_ignore(Lexeme::Const.parser())
.then(xpr_parser(ctx.clone()).repeated().at_least(1))
.map(move |((name, _), value)| Constant{
name,
value: if let Ok(ex) = value.iter().exactly_one() { ex.clone() }
else {
let start = value.first().expect("value cannot be empty")
.location.range().expect("all locations in parsed source are known")
.start;
let end = value.last().expect("asserted right above")
.location.range().expect("all locations in parsed source are known")
.end;
Expr{
location: Location::Range { file: ctx.file(), range: start..end },
value: Clause::S('(', Rc::new(value))
}
}
})
}
pub fn collect_errors<T, E: chumsky::Error<T>>(e: Vec<E>) -> E {
e.into_iter()
.reduce(chumsky::Error::merge)
.expect("Error list must be non_enmpty")
}
fn namespace_parser<'a>(
line: impl Parser<Entry, FileEntry, Error = Simple<Entry>> + 'a,
) -> impl Parser<Entry, (Token<String>, Vec<FileEntry>), Error = Simple<Entry>> + 'a {
Lexeme::Namespace.parser()
.ignore_then(filter_map_lex(enum_filter!(Lexeme::Name)))
.then(
any().repeated().delimited_by(
Lexeme::LP('{').parser(),
Lexeme::RP('{').parser()
).try_map(move |body, _| {
split_lines(&body)
.map(|l| line.parse(l))
.collect::<Result<Vec<_>,_>>()
.map_err(collect_errors)
})
).map(move |((name, _), body)| {
(name, body)
})
}
fn member_parser<'a>(
line: impl Parser<Entry, FileEntry, Error = Simple<Entry>> + 'a,
ctx: impl Context + 'a
) -> impl Parser<Entry, Member, Error = Simple<Entry>> + 'a {
choice((
// In case the usercode wants to parse doc
enum_parser!(Lexeme >> FileEntry; Comment),
just(Lexeme::Import)
.ignore_then(import_parser(intern).map(FileEntry::Import))
.then_ignore(enum_parser!(Lexeme::Comment).or_not()),
just(Lexeme::Export).map_err_with_span(|e, s| {
println!("{:?} could not yield an export", s); e
}).ignore_then(
just(Lexeme::NS).ignore_then(
ns_name_parser(intern).map(Rc::new)
.separated_by(just(Lexeme::name(",")))
.delimited_by(just(Lexeme::LP('(')), just(Lexeme::RP('(')))
).map(FileEntry::Export)
.or(rule_parser(intern).map(|(source, prio, target)| {
FileEntry::Rule(Rule {
source: Rc::new(source),
prio,
target: Rc::new(target)
}, true)
}))
),
// This could match almost anything so it has to go last
rule_parser(intern).map(|(source, prio, target)| {
FileEntry::Rule(Rule{
source: Rc::new(source),
prio,
target: Rc::new(target)
}, false)
}),
namespace_parser(line)
.map(|(name, body)| Member::Namespace(name, body)),
rule_parser(ctx.clone()).map(Member::Rule),
const_parser(ctx).map(Member::Constant),
))
}
pub fn split_lines(data: &str) -> impl Iterator<Item = &str> {
let mut source = data.char_indices();
pub fn line_parser<'a>(ctx: impl Context + 'a)
-> impl Parser<Entry, FileEntry, Error = Simple<Entry>> + 'a
{
recursive(|line: Recursive<Entry, FileEntry, Simple<Entry>>| {
choice((
// In case the usercode wants to parse doc
filter_map_lex(enum_filter!(Lexeme >> FileEntry; Comment)).map(|(ent, _)| ent),
// plain old imports
Lexeme::Import.parser()
.ignore_then(import_parser(ctx.clone()).map(FileEntry::Import)),
Lexeme::Export.parser().ignore_then(choice((
// token collection
Lexeme::NS.parser().ignore_then(
filter_map_lex(enum_filter!(Lexeme::Name)).map(|(e, _)| e)
.separated_by(Lexeme::Name(ctx.interner().i(",")).parser())
.delimited_by(Lexeme::LP('(').parser(), Lexeme::RP('(').parser())
).map(FileEntry::Export),
// public declaration
member_parser(line.clone(), ctx.clone()).map(FileEntry::Exported)
))),
// This could match almost anything so it has to go last
member_parser(line, ctx).map(FileEntry::Internal),
))
})
}
pub fn split_lines(data: &[Entry]) -> impl Iterator<Item = &[Entry]> {
let mut source = data.iter().enumerate();
let mut last_slice = 0;
iter::from_fn(move || {
let mut paren_count = 0;
while let Some((i, c)) = source.next() {
match c {
'(' | '{' | '[' => paren_count += 1,
')' | '}' | ']' => paren_count -= 1,
'\n' if paren_count == 0 => {
while let Some((i, Entry{ lexeme, .. })) = source.next() {
match lexeme {
Lexeme::LP(_) => paren_count += 1,
Lexeme::RP(_) => paren_count -= 1,
Lexeme::BR if paren_count == 0 => {
let begin = last_slice;
last_slice = i;
last_slice = i+1;
return Some(&data[begin..i]);
},
_ => (),
}
}
None
})
}).filter(|s| s.len() > 0)
}

15
src/pipeline/error/mod.rs Normal file
View File

@@ -0,0 +1,15 @@
mod project_error;
mod parse_error_with_path;
mod unexpected_directory;
mod module_not_found;
mod not_exported;
mod too_many_supers;
mod visibility_mismatch;
pub use project_error::{ErrorPosition, ProjectError};
pub use parse_error_with_path::ParseErrorWithPath;
pub use unexpected_directory::UnexpectedDirectory;
pub use module_not_found::ModuleNotFound;
pub use not_exported::NotExported;
pub use too_many_supers::TooManySupers;
pub use visibility_mismatch::VisibilityMismatch;

View File

@@ -0,0 +1,25 @@
use crate::utils::{BoxedIter, iter::box_once};
use super::{ProjectError, ErrorPosition};
/// Error produced when an import refers to a nonexistent module
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ModuleNotFound {
pub file: Vec<String>,
pub subpath: Vec<String>
}
impl ProjectError for ModuleNotFound {
fn description(&self) -> &str {
"an import refers to a nonexistent module"
}
fn message(&self) -> String {
format!(
"module {} in {} was not found",
self.subpath.join("::"),
self.file.join("/"),
)
}
fn positions(&self) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition::just_file(self.file.clone()))
}
}

View File

@@ -0,0 +1,36 @@
use std::rc::Rc;
use crate::{utils::BoxedIter, representations::location::Location};
use super::{ProjectError, ErrorPosition};
#[derive(Debug)]
pub struct NotExported {
pub file: Vec<String>,
pub subpath: Vec<String>,
pub referrer_file: Vec<String>,
pub referrer_subpath: Vec<String>,
}
impl ProjectError for NotExported {
fn description(&self) -> &str {
"An import refers to a symbol that exists but isn't exported"
}
fn positions(&self) -> BoxedIter<ErrorPosition> {
Box::new([
ErrorPosition{
location: Location::File(Rc::new(self.file.clone())),
message: Some(format!(
"{} isn't exported",
self.subpath.join("::")
)),
},
ErrorPosition{
location: Location::File(Rc::new(self.referrer_file.clone())),
message: Some(format!(
"{} cannot see this symbol",
self.referrer_subpath.join("::")
)),
}
].into_iter())
}
}

View File

@@ -0,0 +1,37 @@
use std::rc::Rc;
use crate::representations::location::Location;
use crate::utils::BoxedIter;
use crate::parse::ParseError;
use super::ErrorPosition;
use super::ProjectError;
/// Produced by stages that parse text when it fails.
#[derive(Debug)]
pub struct ParseErrorWithPath {
pub full_source: String,
pub path: Vec<String>,
pub error: ParseError
}
impl ProjectError for ParseErrorWithPath {
fn description(&self) -> &str {"Failed to parse code"}
fn positions(&self) -> BoxedIter<ErrorPosition> {
match &self.error {
ParseError::Lex(lex) => Box::new(lex.iter().map(|s| ErrorPosition {
location: Location::Range {
file: Rc::new(self.path.clone()),
range: s.span(),
},
message: Some(s.to_string())
})),
ParseError::Ast(ast) => Box::new(ast.iter().map(|(_i, s)| ErrorPosition {
location: s.found().map(|e| Location::Range {
file: Rc::new(self.path.clone()),
range: e.range.clone()
}).unwrap_or_else(|| Location::File(Rc::new(self.path.clone()))),
message: Some(s.label().unwrap_or("Parse error").to_string())
})),
}
}
}

View File

@@ -0,0 +1,50 @@
use std::fmt::{Debug, Display};
use std::rc::Rc;
use crate::representations::location::Location;
use crate::utils::BoxedIter;
/// A point of interest in resolving the error, such as the point where
/// processing got stuck, a command that is likely to be incorrect
pub struct ErrorPosition {
pub location: Location,
pub message: Option<String>
}
impl ErrorPosition {
/// An error position referring to an entire file with no comment
pub fn just_file(file: Vec<String>) -> Self {
Self { message: None, location: Location::File(Rc::new(file)) }
}
}
/// Errors addressed to the developer which are to be resolved with
/// code changes
pub trait ProjectError: Debug {
/// A general description of this type of error
fn description(&self) -> &str;
/// A formatted message that includes specific parameters
fn message(&self) -> String {String::new()}
/// Code positions relevant to this error
fn positions(&self) -> BoxedIter<ErrorPosition>;
/// Convert the error into an [Rc<dyn ProjectError>] to be able to
/// handle various errors together
fn rc(self) -> Rc<dyn ProjectError> where Self: Sized + 'static {
Rc::new(self)
}
}
impl Display for dyn ProjectError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let description = self.description();
let message = self.message();
let positions = self.positions();
write!(f, "Problem with the project: {description}; {message}")?;
for ErrorPosition { location, message } in positions {
write!(f, "@{location}: {}",
message.unwrap_or("location of interest".to_string())
)?
}
Ok(())
}
}

View File

@@ -0,0 +1,38 @@
use std::rc::Rc;
use crate::{utils::{BoxedIter, iter::box_once}, representations::location::Location};
use super::{ProjectError, ErrorPosition};
/// Error produced when an import path starts with more `super` segments
/// than the current module's absolute path
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TooManySupers {
pub path: Vec<String>,
pub offender_file: Vec<String>,
pub offender_mod: Vec<String>
}
impl ProjectError for TooManySupers {
fn description(&self) -> &str {
"an import path starts with more `super` segments than \
the current module's absolute path"
}
fn message(&self) -> String {
format!(
"path {} in {} contains too many `super` steps.",
self.path.join("::"),
self.offender_mod.join("::")
)
}
fn positions(&self) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition {
location: Location::File(Rc::new(self.offender_file.clone())),
message: Some(format!(
"path {} in {} contains too many `super` steps.",
self.path.join("::"),
self.offender_mod.join("::")
))
})
}
}

View File

@@ -0,0 +1,26 @@
use crate::utils::{BoxedIter, iter::box_once};
use super::ErrorPosition;
use super::ProjectError;
/// Produced when a stage that deals specifically with code encounters
/// a path that refers to a directory
#[derive(Debug)]
pub struct UnexpectedDirectory {
pub path: Vec<String>
}
impl ProjectError for UnexpectedDirectory {
fn description(&self) -> &str {
"A stage that deals specifically with code encountered a path \
that refers to a directory"
}
fn positions(&self) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition::just_file(self.path.clone()))
}
fn message(&self) -> String {
format!(
"{} was expected to be a file but a directory was found",
self.path.join("/")
)
}
}

View File

@@ -0,0 +1,25 @@
use std::rc::Rc;
use crate::representations::location::Location;
use crate::utils::{BoxedIter, iter::box_once};
use super::project_error::{ProjectError, ErrorPosition};
#[derive(Debug)]
pub struct VisibilityMismatch{
pub namespace: Vec<String>,
pub file: Rc<Vec<String>>
}
impl ProjectError for VisibilityMismatch {
fn description(&self) -> &str {
"Some occurences of a namespace are exported but others are not"
}
fn positions(&self) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition {
location: Location::File(self.file.clone()),
message: Some(format!(
"{} is opened multiple times with different visibilities",
self.namespace.join("::")
))
})
}
}

106
src/pipeline/file_loader.rs Normal file
View File

@@ -0,0 +1,106 @@
use std::path::Path;
use std::rc::Rc;
use std::path::PathBuf;
use std::io;
use std::fs;
use crate::utils::iter::box_once;
use crate::utils::{Cache, BoxedIter};
use crate::interner::{Interner, Token};
use crate::pipeline::error::UnexpectedDirectory;
use crate::pipeline::error::{ProjectError, ErrorPosition};
#[derive(Debug)]
pub struct FileLoadingError{
file: io::Error,
dir: io::Error,
path: Vec<String>
}
impl ProjectError for FileLoadingError {
fn description(&self) -> &str {
"Neither a file nor a directory could be read from \
the requested path"
}
fn positions(&self) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition::just_file(self.path.clone()))
}
fn message(&self) -> String {
format!("File: {}\nDirectory: {}", self.file, self.dir)
}
}
/// Represents the result of loading code from a string-tree form such
/// as the file system.
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum Loaded {
Code(Rc<String>),
Collection(Rc<Vec<String>>),
}
impl Loaded {
pub fn is_code(&self) -> bool {matches!(self, Loaded::Code(_))}
}
pub type IOResult = Result<Loaded, Rc<dyn ProjectError>>;
pub type FileCache<'a> = Cache<'a, Token<Vec<Token<String>>>, IOResult>;
/// Load a file from a path expressed in Rust strings, but relative to
/// a root expressed as an OS Path.
pub fn load_file(root: &Path, path: &[impl AsRef<str>]) -> IOResult {
// let os_path = path.into_iter()
// .map_into::<OsString>()
// .collect::<Vec<_>>();
let full_path = path.iter().fold(
root.to_owned(),
|p, s| p.join(s.as_ref())
);
let file_path = full_path.with_extension("orc");
let file_error = match fs::read_to_string(&file_path) {
Ok(string) => return Ok(Loaded::Code(Rc::new(string))),
Err(err) => err
};
let dir = match fs::read_dir(&full_path) {
Ok(dir) => dir,
Err(dir_error) => {
return Err(FileLoadingError {
file: file_error,
dir: dir_error,
path: path.iter()
.map(|s| s.as_ref().to_string())
.collect(),
}.rc())
}
};
let names = dir.filter_map(Result::ok)
.filter_map(|ent| {
let fname = ent.file_name().into_string().ok()?;
let ftyp = ent.metadata().ok()?.file_type();
Some(if ftyp.is_dir() {fname} else {
fname.strip_suffix(".or")?.to_string()
})
}).collect();
Ok(Loaded::Collection(Rc::new(names)))
}
/// Generates a cached file loader for a directory
pub fn mk_cache(root: PathBuf, i: &Interner) -> FileCache {
Cache::new(move |token: Token<Vec<Token<String>>>, _this| -> IOResult {
let path = i.r(token).iter()
.map(|t| i.r(*t).as_str())
.collect::<Vec<_>>();
load_file(&root, &path)
})
}
/// 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(
path: Token<Vec<Token<String>>>,
load_file: &impl Fn(Token<Vec<Token<String>>>) -> IOResult,
i: &Interner
) -> Result<Rc<String>, Rc<dyn ProjectError>> {
if let Loaded::Code(s) = load_file(path)? {Ok(s)}
else {Err(UnexpectedDirectory{
path: i.r(path).iter().map(|t| i.r(*t)).cloned().collect()
}.rc())}
}

View File

@@ -0,0 +1,32 @@
use std::rc::Rc;
use crate::representations::tree::Module;
use crate::representations::sourcefile::absolute_path;
use crate::utils::{Substack};
use crate::interner::{Token, Interner};
use super::error::{ProjectError, TooManySupers};
pub fn import_abs_path(
src_path: &[Token<String>],
mod_stack: Substack<Token<String>>,
module: &Module<impl Clone, impl Clone>,
import_path: &[Token<String>],
i: &Interner,
) -> Result<Vec<Token<String>>, Rc<dyn ProjectError>> {
// path of module within file
let mod_pathv = mod_stack.iter().rev_vec_clone();
// path of module within compilation
let abs_pathv = src_path.iter().copied()
.chain(mod_pathv.iter().copied())
.collect::<Vec<_>>();
// preload-target path relative to module
// preload-target path within compilation
absolute_path(&abs_pathv, import_path, i, &|n| {
module.items.contains_key(&n)
}).map_err(|_| TooManySupers{
path: import_path.iter().map(|t| i.r(*t)).cloned().collect(),
offender_file: src_path.iter().map(|t| i.r(*t)).cloned().collect(),
offender_mod: mod_pathv.iter().map(|t| i.r(*t)).cloned().collect(),
}.rc())
}

View File

@@ -0,0 +1,53 @@
use hashbrown::{HashMap, HashSet};
use std::hash::Hash;
use crate::interner::Token;
#[derive(Clone, Debug, Default)]
pub struct AliasMap{
pub targets: HashMap<Token<Vec<Token<String>>>, Token<Vec<Token<String>>>>,
pub aliases: HashMap<Token<Vec<Token<String>>>, HashSet<Token<Vec<Token<String>>>>>,
}
impl AliasMap {
pub fn new() -> Self {Self::default()}
pub fn link(&mut self, alias: Token<Vec<Token<String>>>, target: Token<Vec<Token<String>>>) {
let prev = self.targets.insert(alias, target);
debug_assert!(prev.is_none(), "Alias already has a target");
multimap_entry(&mut self.aliases, &target).insert(alias);
// Remove aliases of the alias
if let Some(alts) = self.aliases.remove(&alias) {
for alt in alts {
// Assert that this step has always been done in the past
debug_assert!(
self.aliases.get(&alt)
.map(HashSet::is_empty)
.unwrap_or(true),
"Alias set of alias not empty"
);
debug_assert!(
self.targets.insert(alt, target) == Some(alias),
"Name not target of its own alias"
);
multimap_entry(&mut self.aliases, &target).insert(alt);
}
}
}
pub fn resolve(&self, alias: Token<Vec<Token<String>>>) -> Option<Token<Vec<Token<String>>>> {
self.targets.get(&alias).copied()
}
}
/// find or create the set belonging to the given key in the given
/// map-to-set (aka. multimap)
fn multimap_entry<'a, K: Eq + Hash + Clone, V>(
map: &'a mut HashMap<K, HashSet<V>>,
key: &'_ K
) -> &'a mut HashSet<V> {
map.raw_entry_mut()
.from_key(key)
.or_insert_with(|| (key.clone(), HashSet::new()))
.1
}

View File

@@ -0,0 +1,87 @@
use std::rc::Rc;
use hashbrown::HashMap;
use crate::{utils::Substack, interner::{Token, Interner}, pipeline::{ProjectModule, ProjectExt}, representations::tree::{ModEntry, ModMember}, ast::{Rule, Expr}};
use super::{alias_map::AliasMap, decls::InjectedAsFn};
fn process_expr(
expr: &Expr,
alias_map: &AliasMap,
injected_as: &impl InjectedAsFn,
i: &Interner,
) -> Expr {
expr.map_names(&|n| {
injected_as(&i.r(n)[..]).or_else(|| {
alias_map.resolve(n).map(|n| {
injected_as(&i.r(n)[..]).unwrap_or(n)
})
})
}).unwrap_or_else(|| expr.clone())
}
// TODO: replace is_injected with injected_as
/// Replace all aliases with the name they're originally defined as
fn apply_aliases_rec(
path: Substack<Token<String>>,
module: &ProjectModule,
alias_map: &AliasMap,
i: &Interner,
injected_as: &impl InjectedAsFn,
) -> ProjectModule {
let items = module.items.iter().map(|(name, ent)| {
let ModEntry{ exported, member } = ent;
let member = match member {
ModMember::Item(expr) => 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 {module.clone()} else {
let module = module.as_ref();
Rc::new(apply_aliases_rec(
subpath, module,
alias_map, i, injected_as
))
};
ModMember::Sub(new_mod)
}
};
(*name, ModEntry{ exported: *exported, member })
}).collect::<HashMap<_, _>>();
let rules = module.extra.rules.iter().map(|rule| {
let Rule{ source, prio, target } = rule;
Rule{
prio: *prio,
source: Rc::new(source.iter()
.map(|expr| process_expr(expr, alias_map, injected_as, i))
.collect::<Vec<_>>()
),
target: Rc::new(target.iter()
.map(|expr| process_expr(expr, alias_map, injected_as, i))
.collect::<Vec<_>>()
),
}
}).collect::<Vec<_>>();
ProjectModule{
items,
imports: module.imports.clone(),
extra: ProjectExt{
rules,
exports: module.extra.exports.clone(),
file: module.extra.file.clone(),
imports_from: module.extra.imports_from.clone(),
}
}
}
pub fn apply_aliases(
module: &ProjectModule,
alias_map: &AliasMap,
i: &Interner,
injected_as: &impl InjectedAsFn,
) -> ProjectModule {
apply_aliases_rec(Substack::Bottom, module, alias_map, i, injected_as)
}

View File

@@ -0,0 +1,103 @@
use std::rc::Rc;
use crate::representations::tree::{WalkErrorKind, ModMember};
use crate::pipeline::error::{ProjectError, NotExported};
use crate::pipeline::project_tree::{ProjectTree, split_path, ProjectModule};
use crate::interner::{Token, Interner};
use crate::utils::{Substack, pushed};
use super::alias_map::AliasMap;
use super::decls::InjectedAsFn;
/// Assert that a module identified by a path can see a given symbol
fn assert_visible(
source: &[Token<String>], // must point to a file or submodule
target: &[Token<String>], // may point to a symbol or module of any kind
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 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 {
WalkErrorKind::Missing => panic!("checked in parsing"),
WalkErrorKind::Private => {
let full_path = &tgt_path[..shared_len + e.pos];
let (file, sub) = split_path(full_path, &project);
let (ref_file, ref_sub) = split_path(source, &project);
NotExported{
file: i.extern_all(file),
subpath: i.extern_all(sub),
referrer_file: i.extern_all(ref_file),
referrer_subpath: i.extern_all(ref_sub),
}.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);
if !tgt_item_exported && !target_prefixes_source {
let (file, sub) = split_path(target, &project);
let (ref_file, ref_sub) = split_path(source, &project);
Err(NotExported{
file: i.extern_all(file),
subpath: i.extern_all(sub),
referrer_file: i.extern_all(ref_file),
referrer_subpath: i.extern_all(ref_sub),
}.rc())
} else {Ok(())}
}
/// Populate target and alias maps from the module tree recursively
fn collect_aliases_rec(
path: Substack<Token<String>>,
module: &ProjectModule,
project: &ProjectTree,
alias_map: &mut AliasMap,
i: &Interner,
injected_as: &impl InjectedAsFn,
) -> 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() {return Ok(())};
for (&name, &target_mod) in module.extra.imports_from.iter() {
let target_mod_v = i.r(target_mod);
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);
alias_map.link(sym_path, target_sym);
}
for (&name, entry) in module.items.iter() {
let submodule = if let ModMember::Sub(s) = &entry.member {
s.as_ref()
} else {continue};
collect_aliases_rec(
path.push(name),
submodule, project, alias_map,
i, injected_as,
)?
}
Ok(())
}
/// Populate target and alias maps from the module tree
pub fn collect_aliases(
module: &ProjectModule,
project: &ProjectTree,
alias_map: &mut AliasMap,
i: &Interner,
injected_as: &impl InjectedAsFn,
) -> Result<(), Rc<dyn ProjectError>> {
collect_aliases_rec(
Substack::Bottom, module, project, alias_map,
i, injected_as
)
}

View File

@@ -0,0 +1,5 @@
use crate::interner::Token;
pub trait InjectedAsFn = Fn(
&[Token<String>]
) -> Option<Token<Vec<Token<String>>>>;

View File

@@ -0,0 +1,7 @@
mod alias_map;
mod collect_aliases;
mod apply_aliases;
mod resolve_imports;
mod decls;
pub use resolve_imports::resolve_imports;

View File

@@ -0,0 +1,28 @@
use std::rc::Rc;
use crate::interner::Interner;
use crate::pipeline::error::ProjectError;
use crate::pipeline::project_tree::ProjectTree;
use super::alias_map::AliasMap;
use super::collect_aliases::collect_aliases;
use super::apply_aliases::apply_aliases;
use super::decls::InjectedAsFn;
/// Follow import chains to locate the original name of all tokens, then
/// replace these aliases with the original names throughout the tree
pub fn resolve_imports(
project: ProjectTree,
i: &Interner,
injected_as: &impl InjectedAsFn,
) -> Result<ProjectTree, Rc<dyn ProjectError>> {
let mut map = AliasMap::new();
collect_aliases(
project.0.as_ref(),
&project, &mut map,
i, injected_as
)?;
let new_mod = apply_aliases(project.0.as_ref(), &map, i, injected_as);
Ok(ProjectTree(Rc::new(new_mod)))
}

19
src/pipeline/mod.rs Normal file
View File

@@ -0,0 +1,19 @@
pub mod error;
mod project_tree;
mod source_loader;
mod import_abs_path;
mod split_name;
mod import_resolution;
pub mod file_loader;
mod parse_layer;
pub use parse_layer::parse_layer;
pub use project_tree::{
ConstTree, ProjectExt, ProjectModule, ProjectTree, from_const_tree,
collect_consts, collect_rules,
};
// pub use file_loader::{Loaded, FileLoadingError, IOResult};
// pub use error::{
// ErrorPosition, ModuleNotFound, NotExported, ParseErrorWithPath,
// ProjectError, TooManySupers, UnexpectedDirectory
// };

View File

@@ -0,0 +1,52 @@
use std::rc::Rc;
use crate::representations::sourcefile::FileEntry;
use crate::interner::{Token, Interner};
use super::{project_tree, import_resolution};
use super::source_loader;
use super::file_loader::IOResult;
use super::error::ProjectError;
use super::ProjectTree;
/// Using an IO callback, produce a project tree that includes the given
/// target symbols or files if they're defined.
///
/// The environment accessible to the loaded source can be specified with
/// a pre-existing tree which will be merged with the loaded data, and a
/// prelude which will be prepended to each individual file. Since the
/// prelude gets compiled with each file, normally it should be a glob
/// import pointing to a module in the environment.
pub fn parse_layer<'a>(
targets: &[Token<Vec<Token<String>>>],
loader: &impl Fn(Token<Vec<Token<String>>>) -> IOResult,
environment: &'a ProjectTree,
prelude: &[FileEntry],
i: &Interner,
) -> Result<ProjectTree, Rc<dyn ProjectError>> {
// A path is injected if it is walkable in the injected tree
let injected_as = |path: &[Token<String>]| {
let (item, modpath) = path.split_last()?;
let module = environment.0.walk(modpath, false).ok()?;
let inj = module.extra.exports.get(item).copied()?;
Some(inj)
};
let injected_names = |path: Token<Vec<Token<String>>>| {
let pathv = &i.r(path)[..];
let module = environment.0.walk(&pathv, false).ok()?;
Some(Rc::new(
module.extra.exports.keys().copied().collect()
))
};
let source = source_loader::load_source(
targets, i, loader, &|path| injected_as(path).is_some()
)?;
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()
));
let resolvd = import_resolution::resolve_imports(sum, i, &injected_as)?;
// Addition among modules favours the left hand side.
Ok(resolvd)
}

View File

@@ -0,0 +1,52 @@
use crate::representations::sourcefile::{Member, FileEntry};
use crate::interner::Token;
fn member_rec(
// object
member: Member,
// context
path: &[Token<String>],
prelude: &[FileEntry],
) -> Member {
match member {
Member::Namespace(name, body) => {
let new_body = entv_rec(
body,
path,
prelude
);
Member::Namespace(name, new_body)
},
any => any
}
}
fn entv_rec(
// object
data: Vec<FileEntry>,
// context
mod_path: &[Token<String>],
prelude: &[FileEntry],
) -> Vec<FileEntry> {
prelude.iter().cloned()
.chain(data.into_iter()
.map(|ent| match ent {
FileEntry::Exported(mem) => FileEntry::Exported(member_rec(
mem, mod_path, prelude
)),
FileEntry::Internal(mem) => FileEntry::Internal(member_rec(
mem, mod_path, prelude
)),
any => any
})
)
.collect()
}
pub fn add_prelude(
data: Vec<FileEntry>,
path: &[Token<String>],
prelude: &[FileEntry],
) -> Vec<FileEntry> {
entv_rec(data, path, prelude)
}

View File

@@ -0,0 +1,215 @@
use std::rc::Rc;
use hashbrown::HashMap;
use crate::pipeline::error::ProjectError;
use crate::interner::{Token, Interner};
use crate::utils::iter::{box_once, box_empty};
use crate::utils::{Substack, pushed};
use crate::ast::{Expr, Constant};
use crate::pipeline::source_loader::{LoadedSourceTable, LoadedSource};
use crate::representations::tree::{Module, ModMember, ModEntry};
use crate::representations::sourcefile::{FileEntry, Member, absolute_path};
use super::collect_ops::InjectedOperatorsFn;
use super::{collect_ops, ProjectTree, ProjectExt};
use super::parse_file::parse_file;
#[derive(Debug)]
struct ParsedSource<'a> {
path: Vec<Token<String>>,
loaded: &'a LoadedSource,
parsed: Vec<FileEntry>
}
pub fn split_path<'a>(path: &'a [Token<String>], proj: &'a ProjectTree)
-> (&'a [Token<String>], &'a [Token<String>])
{
let (end, body) = if let Some(s) = path.split_last() {s}
else {return (&[], &[])};
let mut module = proj.0.walk(body, false).expect("invalid path cannot be split");
if let ModMember::Sub(m) = &module.items[end].member {
module = m.clone();
}
let file = module.extra.file.as_ref()
.map(|s| &path[..s.len()])
.unwrap_or(&path[..]);
let subpath = &path[file.len()..];
(file, subpath)
}
/// Convert normalized, prefixed source into a module
fn source_to_module(
// level
path: Substack<Token<String>>,
preparsed: &Module<impl Clone, impl Clone>,
// data
data: Vec<FileEntry>,
// context
i: &Interner,
filepath_len: usize,
) -> Rc<Module<Expr, ProjectExt>> {
let path_v = path.iter().rev_vec_clone();
let imports = data.iter()
.filter_map(|ent| if let FileEntry::Import(impv) = ent {
Some(impv.iter())
} else {None})
.flatten()
.cloned()
.collect::<Vec<_>>();
let imports_from = imports.iter()
.map(|imp| {
let mut imp_path_v = i.r(imp.path).clone();
imp_path_v.push(imp.name.expect("imports normalized"));
let mut abs_path = absolute_path(
&path_v,
&imp_path_v,
i, &|n| preparsed.items.contains_key(&n)
).expect("tested in preparsing");
let name = abs_path.pop().expect("importing the global context");
(name, i.i(&abs_path))
})
.collect::<HashMap<_, _>>();
let exports = data.iter()
.flat_map(|ent| {
let mk_ent = |name| (name, i.i(&pushed(&path_v, name)));
match ent {
FileEntry::Export(names)
=> Box::new(names.iter().copied().map(mk_ent)),
FileEntry::Exported(mem) => match mem {
Member::Constant(constant) => box_once(mk_ent(constant.name)),
Member::Namespace(name, _) => box_once(mk_ent(*name)),
Member::Rule(rule) => {
let mut names = Vec::new();
for e in rule.source.iter() {
e.visit_names(Substack::Bottom, &mut |n| {
if let Some([name]) = i.r(n).strip_prefix(&path_v[..]) {
names.push((*name, n))
}
})
}
Box::new(names.into_iter())
}
}
_ => box_empty()
}
})
.collect::<HashMap<_, _>>();
let rules = data.iter()
.filter_map(|ent| match ent {
FileEntry::Exported(Member::Rule(rule)) => Some(rule),
FileEntry::Internal(Member::Rule(rule)) => Some(rule),
_ => None,
})
.cloned()
.collect::<Vec<_>>();
let items = data.into_iter()
.filter_map(|ent| match ent {
FileEntry::Exported(Member::Namespace(name, body)) => {
let prep_member = &preparsed.items[&name].member;
let new_prep = if let ModMember::Sub(s) = prep_member {s.as_ref()}
else { panic!("preparsed missing a submodule") };
let module = source_to_module(
path.push(name),
new_prep, body, i, filepath_len
);
let member = ModMember::Sub(module);
Some((name, ModEntry{ exported: true, member }))
}
FileEntry::Internal(Member::Namespace(name, body)) => {
let prep_member = &preparsed.items[&name].member;
let new_prep = if let ModMember::Sub(s) = prep_member {s.as_ref()}
else { panic!("preparsed missing a submodule") };
let module = source_to_module(
path.push(name),
new_prep, body, i, filepath_len
);
let member = ModMember::Sub(module);
Some((name, ModEntry{ exported: false, member }))
}
FileEntry::Exported(Member::Constant(Constant{ name, value })) => {
let member = ModMember::Item(value);
Some((name, ModEntry{ exported: true, member }))
}
FileEntry::Internal(Member::Constant(Constant{ name, value })) => {
let member = ModMember::Item(value);
Some((name, ModEntry{ exported: false, member }))
}
_ => None,
})
.collect::<HashMap<_, _>>();
Rc::new(Module {
imports,
items,
extra: ProjectExt {
imports_from,
exports,
rules,
file: Some(path_v[..filepath_len].to_vec())
}
})
}
fn files_to_module(
path: Substack<Token<String>>,
files: &[ParsedSource],
i: &Interner
) -> Rc<Module<Expr, ProjectExt>> {
let lvl = path.len();
let path_v = path.iter().rev_vec_clone();
if files.len() == 1 && files[0].path.len() == lvl {
return source_to_module(
path,
files[0].loaded.preparsed.0.as_ref(),
files[0].parsed.clone(),
i, path.len()
)
}
let items = files.group_by(|a, b| a.path[lvl] == b.path[lvl]).into_iter()
.map(|files| {
let namespace = files[0].path[lvl];
let subpath = path.push(namespace);
let module = files_to_module(subpath, files, i);
let member = ModMember::Sub(module);
(namespace, ModEntry{ exported: true, member })
})
.collect::<HashMap<_, _>>();
let exports = items.keys()
.copied()
.map(|name| (name, i.i(&pushed(&path_v, name))))
.collect();
Rc::new(Module{
items,
imports: vec![],
extra: ProjectExt {
exports,
imports_from: HashMap::new(),
rules: vec![], file: None,
}
})
}
pub fn build_tree<'a>(
files: LoadedSourceTable,
i: &Interner,
prelude: &[FileEntry],
injected: &impl InjectedOperatorsFn,
) -> Result<ProjectTree, Rc<dyn ProjectError>> {
let ops_cache = collect_ops::mk_cache(&files, i, injected);
let mut entries = files.iter()
.map(|(path, loaded)| Ok((
i.r(*path),
loaded,
parse_file(*path, &files, &ops_cache, i, prelude)?
)))
.collect::<Result<Vec<_>, Rc<dyn ProjectError>>>()?;
// sort by similarity, then longest-first
entries.sort_unstable_by(|a, b| a.0.cmp(&b.0).reverse());
let files = entries.into_iter()
.map(|(path, loaded, parsed)| ParsedSource{
loaded, parsed,
path: path.clone()
})
.collect::<Vec<_>>();
Ok(ProjectTree(files_to_module(Substack::Bottom, &files, i)))
}

View File

@@ -0,0 +1,75 @@
use std::rc::Rc;
use hashbrown::HashSet;
use crate::representations::tree::WalkErrorKind;
use crate::pipeline::source_loader::LoadedSourceTable;
use crate::pipeline::error::{ProjectError, ModuleNotFound};
use crate::interner::{Token, Interner};
use crate::utils::Cache;
use crate::pipeline::split_name::split_name;
pub type OpsResult = Result<Rc<HashSet<Token<String>>>, Rc<dyn ProjectError>>;
pub type ExportedOpsCache<'a> = Cache<'a, Token<Vec<Token<String>>>, OpsResult>;
pub trait InjectedOperatorsFn = Fn(
Token<Vec<Token<String>>>
) -> Option<Rc<HashSet<Token<String>>>>;
fn coprefix<T: Eq>(
l: impl Iterator<Item = T>,
r: impl Iterator<Item = T>
) -> usize {
l.zip(r).take_while(|(a, b)| a == b).count()
}
/// Collect all names exported by the module at the specified path
pub fn collect_exported_ops(
path: Token<Vec<Token<String>>>,
loaded: &LoadedSourceTable,
i: &Interner,
injected: &impl InjectedOperatorsFn
) -> OpsResult {
if let Some(i) = injected(path) {return Ok(i)}
let is_file = |n: &[Token<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().copied()
.filter_map(|modname| {
let modname_s = i.r(modname);
if path_s.len() == coprefix(path_s.iter(), modname_s.iter()) {
Some(modname_s[path_s.len()])
} else {None}
})
.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| match walk_err.kind {
WalkErrorKind::Private => unreachable!("visibility is not being checked here"),
WalkErrorKind::Missing => ModuleNotFound{
file: i.extern_vec(fpath),
subpath: subpath_v.into_iter()
.take(walk_err.pos)
.map(|t| i.r(*t))
.cloned()
.collect()
}.rc(),
})?;
Ok(Rc::new(module.items.iter()
.filter(|(_, v)| v.exported)
.map(|(k, _)| *k)
.collect()
))
}
pub fn mk_cache<'a>(
loaded: &'a LoadedSourceTable,
i: &'a Interner,
injected: &'a impl InjectedOperatorsFn,
) -> ExportedOpsCache<'a> {
Cache::new(|path, _this| collect_exported_ops(path, loaded, i, injected))
}

View File

@@ -0,0 +1,8 @@
mod exported_ops;
mod ops_for;
pub use exported_ops::{
ExportedOpsCache, OpsResult, InjectedOperatorsFn,
collect_exported_ops, mk_cache
};
pub use ops_for::collect_ops_for;

View File

@@ -0,0 +1,49 @@
use std::rc::Rc;
use hashbrown::HashSet;
use crate::parse::is_op;
use crate::pipeline::error::ProjectError;
use crate::pipeline::source_loader::LoadedSourceTable;
use crate::interner::{Token, Interner};
use crate::representations::tree::{Module, ModMember};
use crate::pipeline::import_abs_path::import_abs_path;
use super::exported_ops::{ExportedOpsCache, OpsResult};
/// Collect all operators and names, exported or local, defined in this
/// tree.
fn tree_all_ops(
module: &Module<impl Clone, impl Clone>,
ops: &mut HashSet<Token<String>>
) {
ops.extend(module.items.keys().copied());
for ent in module.items.values() {
if let ModMember::Sub(m) = &ent.member {
tree_all_ops(m.as_ref(), ops);
}
}
}
/// Collect all names imported in this file
pub fn collect_ops_for(
file: &[Token<String>],
loaded: &LoadedSourceTable,
ops_cache: &ExportedOpsCache,
i: &Interner
) -> OpsResult {
let tree = &loaded[&i.i(file)].preparsed.0;
let mut ret = HashSet::new();
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 {
let path = import_abs_path(
&file, modpath, module, &i.r(import.path)[..], i
).expect("This error should have been caught during loading");
ret.extend(ops_cache.find(&i.i(&path))?.iter().copied());
}
Ok::<_, Rc<dyn ProjectError>>(())
})?;
ret.drain_filter(|t| !is_op(i.r(*t)));
Ok(Rc::new(ret))
}

View File

@@ -0,0 +1,93 @@
use std::{ops::Add, rc::Rc};
use hashbrown::HashMap;
use crate::representations::tree::{ModEntry, ModMember, Module};
use crate::representations::Primitive;
use crate::representations::location::Location;
use crate::foreign::ExternFn;
use crate::interner::{Token, Interner};
use crate::ast::{Expr, Clause};
use crate::utils::{Substack, pushed};
use super::{ProjectModule, ProjectExt, ProjectTree};
pub enum ConstTree {
Const(Expr),
Tree(HashMap<Token<String>, ConstTree>)
}
impl ConstTree {
pub fn xfn(xfn: impl ExternFn + 'static) -> Self {
Self::Const(Expr{
location: Location::Unknown,
value: Clause::P(Primitive::ExternFn(Box::new(xfn)))
})
}
pub fn tree(
arr: impl IntoIterator<Item = (Token<String>, Self)>
) -> Self {
Self::Tree(arr.into_iter().collect())
}
}
impl Add for ConstTree {
type Output = ConstTree;
fn add(self, rhs: ConstTree) -> Self::Output {
if let (Self::Tree(t1), Self::Tree(mut t2)) = (self, rhs) {
let mut product = HashMap::new();
for (key, i1) in t1 {
if let Some(i2) = t2.remove(&key) {
product.insert(key, i1 + i2);
} else {
product.insert(key, i1);
}
}
product.extend(t2.into_iter());
Self::Tree(product)
} else {
panic!("cannot combine tree and value fields")
}
}
}
fn from_const_tree_rec(
path: Substack<Token<String>>,
consts: HashMap<Token<String>, ConstTree>,
file: &[Token<String>],
i: &Interner,
) -> ProjectModule {
let mut items = HashMap::new();
let path_v = path.iter().rev_vec_clone();
for (name, item) in consts {
items.insert(name, ModEntry{
exported: true,
member: match item {
ConstTree::Const(c) => ModMember::Item(c),
ConstTree::Tree(t) => ModMember::Sub(Rc::new(
from_const_tree_rec(path.push(name), t, file, i)
)),
}
});
}
let exports = items.keys()
.map(|name| (*name, i.i(&pushed(&path_v, *name))))
.collect();
Module {
items,
imports: vec![],
extra: ProjectExt {
exports,
file: Some(file.to_vec()),
..Default::default()
}
}
}
pub fn from_const_tree(
consts: HashMap<Token<String>, ConstTree>,
file: &[Token<String>],
i: &Interner,
) -> ProjectTree {
let module = from_const_tree_rec(Substack::Bottom, consts, file, i);
ProjectTree(Rc::new(module))
}

View File

@@ -0,0 +1,38 @@
/* FILE SEPARATION BOUNDARY
Collect all operators accessible in each file, parse the files with
correct tokenization, resolve glob imports, convert expressions to
refer to tokens with (local) absolute path, and connect them into a
single tree.
The module checks for imports from missing modules (including submodules).
All other errors must be checked later.
Injection strategy:
Return all items of the given module in the injected tree for `injected`
The output of this stage is a tree, which can simply be overlaid with
the injected tree
*/
mod collect_ops;
mod parse_file;
mod build_tree;
mod normalize_imports;
mod prefix;
mod tree;
mod const_tree;
mod add_prelude;
pub use collect_ops::InjectedOperatorsFn;
pub use const_tree::{
ConstTree, from_const_tree,
};
pub use tree::{
ProjectExt, ProjectModule, ProjectTree, collect_consts, collect_rules
};
pub use build_tree::{
build_tree, split_path
};

View File

@@ -0,0 +1,84 @@
use crate::representations::tree::{Module, ModMember};
use crate::representations::sourcefile::{Member, FileEntry, Import};
use crate::utils::BoxedIter;
use crate::utils::{Substack, iter::box_once};
use crate::interner::{Interner, Token};
use crate::pipeline::import_abs_path::import_abs_path;
use super::collect_ops::ExportedOpsCache;
fn member_rec(
// level
mod_stack: Substack<Token<String>>,
preparsed: &Module<impl Clone, impl Clone>,
// object
member: Member,
// context
path: &[Token<String>],
ops_cache: &ExportedOpsCache,
i: &Interner
) -> Member {
match member {
Member::Namespace(name, body) => {
let prepmember = &preparsed.items[&name].member;
let subprep = if let ModMember::Sub(m) = prepmember {m.clone()}
else {unreachable!("This name must point to a namespace")};
let new_body = entv_rec(
mod_stack.push(name),
subprep.as_ref(),
body,
path, ops_cache, i
);
Member::Namespace(name, new_body)
},
any => any
}
}
fn entv_rec(
// level
mod_stack: Substack<Token<String>>,
preparsed: &Module<impl Clone, impl Clone>,
// object
data: Vec<FileEntry>,
// context
mod_path: &[Token<String>],
ops_cache: &ExportedOpsCache,
i: &Interner
) -> Vec<FileEntry> {
data.into_iter()
.map(|ent| match ent {
FileEntry::Import(imps) => FileEntry::Import(imps.into_iter()
.flat_map(|import| if let Import{ name: None, path } = import {
let p = import_abs_path(
mod_path, mod_stack, preparsed, &i.r(path)[..], i
).expect("Should have emerged in preparsing");
let names = ops_cache.find(&i.i(&p))
.expect("Should have emerged in second parsing");
let imports = names.iter()
.map(move |&n| Import{ name: Some(n), path })
.collect::<Vec<_>>();
Box::new(imports.into_iter()) as BoxedIter<Import>
} else {box_once(import)})
.collect()
),
FileEntry::Exported(mem) => FileEntry::Exported(member_rec(
mod_stack, preparsed, mem, mod_path, ops_cache, i
)),
FileEntry::Internal(mem) => FileEntry::Internal(member_rec(
mod_stack, preparsed, mem, mod_path, ops_cache, i
)),
any => any
})
.collect()
}
pub fn normalize_imports(
preparsed: &Module<impl Clone, impl Clone>,
data: Vec<FileEntry>,
path: &[Token<String>],
ops_cache: &ExportedOpsCache,
i: &Interner
) -> Vec<FileEntry> {
entv_rec(Substack::Bottom, preparsed, data, path, ops_cache, i)
}

View File

@@ -0,0 +1,44 @@
use std::rc::Rc;
use crate::parse;
use crate::pipeline::error::ProjectError;
use crate::representations::sourcefile::{FileEntry, normalize_namespaces};
use crate::pipeline::source_loader::LoadedSourceTable;
use crate::interner::{Token, Interner};
use super::add_prelude::add_prelude;
use super::collect_ops::{ExportedOpsCache, collect_ops_for};
use super::normalize_imports::normalize_imports;
use super::prefix::prefix;
pub fn parse_file(
path: Token<Vec<Token<String>>>,
loaded: &LoadedSourceTable,
ops_cache: &ExportedOpsCache,
i: &Interner,
prelude: &[FileEntry],
) -> Result<Vec<FileEntry>, Rc<dyn ProjectError>> {
let ld = &loaded[&path];
// let ops_cache = collect_ops::mk_cache(loaded, i);
let ops = collect_ops_for(&i.r(path)[..], loaded, ops_cache, i)?;
let ops_vec = ops.iter()
.map(|t| i.r(*t))
.cloned()
.collect::<Vec<_>>();
let ctx = parse::ParsingContext{
interner: i,
ops: &ops_vec,
file: Rc::new(i.extern_vec(path))
};
let entries = parse::parse(ld.text.as_str(), ctx)
.expect("This error should have been caught during loading");
let with_prelude = add_prelude(entries, &i.r(path)[..], prelude);
let impnormalized = normalize_imports(
&ld.preparsed.0, with_prelude, &i.r(path)[..], ops_cache, i
);
let nsnormalized = normalize_namespaces(
Box::new(impnormalized.into_iter()), i
).expect("This error should have been caught during preparsing");
let prefixed = prefix(nsnormalized, &i.r(path)[..], ops_cache, i);
Ok(prefixed)
}

View File

@@ -0,0 +1,82 @@
use std::rc::Rc;
use crate::ast::{Constant, Rule};
use crate::interner::{Token, Interner};
use crate::utils::Substack;
use crate::representations::sourcefile::{Member, FileEntry};
use super::collect_ops::ExportedOpsCache;
fn member_rec(
// level
mod_stack: Substack<Token<String>>,
// object
data: Member,
// context
path: &[Token<String>],
ops_cache: &ExportedOpsCache,
i: &Interner
) -> Member {
// let except = |op| imported.contains(&op);
let except = |_| false;
let prefix_v = path.iter().copied()
.chain(mod_stack.iter().rev_vec_clone().into_iter())
.collect::<Vec<_>>();
let prefix = i.i(&prefix_v);
match data {
Member::Namespace(name, body) => {
let new_body = entv_rec(
mod_stack.push(name),
body,
path, ops_cache, i
);
Member::Namespace(name, new_body)
}
Member::Constant(constant) => Member::Constant(Constant{
name: constant.name,
value: constant.value.prefix(prefix, i, &except)
}),
Member::Rule(rule) => Member::Rule(Rule{
prio: rule.prio,
source: Rc::new(rule.source.iter()
.map(|e| e.prefix(prefix, i, &except))
.collect()
),
target: Rc::new(rule.target.iter()
.map(|e| e.prefix(prefix, i, &except))
.collect()
),
})
}
}
fn entv_rec(
// level
mod_stack: Substack<Token<String>>,
// object
data: Vec<FileEntry>,
// context
path: &[Token<String>],
ops_cache: &ExportedOpsCache,
i: &Interner
) -> Vec<FileEntry> {
data.into_iter().map(|fe| match fe {
FileEntry::Exported(mem) => FileEntry::Exported(member_rec(
mod_stack, mem, path, ops_cache, i
)),
FileEntry::Internal(mem) => FileEntry::Internal(member_rec(
mod_stack, mem, path, ops_cache, i
)),
// XXX should [FileEntry::Export] be prefixed?
any => any
}).collect()
}
pub fn prefix(
data: Vec<FileEntry>,
path: &[Token<String>],
ops_cache: &ExportedOpsCache,
i: &Interner
) -> Vec<FileEntry> {
entv_rec(Substack::Bottom, data, path, ops_cache, i)
}

View File

@@ -0,0 +1,87 @@
use std::{ops::Add, rc::Rc};
use hashbrown::HashMap;
use crate::representations::tree::{Module, ModMember};
use crate::ast::{Rule, Expr};
use crate::interner::{Token, Interner};
use crate::utils::Substack;
#[derive(Clone, Debug, Default)]
pub struct ProjectExt{
/// Pairs each foreign token to the module it was imported from
pub imports_from: HashMap<Token<String>, Token<Vec<Token<String>>>>,
/// Pairs each exported token to its original full name.
pub exports: HashMap<Token<String>, Token<Vec<Token<String>>>>,
/// All rules defined in this module, exported or not
pub rules: Vec<Rule>,
/// Filename, if known, for error reporting
pub file: Option<Vec<Token<String>>>
}
impl Add for ProjectExt {
type Output = Self;
fn add(mut self, rhs: Self) -> Self::Output {
let ProjectExt{ imports_from, exports, rules, file } = rhs;
self.imports_from.extend(imports_from.into_iter());
self.exports.extend(exports.into_iter());
self.rules.extend(rules.into_iter());
if file.is_some() { self.file = file }
self
}
}
pub type ProjectModule = Module<Expr, ProjectExt>;
pub struct ProjectTree(pub Rc<ProjectModule>);
fn collect_rules_rec(bag: &mut Vec<Rule>, module: &ProjectModule) {
bag.extend(module.extra.rules.iter().cloned());
for item in module.items.values() {
if let ModMember::Sub(module) = &item.member {
collect_rules_rec(bag, module.as_ref());
}
}
}
pub fn collect_rules(project: &ProjectTree) -> Vec<Rule> {
let mut rules = Vec::new();
collect_rules_rec(&mut rules, project.0.as_ref());
rules
}
fn collect_consts_rec(
path: Substack<Token<String>>,
bag: &mut HashMap<Token<Vec<Token<String>>>, Expr>,
module: &ProjectModule,
i: &Interner
) {
for (key, entry) in module.items.iter() {
match &entry.member {
ModMember::Item(expr) => {
let mut name = path.iter().rev_vec_clone();
name.push(*key);
bag.insert(i.i(&name), expr.clone());
}
ModMember::Sub(module) => {
collect_consts_rec(
path.push(*key),
bag, module, i
)
}
}
}
}
pub fn collect_consts(project: &ProjectTree, i: &Interner)
-> HashMap<Token<Vec<Token<String>>>, Expr>
{
let mut consts = HashMap::new();
collect_consts_rec(
Substack::Bottom,
&mut consts,
project.0.as_ref(),
i
);
consts
}

View File

@@ -0,0 +1,82 @@
use std::iter;
use std::rc::Rc;
use crate::pipeline::error::ProjectError;
use crate::pipeline::import_abs_path::import_abs_path;
use crate::pipeline::split_name::split_name;
use crate::interner::{Token, Interner};
use crate::pipeline::file_loader::{Loaded, load_text, IOResult};
use super::loaded_source::{LoadedSourceTable, LoadedSource};
use super::preparse::preparse;
/// Load the source at the given path or all within if it's a collection,
/// and all sources imported from these.
fn load_abs_path_rec(
abs_path: Token<Vec<Token<String>>>,
table: &mut LoadedSourceTable,
i: &Interner,
get_source: &impl Fn(Token<Vec<Token<String>>>) -> IOResult,
is_injected: &impl Fn(&[Token<String>]) -> 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) {
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, i, get_source, is_injected
)?
}
return Ok(());
};
// otherwise 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(),
text.as_str(), i
)?;
table.insert(abs_path, LoadedSource{ text, preparsed: preparsed.clone() });
// recurse on all imported modules
preparsed.0.visit_all_imports(&mut |modpath, module, import| {
let abs_pathv = import_abs_path(
&filename, modpath,
module, &import.nonglob_path(i), i
)?;
// recurse on imported module
load_abs_path_rec(i.i(&abs_pathv), table, i, get_source, is_injected)
})
}
/// Load and preparse all files reachable from the load targets via
/// imports that aren't injected.
pub fn load_source(
targets: &[Token<Vec<Token<String>>>],
i: &Interner,
get_source: &impl Fn(Token<Vec<Token<String>>>) -> IOResult,
is_injected: &impl Fn(&[Token<String>]) -> bool,
) -> Result<LoadedSourceTable, Rc<dyn ProjectError>> {
let mut table = LoadedSourceTable::new();
for target in targets {
load_abs_path_rec(
*target,
&mut table,
i, get_source, is_injected
)?
}
Ok(table)
}

View File

@@ -0,0 +1,13 @@
use std::{rc::Rc, collections::HashMap};
use crate::interner::Token;
use super::preparse::Preparsed;
#[derive(Debug)]
pub struct LoadedSource {
pub text: Rc<String>,
pub preparsed: Preparsed,
}
pub type LoadedSourceTable = HashMap<Token<Vec<Token<String>>>, LoadedSource>;

View File

@@ -0,0 +1,25 @@
/* PULL LOGISTICS BOUNDARY
Specifying exactly what this module should be doing was an unexpectedly
hard challenge. It is intended to encapsulate all pull logistics, but
this definition is apparently prone to scope creep.
Load files, preparse them to obtain a list of imports, follow these.
Preparsing also returns the module tree and list of exported synbols
for free, which is needed later so the output of preparsing is also
attached to the module output.
The module checks for IO errors, syntax errors, malformed imports and
imports from missing files. All other errors must be checked later.
Injection strategy:
see whether names are valid in the injected tree for is_injected
*/
mod load_source;
mod loaded_source;
mod preparse;
pub use loaded_source::{LoadedSource, LoadedSourceTable};
pub use load_source::load_source;
pub use preparse::Preparsed;

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