Compare commits
No commits in common. "main" and "bytecode" have entirely different histories.
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -1 +0,0 @@
|
|||
*.wasm filter=lfs diff=lfs merge=lfs -text
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,5 +1,2 @@
|
|||
/target
|
||||
/Cargo.lock
|
||||
/node_modules
|
||||
flake-inputs
|
||||
.direnv
|
||||
|
|
466
Cargo.lock
generated
Normal file
466
Cargo.lock
generated
Normal file
|
@ -0,0 +1,466 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "bitmaps"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d084b0137aaa901caf9f1e8b21daa6aa24d41cd806e111335541eff9683bd6"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chumsky"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14377e276b2c8300513dff55ba4cc4142b44e5d6de6d00eb5b2307d650bb4ec1"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
"regex-automata 0.3.9",
|
||||
"serde",
|
||||
"stacker",
|
||||
"unicode-ident",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imbl"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc3be8d8cd36f33a46b1849f31f837c44d9fa87223baee3b4bd96b8f11df81eb"
|
||||
dependencies = [
|
||||
"bitmaps",
|
||||
"imbl-sized-chunks",
|
||||
"rand_core",
|
||||
"rand_xoshiro",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imbl-sized-chunks"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "144006fb58ed787dcae3f54575ff4349755b00ccc99f4b4873860b654be1ed63"
|
||||
dependencies = [
|
||||
"bitmaps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.161"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psm"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ran"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ec3b34b7476ace56430f442988b1faca754c3f05932f4d32a3a6cb7367f25f8"
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
|
||||
[[package]]
|
||||
name = "rand_xoshiro"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.7.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "rudus"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"chumsky",
|
||||
"imbl",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"ran",
|
||||
"regex",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.214"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.214"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "stacker"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"psm",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
17
Cargo.toml
17
Cargo.toml
|
@ -3,20 +3,21 @@ name = "rudus"
|
|||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
chumsky = "0.10.1"
|
||||
imbl = "5.0.0"
|
||||
imbl = "3.0.0"
|
||||
ran = "2.0.1"
|
||||
num-derive = "0.4.2"
|
||||
num-traits = "0.2.19"
|
||||
regex = "1.11.1"
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = "0.4.50"
|
||||
serde = {version = "1.0", features = ["derive"]}
|
||||
serde_json = "1.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
struct_scalpel = "0.1.1"
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
ordered-float = "5.0.0"
|
||||
# struct_scalpel = "0.1.1"
|
||||
# rust-embed = "8.5.0"
|
||||
# boxing = "0.1.2"
|
||||
# ordered-float = "4.5.0"
|
||||
# index_vec = "0.1.4"
|
||||
|
|
88
README.md
88
README.md
|
@ -1,87 +1,3 @@
|
|||

|
||||
## Ludus: A friendly, dynamic, functional language
|
||||
Ludus is a scripting programming language that is friendly, dynamic, and functional.
|
||||
# rudus
|
||||
|
||||
This repo contains a work-in-progress implementation of an interpreter for the Ludus programming language, using Rust as the host language.
|
||||
Ludus is part of the [_Thinking with Computers_ project](https://alea.ludus.dev/twc/), run by Scott Richmond at the University of Toronto, with collaborator Matt Nish-Lapidus; Bree Lohman and Mynt Marsellus are the RAs for the project. Ludus is our research language, which aspires to be a free translation of Logo for the 2020s.
|
||||
|
||||
Here are our design goals:
|
||||
|
||||
#### Friendly
|
||||
Ludus, like Logo, is meant to be a teaching language, often for students who don't think of themselves as "computer people." Our intended audience are humanities and art people at the university level (undergrads, grads, faculty). Everything is kept as simple as possible, but no simpler. Everything is consistent as possible. We aspire to the best error messages we can muster, which is important for a language to be teachable. That means being as strict as we can muster, _in order to be friendlier_.
|
||||
|
||||
Our current development target is Ludus on the web: https://web.ludus.dev. That wires what we do on the langauge interpreter (here in this repo) to a web frontend.
|
||||
|
||||
Naturally, it starts with Logo's famed turtle graphics.
|
||||
|
||||
#### Dynamic
|
||||
Statically typed programming languages generally give more helpful error messages than dynamic ones, but learning a type system (even one with robust type inference) requires learning two parallel systems: the type system and the expression system (well, and the pattern system). Type systems only really make sense once you've learned why they're necessary. And their benefits seem (to us, anyway) to be largely necessary when writing long-lived, maintainable, multi-author code. Ludus code is likely to be one-off, expressive, and single-authored.
|
||||
|
||||
To stay friendly, Ludus is dynamic. But: despite the dynamism, we aim to be as strict as possible. Certainly, we want to avoid the type conversion shenanigans of a language like JavaScript.
|
||||
|
||||
#### Functional
|
||||
Ludus is emphatically functional: it uses functions for just about everything. This is both because your humble PI had his world reordered when he learned his first functional language (Elixir), and because the research into Logo and the programming cultures of MIT in the 1970s revolve around extremely functional Lisp code (i.e., Scheme). Logo is a weird little language, but it is a descendant of Lisp. So is Ludus.
|
||||
|
||||
Also, we believe that Ludus's immutable bindings and persistent or immutable data structures and careful approach to manipulating state lead to a lot of good pedagogical results. Learning a programming language involves learning how to model what's going on inside the computer; Ludus, we think, makes that both simpler and easier.
|
||||
|
||||
If you're looking for cognate languages, Ludus takes a _lot_ of design inspiration from Clojure and Elixir (which itself took a lot from Clojure). Clojure and Elixir are great! If you're asking why you should use Ludus instead of them, you're already at the point where you should be using them. Ludus is, maybe, for the people whom you'd like to work with in 5 years at your Pheonix shop (but even then, probably not).
|
||||
|
||||
### Status
|
||||
Pre-alpha, still under active development. Lots of things change all the time. Definitely some broken bits, especially around processes.
|
||||
|
||||
### Use
|
||||
Current emphasis is on the web version: https://web.ludus.dev.
|
||||
|
||||
### Main features
|
||||
* Expression-oriented: everything returns a value
|
||||
* Pattern matching in all the places
|
||||
* No operators: everything is called as a function
|
||||
* Easy-peasy partial application with placeholders
|
||||
* Function pipelines
|
||||
* Persistent or immutable data structures
|
||||
* Careful, explicit state management using `box`es
|
||||
* Clean, concise, expressive syntax
|
||||
* Value-based equality; only functions are reference types
|
||||
* Persistent, immutable data structures, à la Clojure
|
||||
|
||||
### `Hello, world!`
|
||||
Ludus is a scripting language. At current it does not have a good REPL. Our aim is to get interactive coding absolutely correct, and our efforts in [ludus-web](https://github.com/thinking-with-computers/ludus-web) are currently under way to surface the right interactivity models for Ludus.
|
||||
|
||||
Either:
|
||||
```
|
||||
"Hello, world!"
|
||||
```
|
||||
`=> "Hello, world!"`
|
||||
|
||||
Ludus scripts (and blocks) simply return their last expression; this script returns the bare string and exits.
|
||||
|
||||
Or:
|
||||
```
|
||||
print! ("Hello, world!")
|
||||
```
|
||||
```
|
||||
=> Hello, world!
|
||||
=> :ok
|
||||
```
|
||||
|
||||
Here, we use the `print!` function, which sends a string to a console (`stdout` on Unix, or a little console box on the web). Because `print!` returns the keyword `:ok` when it completes, that is the result of the last expression in the script--and so Ludus also prints this.
|
||||
|
||||
### Some code
|
||||
Fibonacci numbers:
|
||||
```
|
||||
& fibonacci!, with multi-clause fns/pattern matching
|
||||
|
||||
fn fib {
|
||||
"Returns the nth fibonacci number."
|
||||
(1) -> 1
|
||||
(2) -> 1
|
||||
(n) -> add (
|
||||
fib (sub (n, 1))
|
||||
fib (sub (n, 2)))
|
||||
}
|
||||
|
||||
fib (10) &=> 55
|
||||
```
|
||||
|
||||
### More on Ludus
|
||||
See the [language reference](language.md) and [the documentation for the prelude](prelude.md).
|
||||
A Rust implementation of Ludus.
|
|
@ -1,31 +0,0 @@
|
|||
fn agent (val) -> receive {
|
||||
(:set, new) -> agent (new)
|
||||
(:get, pid) -> {
|
||||
send (pid, (:response, val))
|
||||
agent (val)
|
||||
}
|
||||
(:update, f) -> agent (f (val))
|
||||
}
|
||||
|
||||
fn agent/set (pid, val) -> {
|
||||
send (pid, (:set, val))
|
||||
val
|
||||
}
|
||||
|
||||
fn agent/get (pid) -> {
|
||||
send (pid, (:get, self ()))
|
||||
receive {
|
||||
(:response, val) -> val
|
||||
}
|
||||
}
|
||||
|
||||
fn agent/update (pid, f) -> {
|
||||
send (pid, (:update, f))
|
||||
agent/get (pid)
|
||||
}
|
||||
|
||||
let myagent = spawn! (fn () -> agent (42))
|
||||
|
||||
print! ("incrementing agent value to", agent/update (myagent, inc))
|
||||
|
||||
:done!
|
1674
assets/prelude.ld
1674
assets/prelude.ld
File diff suppressed because it is too large
Load Diff
1380
assets/test_prelude.ld
Normal file
1380
assets/test_prelude.ld
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -1,469 +0,0 @@
|
|||
# Ludus for programmers
|
||||
## A brief introduction
|
||||
|
||||
Ludus is mostly understood by its research and design team as a language for _learners_.
|
||||
It is a _pedagogical_ language, whose primary purpose is to lead students to critical encounters with the history and present of computing.
|
||||
The design principles, then, lead with learnability as well as making certain key examples in the history of computing easy.
|
||||
|
||||
Because of that, Ludus has some weird features.
|
||||
It will likely not feel especially familiar, particularly if you have not written funtional code before.
|
||||
We encourage you to feel disoriented by it, and to lean into that disorientation.
|
||||
Instead of trying to write code like you have in the past, write code like Ludus wants you to.
|
||||
|
||||
There are two big influences on Ludus.
|
||||
In terms of historical languages, Ludus draws a lot from Logo and Scheme.
|
||||
In terms of contemporary languages, Ludus has deep affinities with Elixir and Clojure.
|
||||
To put it in an abstraction cluster, Ludus is a dynamically typed, extremely strict functional programming language with persistent data structures, deep immutability, and pattern matching.
|
||||
None of these are especially mainstream.
|
||||
|
||||
It is not "batteries included," but rather offers a quite minimalistic set of capabilities.
|
||||
These are devised, as I noted above, to make encountering key concepts from the history of comptuing easy.
|
||||
But beyond that, Ludus is extremely minimal, in the tradition of Scheme and Logo.
|
||||
The profound pedagogical perspective behind Scheme and Logo is that building the things you want is an important motivator for learning how to make computers do things.
|
||||
Ludus follows in this path.
|
||||
|
||||
If you've mostly written object-oriented code, Ludus will, frankly, feel weird.
|
||||
And that's awesome.
|
||||
|
||||
### Ludus is expression based
|
||||
Ludus has no statements, only expressions.
|
||||
Every expression returns a value, including conditional forms like `if`, `when`, and `match`.
|
||||
That said, not every kind of expression can go anywhere.
|
||||
|
||||
In Ludus, different types of expressions are called _forms_, riffing on the grand Lisp tradition.
|
||||
|
||||
### Ludus is dynamically typed
|
||||
Like its inspirations, Elixir and Clojure and the whole family of Lisps, Ludus is dynamically typed.
|
||||
It is _strictly_ typed, however.
|
||||
Unlike Javascript, Ludus will never convert between values of one type or another.
|
||||
Ludus has the following types:
|
||||
* `:nil`: The type of `nil`, Ludus's name for nothing.
|
||||
* `:bool`: Boolean--`true` or `false`.
|
||||
* `:number`: IEEE-754 64-bit floating point numbers. Ludus does not have an integer type. That said, Ludus avoids `NaN` as much as possible.
|
||||
* `:string`: UTF-8 strings.
|
||||
* `:keyword`: Keywords are self-identical atoms, evaluating only to themselves. The equivalent of a `Symbol` in Javascript (or a keyword in Clojure or an atom in Elixir). (The types in this list--and in Ludus--are represented as keywords.)
|
||||
* `:tuple`: Fixed-length, fully immutable collections of between zero and 7 values (inclusive). Tuples are comma-or-newline separated values, surrounded by parentheses: `(1, 2, 3)`.
|
||||
* `:list`: Persistent, immutable ordered list of any number of Ludus values. Lists are comma-or-newline separated values, surrounded by square brackets: `[:foo, :bar, :baz]`.
|
||||
* `:dict`: Persistent, immutable associative collection of keyword keys and any Ludus values. Dicts are comma-or-newline separated keyword-and-value pairs, introduced by `#{` and closed with a curly brace: `#{:a 1, :b 2}`. Keys may be keywords or strings.
|
||||
* `:fn`: Functions!
|
||||
* `:box`: A holder for any value, which can change over time. A cognate of Clojure's atom. This is the only data type in Ludus that can hold mutable state.
|
||||
|
||||
At current, three other types are planned but not implemented: `:set`, `:pkg`, `:process`.
|
||||
|
||||
Ludus does not allow creating new nominal types.
|
||||
|
||||
### Ludus has a weird comment character
|
||||
It uses the ampersand--`&`--to introduce comments.
|
||||
It does not have mulitline comments.
|
||||
|
||||
### Ludus does not have variables, it has bindings
|
||||
The basic form of assignment in Ludus looks very familiar:
|
||||
|
||||
```
|
||||
let foo = 42
|
||||
let bar = :quux
|
||||
let baz = "hello, world"
|
||||
```
|
||||
|
||||
These are _let bindings_.
|
||||
|
||||
#### Let bindings are extremely immutable
|
||||
They may not change.
|
||||
In addition, you may not shadow let bindings.
|
||||
You may not shadow a binding, like in Rust, where you can re-use the name and discard the old binding.
|
||||
You may also not bind the same name in a nested scope (e.g., inside a function).
|
||||
Once you bind a name, it is forever bound to that value.
|
||||
The value in this is that language learners need (almost) never wonder what value a name is bound to, since it can never change.
|
||||
|
||||
Except, of course, with function calls.
|
||||
|
||||
#### The left-hand side of a let binding is a _pattern_
|
||||
Ludus makes extensive use of pattern matching.
|
||||
The left-hand side of a let binding need not be a simple name.
|
||||
A simple name is only one kind of pattern.
|
||||
|
||||
For example, this is valid Ludus:
|
||||
```
|
||||
let foo = 42
|
||||
let 42 = foo
|
||||
let nil = nil
|
||||
```
|
||||
The second line does nothing _except_ match the value on the left hand side to the value on the right hand side.
|
||||
If a let binding does not match, e.g., `let 1 = 2`, then Ludus will panic.
|
||||
|
||||
Patterns can also be used to destructure all Ludus collections:
|
||||
```
|
||||
let (:ok, x) = (:ok, 42) & tuple pattern: x is now 42
|
||||
let [l, m, ...] = [1, 2, 3] & list pattern: l = 1, m = 2
|
||||
let #{a, b} = #{:a 1, :b 2} & dict pattern: a = 1, b = 2
|
||||
```
|
||||
#### Collection patterns are exact & complete, unless otherwise specified
|
||||
In the second line in the example above, the pattern `[l, m, ...]` includes a splat pattern (or splattern).
|
||||
If we had written `let [l, m] = [1, 2, 3]`, Ludus would have panicked with `no match`.
|
||||
There are three list members on the right, only two on the left.
|
||||
The splat, `...` (or ellipsis) matches "anything else in the list."
|
||||
You may also include a name after the splat, which will be bound to "anything else in the list," e.g.
|
||||
```
|
||||
let [head, ...tail] = [1, 2, 3, 4, 5]
|
||||
head &=> 1
|
||||
tail &=> [2, 3, 4, 5]
|
||||
```
|
||||
|
||||
#### The placeholder is a special pattern
|
||||
A placholder pattern, `_`, matches against anything but does not bind a name.
|
||||
Also, you may name your placholders, e.g., `_ignored`, but that is for the programmer only.
|
||||
Named or unnamed placeholder patterns are strictly equivalent.
|
||||
|
||||
### Ludus panics
|
||||
Ludus has exactly one type of runtime error: a panic.
|
||||
Panics will always crash the program.
|
||||
You cannot `catch` a panic.
|
||||
|
||||
You can raise a panic thusly:
|
||||
```
|
||||
panic! "oh shit"
|
||||
```
|
||||
`panic!` may only take a single value, but that value can be a collection.
|
||||
|
||||
Panics bring down processes, and any linked processes. More on processes below.
|
||||
|
||||
### Almost everything is a function
|
||||
Ludus does not have operators.
|
||||
In the grand Lisp tradition, all operations look like (and, for the most part, substantively are) function calls.
|
||||
|
||||
* To add two numbers in Ludus: `add (1, 2) &=> 3`.
|
||||
* To subtract one number from another: `sub (2, 1) &=> 1`
|
||||
* To determine, are two things equal?: `eq? (3, "three") &=> false`
|
||||
|
||||
### The Prelude is loaded before every evaluation
|
||||
The Ludus Prelude is its standard library, and all functions in the Prelude are available in every Ludus script.
|
||||
Consult the [Prelude documentation](./prelude.md) for information for all functions in Prelude.
|
||||
Everything you'll want to do with Ludus involves the Prelude in some way.
|
||||
|
||||
Note that most Prelude function names can, in fact, be shadowed by local bindings in a script.
|
||||
That said, there are several functions that, for optimization reasons, are "builtin," whose names may never be used, e.g., `add`, `sub`, `eq?`, `inc`, `dec`, and so on.
|
||||
|
||||
#### Boolean "functions" are actually "special forms"
|
||||
`and` and `or` are special, in that they are compiled differently than other functions.
|
||||
Their arguments are evaluated lazily, rather than eagerly, so they can short-circuit (and prevent panics).
|
||||
Because of that, they may not be referred to or passed around like other functions.
|
||||
|
||||
### Ludus lists and dicts are persistent
|
||||
Dicts and lists are persistent.
|
||||
This means you cannot mutate them.
|
||||
However, you can still add things to a list--you just get back a _new_ list with the value added:
|
||||
```
|
||||
let foo = [1, 2, 3]
|
||||
let bar = append (foo, 4) &=> [1, 2, 3, 4]
|
||||
|
||||
let baz = #{:a 1, :b 2}
|
||||
let quux = assoc (baz, :c, 3) &=> #{:a 1, :b 2, :c 3}
|
||||
|
||||
foo &=> [1, 2, 3]
|
||||
baz &=> #{:a 1, :b 2}
|
||||
```
|
||||
Persistent data structures are wonderful, and use a lot of computer science magic to make them competitive in terms of performance: they use "structural sharing" and attempt "opportunistic mutation."
|
||||
|
||||
### Ludus has three conditional forms
|
||||
The three conditional forms in Ludus are `if`, `when`, and `match`.
|
||||
|
||||
#### The `if` form
|
||||
Ludus's base conditional form is `if`:
|
||||
```
|
||||
if foo then bar else baz
|
||||
```
|
||||
Does what you'd expect!
|
||||
But with two caveats.
|
||||
|
||||
(Before the caveats: you can put newlines before `then` and `else`.)
|
||||
|
||||
#### Falsy values: `nil` and `false`
|
||||
The condition (`foo` in the example above) is evaluated not strictly as `true` or `false`.
|
||||
Ludus "falsy" values are `nil` and `false`.
|
||||
Everything else is truthy, including `0` and `()` (the empty tuple), and `""` (the empty string).
|
||||
This holds across anywhere in the language you are dealing with notions of truth and falsity: `if` and `when` forms, `guard` expressions in `match` forms, `and` and `or`, etc.
|
||||
|
||||
#### Both `then` and `else` are obligatory
|
||||
`if` forms in Ludus _must_ have both `then` and `else` branches.
|
||||
This is because every expression in Ludus must return a value.
|
||||
If you want to throw away a value, you can do that, but you'll need something like `else nil` or `else :nothing`.
|
||||
|
||||
#### The `when` form
|
||||
If you have multiple conditions you'd like to chain together, `when` forms are what you want.
|
||||
(Ludus does not have an `else if` form, although you can certainly chain `if` forms together.)
|
||||
`when` puts multiple clauses together, each of which has a left-hand condition expression and a right-hand body expression: `<condition expr> -> <body expr>`
|
||||
Ludus will evaluate the left-hand expression, and, if it's truthy, evaluate and return the corresponding right-hand expression:
|
||||
```
|
||||
when {
|
||||
eq? (1, 2) -> :nope
|
||||
eq? (3, 4) -> :not_this_either
|
||||
eq? (0, 0) -> :this!
|
||||
} &=> :this!
|
||||
```
|
||||
If no clause in a when form has a truthy left-hand side, Ludus panics.
|
||||
|
||||
Any truthy value will do if you want the equivalent of an `else` branch.
|
||||
By convention, `:else` is used as the catch-all at the end of a match form.
|
||||
|
||||
#### The `match` form
|
||||
A `match` form is the equivalent of a `switch` statement in C-family languages.
|
||||
It is much more powerful, however.
|
||||
`match` is much beloved by functional programmers.
|
||||
`match` forms are similar to `when` forms, but they require a value--a "scrutinee."
|
||||
And, in place of expressions on the left-hand side of their clauses, they have patterns: `<pattern> -> <expr>`.
|
||||
They attempt to match the value against each pattern until there is a match.
|
||||
If no clause matches, then Ludus panics.
|
||||
|
||||
This is an extremely common pattern in Ludus:
|
||||
```
|
||||
let might_fail = (:ok, 42)
|
||||
match might_fail with {
|
||||
(:ok, value) -> print! ("got {value}!")
|
||||
(:err, _) -> print! ("the thing failed")
|
||||
} &=> :ok, prints "got 42!"
|
||||
```
|
||||
|
||||
##### Match clauses may have a guard expression
|
||||
A match clause may also have a guard expression.
|
||||
Afer the pattern and before the arrow, you may put `if <expr>`.
|
||||
(Here you may not use `then` or `else`.)
|
||||
Bindings made in the pattern are valid in that expression.
|
||||
If the expression is truthy, then that's a match.
|
||||
If it's falsy, no match:
|
||||
```
|
||||
let foo = 42
|
||||
let bar = 23
|
||||
match bar with {
|
||||
x if even? (x) -> :even
|
||||
x if eq? (foo, x) -> :foo
|
||||
_ -> :odd_not_foo
|
||||
} &=> :odd_not_foo
|
||||
```
|
||||
|
||||
### Ludus groups expressions together with blocks
|
||||
A block groups expressions together.
|
||||
Ludus blocks must have at least one expression (because everything in Ludus must return a value).
|
||||
A block evaluates to its last expression.
|
||||
Expressions are separated by one or more terminators--newlines or semicolons.
|
||||
Use curly braces to form a block:
|
||||
```
|
||||
if true
|
||||
then {
|
||||
:first; :second & these are two different expressions
|
||||
:third
|
||||
}
|
||||
else {
|
||||
:nothing
|
||||
} &=> :third
|
||||
```
|
||||
Blocks can go most anywhere single expressions can go.
|
||||
|
||||
### Ludus has synthetic expressions
|
||||
We have already seen function calls, e.g., `add (1, 2)`.
|
||||
This is a _synthetic_ expression, which is a chained combination of bound names, tuples, keywords, and method calls (more on these below).
|
||||
The root of a synthetic expression may be either a name or a keyword.
|
||||
Subsequent terms must either be tuples or keywords.
|
||||
They are evaluated by applying the second term to the first, then applying the third term to the result of that first application, and applying the fourth to the second result, and so on.
|
||||
Applying a tuple will call something as a function: `add (1, 2)`.
|
||||
Applying a keyword will access the value stored at that key in a dict: `foo :bar`.
|
||||
|
||||
These may be chained arbitrarily.
|
||||
Take, for example, `foo :bar (1, 2) :baz`.
|
||||
This accesses `:bar` on `foo`, applies the arguments `(1, 2)` to that value (presumably a function), and then access `:baz` on value returned by that function.
|
||||
|
||||
#### Keywords may be called as functions
|
||||
Following Clojure's example, you may call a keyword as a function: `foo :bar` and `:bar (foo)` are strictly equivalent.
|
||||
Keywords may also be called when they are not keyword literals but bound names, e.g., this is valid:
|
||||
|
||||
```
|
||||
let foo = :foo
|
||||
let bar = #{:foo :baz}
|
||||
foo (bar) &=> :baz
|
||||
```
|
||||
|
||||
### Ludus has function pipelines
|
||||
In addition to normal function application, Ludus also has function pipelines, equivalent to Elixir's pipelines or Clojure's thread macros.
|
||||
In these, the first term is applied, as a single argument, to the second. The result of that is then applied, as a single argument, to the third, and so on.
|
||||
|
||||
Function pipelines are introduced by the reserved word, `do`.
|
||||
These two expressions are exactly equivalent:
|
||||
```
|
||||
do foo > bar >
|
||||
baz > quux
|
||||
quux (baz (bar (foo)))
|
||||
```
|
||||
Newlines may be inserted _after_ the `>` pipeline symbol, not before.
|
||||
Note that a line ending with the pipeline symbol will "eat" the line after it, even if separated by many terminators, so be careful.
|
||||
|
||||
Because keywords can be called like functions, bare keywords may be used in function pipelines.
|
||||
|
||||
### Ludus has partial function application
|
||||
Any function in Ludus may be partially applied by using the placholder, `_`, in place of an argument.
|
||||
Doing so returns a function that takes a single argument.
|
||||
When that function is called, it calls the original function with that argument put in the placeholder's position.
|
||||
Here's a simple example:
|
||||
```
|
||||
let double = mult (2, _)
|
||||
double (3) &=> 6
|
||||
double (12) &=> 24
|
||||
```
|
||||
Partially applied functions play very nicely with pipelines:
|
||||
```
|
||||
let double = mult (2, _)
|
||||
let mynums = [1, 2, 3, 4, 5, 6]
|
||||
do mynums >
|
||||
filter (even?, _) > &-> [2, 4, 6]
|
||||
map (double, _) &=> [4, 8, 12]
|
||||
```
|
||||
|
||||
### Ludus function definitions
|
||||
Functions come in three flavours, all of which have a concept of a function clause.
|
||||
A function clause is a special case of a match clause: it has a _tuple_ pattern on its left hand side (since we call functions with tuples).
|
||||
Otherwise,
|
||||
|
||||
#### Anonymous lambdas
|
||||
An anonymous lambda is the `fn` reserved word, followed by a function clause:
|
||||
```
|
||||
let double = fn (x) -> mult (x, 2)
|
||||
double &=> fn anon.
|
||||
double (13) &=> 26
|
||||
```
|
||||
|
||||
#### Named functions
|
||||
Named functions are exactly the same as anonyomous lambdas, but they have a name between `fn` and the clause:
|
||||
```
|
||||
fn double (x) -> mult (x, 2)
|
||||
double &=> fn double
|
||||
double (-4) &=> -8
|
||||
```
|
||||
|
||||
#### Compound functions
|
||||
Compound functions have multiple clauses, separated off by curly braces:
|
||||
```
|
||||
fn foo? {
|
||||
("foo") -> true
|
||||
(:foo) -> true
|
||||
(_) -> false
|
||||
}
|
||||
foo? (:bar) &=> false
|
||||
```
|
||||
There's a very close relationship between match forms and function definitions.
|
||||
|
||||
##### docstrings
|
||||
A compound function may, optionally, take a string before any of its clauses, that serves as documentation for the function:
|
||||
```
|
||||
fn foo? {
|
||||
"Tells if its argument is a `foo`."
|
||||
("foo") -> true
|
||||
(:foo) -> true
|
||||
(_) -> false
|
||||
}
|
||||
```
|
||||
Ludus will print the documentation for a function by means of the `doc!` function.
|
||||
`doc!` will also display all the patterns in a function's clauses.
|
||||
|
||||
### Ludus has a convention of "commands": they end with a bang
|
||||
By convention, Ludus functions that end in an exclamation point have side effects.
|
||||
These are called _commands_.
|
||||
`doc!` is a command; so is `print!`.
|
||||
|
||||
Ludus commands typically return the keyword `:ok` rather than `nil`.
|
||||
|
||||
Much of Ludus involves manipulating turtle graphics commands, `forward!`, and so on.
|
||||
|
||||
### Ludus has loops, but you should probably use recursion
|
||||
Ludus, in the grand Lisp (and Logo) tradition, eschews looping constructs in favour of functional recursion.
|
||||
Ludus is tail-call optimized, which means that recursion, even mutual recursion, is as fast as looping.
|
||||
The `loop` form, anyway, isn't anything like you're expecting; it's basically function calls.
|
||||
|
||||
Two examples of factorial, looping and recurisve:
|
||||
```
|
||||
loop (6, 1) with {
|
||||
(0, acc) -> acc
|
||||
(n, acc) -> recur (dec (n), mult (n, acc))
|
||||
} &=> 720
|
||||
|
||||
fn fact {
|
||||
(n) -> fact (n, 1)
|
||||
(0, acc) -> acc
|
||||
(n, acc) -> fact (dec (n), mult (n, acc))
|
||||
}
|
||||
|
||||
fact (6) &=> 720
|
||||
```
|
||||
The difference between these is that Ludus will throw a compile error if `recur` isn't in tail position.
|
||||
In addition, all clauses in a loop form, and all invocations of `recur` must have the same arity and may not have splats in patterns, whereas functions may have clauses of arbitrary arity.
|
||||
|
||||
### Ludus has multiple "levels" of expressions
|
||||
Not all Ludus expressions can appear anywhere you need an expression.
|
||||
Ludus has four levels of expressions that restrict where they may go: simple, nonbinding, expressions, and toplevel.
|
||||
* _Simple_ expressions include all literals as well as bare names, synthetic expressions, anonymous `fn` lambdas, `do` forms, and `panic`s. They may go anywhere you expect an expression, e.g. in the condition position in if or when forms. But in these positions, you may not use, say, another conditional form, nor bind a name.
|
||||
* _Nonbinding_ forms include all expressions _except_ those that bind a name. To simple expressions, nonbinding expressions extend to conditional forms (`if`, `when`, `match`, `receive`).
|
||||
* _Expressions_ (tout court) include all Ludus expressions, including those that bind names: `let`, named `fn`s, and `box`.
|
||||
|
||||
### Ludus has carefully managed state
|
||||
At some point, you need state.
|
||||
(You need far less than you think!)
|
||||
For that, you need a `box`.
|
||||
A box holds a value that can change over time.
|
||||
It can hold any other Ludus value, including a box.
|
||||
Getting a value out of a box isn't as simple, however, as using its name.
|
||||
The name is bound to the _box_, not its value.
|
||||
To get the value out of a box, you use the `unbox` function:
|
||||
```
|
||||
box foo = 42
|
||||
foo &=> box [42]
|
||||
unbox (foo) &=> 42
|
||||
```
|
||||
To change the value in a box, you use either the `store!` command, or the `update!` command.
|
||||
`store!` takes a box and a value, and simply puts the new value in the box.
|
||||
`update!` (not to be confused with the function `update`) takes a box and a function, and updates the value in the box by applying the function to the value in the box:
|
||||
```
|
||||
box foo = 42 &=> box [42]
|
||||
store! (foo, 23) &=> box [23]
|
||||
update! (foo, add(13, _)) &=> box [36]
|
||||
unbox (foo) &=> 36
|
||||
```
|
||||
|
||||
#### Boxes are not variables
|
||||
We have put the section on boxes last in this introduction because boxes are _not_ variables.
|
||||
Most state can actually be, and within Ludus, absolutely ought to be, modeled not with boxes but with recursive functions.
|
||||
|
||||
Consider the factorial example from earlier.
|
||||
|
||||
A straightforward Javascript implementation might look like this:
|
||||
```javascript
|
||||
function fact (n) {
|
||||
let acc = 1;
|
||||
while n > 1 {
|
||||
acc = n * acc;
|
||||
n--;
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
```
|
||||
You'll note that the `while` statement doesn't have an easy equivalent in Ludus.
|
||||
But if you were really stubborn about wanting to twised boxes into variables, you could do something like this:
|
||||
```
|
||||
fn fact (n) -> {
|
||||
box acc = 1
|
||||
loop (n) with (m) -> if lt? (m, 1)
|
||||
then unbox (acc)
|
||||
else {
|
||||
store! (acc, mult (m, unbox (acc)))
|
||||
recur (dec (m))
|
||||
}
|
||||
}
|
||||
```
|
||||
Let me tell you, this is _wild_ Ludus.
|
||||
The `loop` there is very weird indeed.
|
||||
|
||||
`box`es, also, are much, much slower than variables.
|
||||
|
||||
The short version is, if you can possibly avoid it--and you probably can--don't use boxes.
|
||||
|
||||
The more complex version is this:
|
||||
The functional and immutable nature of Ludus will change your ideas about programming.
|
||||
This is part of the point.
|
||||
|
||||
(More to come...)
|
513
doc/language.md
513
doc/language.md
|
@ -1,513 +0,0 @@
|
|||
# Ludus language reference
|
||||
|
||||
This is not intended for beginners, but to be a language overview for experienced programmers. That said, it may help beginners orient themselves in the language.
|
||||
|
||||
## Comments
|
||||
Ludus's comment character is `&`. Anything after an ampersand on a line is ignored. There are no multiline comments.
|
||||
|
||||
## Atomic values
|
||||
Ludus has four types of atomic values.
|
||||
|
||||
### `nil`
|
||||
`nil` is Ludus's representation of nothing. In the grand Lisp tradition, Ludus can, and occasionally does, use `nil`-punning. Its type is `:nil`.
|
||||
|
||||
### Booleans
|
||||
`true` and `false`. That said, in all conditional constructs, `nil` and `false` are "falsy," and everything else is "truthy." Their type is `:boolean`.
|
||||
|
||||
### Numbers
|
||||
Ludus has numbers, which are IEEE-754 64-bit floats. Numbers are more complicated than you think, probably.
|
||||
|
||||
Number literals in Ludus are either integers or decimal floating point numbers, e.g. `32.34`, `42`, `-0.23`. Underscores in numbers are ignored, and can be used to separate long numbers, e.g. `1_234_567_890`.
|
||||
|
||||
Numbers' type is `:number`.
|
||||
|
||||
### Keywords
|
||||
Ludus keywords begin with a colon and a letter, e.g. `:keyword`. Types are represented as keywords. Some functions take an optional units argument as a keyword, e.g. `:radians`. Keywords are also used as keys for associative collections. Keywords' type is `:keyword`.
|
||||
|
||||
Keywords must begin with an ASCII upper- or lower-case letter, and can then include any letter character, as well as `_`, `/`, `!`, `?`, and `*`.
|
||||
|
||||
## Strings
|
||||
Ludus strings are UTF-8 strings, and only use double quotes. Strings may be multiline. For example, this is a string: `"foo"`. So is this:
|
||||
|
||||
```
|
||||
"foo
|
||||
|
||||
|
||||
bar baz"
|
||||
```
|
||||
Strings use backslashes for escapes, including `\n` for newline, `\t` for tab, `\"` for a double quote, and `\{` for an open curly brace (see below on interpolation).
|
||||
|
||||
Strings' type is `:string`.
|
||||
|
||||
### String interpolation
|
||||
Strings may also insert a string representation of any Ludus value that is bound to a name, by inserting that name in curly braces. To wit,
|
||||
```
|
||||
let foo = :foo
|
||||
let bar = 42
|
||||
let baz = [1, 2, 3]
|
||||
"{foo} {bar} {baz}" &=> ":foo 42 1, 2, 3"
|
||||
```
|
||||
Interpolations may _not_ be arbitrary expressions: only bound names may be used in interpolations.
|
||||
|
||||
## Collections
|
||||
Ludus has a few different types of collections, in increasing order of complexity: tuples, lists, sets, dicts, and packages. All collections are immutable.
|
||||
|
||||
#### Separators
|
||||
In all collection literals, members are written with a separator between them. On the same line, use a comma; or a newline will also separate elements. You may use as many separators as you wish at any point inside a collection or pattern. `(,,,,,,,3,,4,,,,,,)` and `(3, 4)` are the same value.
|
||||
|
||||
#### Efficiency
|
||||
At the current moment, Ludus collections are all copy-on-write; this means that Ludus is _not_ performant with large collections. Eventually, Ludus will have Clojure-style persistent, immutable collections.
|
||||
|
||||
### Tuples
|
||||
Tuples are fully-immutable, ordered collections of any kinds of values, delimited by parentheses, e.g. `(1, :a, "foo")`. At current, they have no length limit (although they eventually will). Unlike in some languages, tuples can be empty or contain a single element: `()` and `(:foo)` are both just fine. Tuples largely cannot be manipulated functionally; they must be written as literals and unpacked using pattern matching. They can, however, be converted to lists, either through pattern matching or the `list` function. Their type is `:tuple`.
|
||||
|
||||
### Lists
|
||||
Lists are persistent and immutable ordered collections of any kinds of values, delimited by square braces, e.g. `[1, :a, "foo"]`. Their type is `:list`.
|
||||
|
||||
Lists may be combined using splats, written with ellipses, e.g., `[...foo, ...bar]`.
|
||||
|
||||
### Sets
|
||||
Sets are persistent and immutable unordered collections of any kinds of values, which can only contain one instance of any given value. They are written similarly to ordered collections: `${1, :a, "foo"}`. Their type is `:set`.
|
||||
|
||||
### Dictionaries, or dicts
|
||||
Dicts are persistent and immutable associative collections of any kinds of values. Dicts use keywords as keys (and cannot use any other kind of Ludus value as a key, not even strings), but can store any values. Dict literals are written as keyword-value pairs: `#{:a 1, :b false}`. Single words may be used as a shorthand for a key-value pair. Accessing a key that holds no value returns `nil`. Their type is `:dict`.
|
||||
|
||||
### Packages
|
||||
Packages are immutable collections of bindings. They may only be described at the top level of a script, and their names must begin with a capital letter. Accessing a key that has no value on a package results in a validation error. They may not be accessed using functions, but only direct keyword access. Their type is `:pkg`.
|
||||
|
||||
They are written with the form `pkg`, then a package name, beginning with a capital letter, that will be bound as their name, and then an associative structure (pairs or word shorthands), delimited by `{}`, e.g.:
|
||||
|
||||
```
|
||||
pkg Foo {
|
||||
:bar "bar"
|
||||
:baz 42
|
||||
quux
|
||||
}
|
||||
```
|
||||
|
||||
### Working with collections
|
||||
Ludus names are bound permanently and immutably. Collections are immutable. How do you add something to a list or a dict? How do you get things out of them?
|
||||
|
||||
Ludus provides functions that allow working with persistent collections. They're detailed in [the Prelude](prelude.md). That said, all functions that modify collections take a collection and produce the modified collection _as a return value_, without changing the original collection. E.g., `append ([1, 2, 3], 4)` will produce `[1, 2, 3, 4]`, but the original list is unchanged. (For dicts, the equivalent is `assoc`.)
|
||||
|
||||
## Expressions
|
||||
Ludus is an expression-based language: all forms in the language are expressions and return values, except `panic!`. That said, not all expressions may be used everywhere.
|
||||
|
||||
### Terminating expressions
|
||||
Expressions in scripts and blocks are terminated by a newline or a semicolon. In compound forms, like, `if`, the terminator comes after the `else` expression.
|
||||
|
||||
In forms with multiple clauses surrounded by curly braces (i.e., function bodies, `match`, `when`, etc.), you may separate clauses with semicolons as well as newlines.
|
||||
|
||||
### Toplevel expressions
|
||||
Some expressions may only be used in the "top level" of a script. Because they are the toplevel, they are assured to be statically knowable. These include: `pkg`, `ns`, `use`, `import`, and `test`. (NB: not all of these are yet implemented.)
|
||||
|
||||
### Non-binding expressions
|
||||
Some forms may take any expression that does _not_ [bind a name](#words-and-bindings), for example, any entry in a collection, or the right-hand side of a `let` binding. This is because binding a name in some positions is ambiguous, or nonsensical, or leads to unwarranted complications.
|
||||
|
||||
### Simple expressions
|
||||
Many compound forms will only accept "simple" expressions. Formally, simple expressions are either literal (atomic, string, or collection literals) or synthetic expressions. They are expressions which do not take sub-expressions: no `if`, `when`, `match`, etc. (`do` expressions are currently not simple, but that may be revised.)
|
||||
|
||||
## Words and bindings
|
||||
Ludus uses _words_ to bind values to names. Words must start with a lower case ASCII letter, and can subsequently include any letter character (modulo backing character encoding), as well as , `_`, `/`, `?`, `!`, and `*`.
|
||||
|
||||
Ludus binds values to names immutably and permanently: no name in the same scope may ever be re-bound to a different value. (Although see [boxes](#boxes-and-state), below.
|
||||
|
||||
Attempting to use an unbound name (a word that has not had a value bound to it) will result in a validation error, and the script will not run.
|
||||
|
||||
### `let` bindings: a very short introduction
|
||||
Ludus's basic binding form is `let`:
|
||||
|
||||
```
|
||||
let foo = :bar & `foo` is now bound to `bar` for the rest of the scope.
|
||||
|
||||
let foo = :baz & Validation error: name foo was bound in line 1
|
||||
```
|
||||
|
||||
`let` bindings are more complex; we will return to these below.
|
||||
|
||||
## Patterns
|
||||
Ludus makes extensive use of pattern-matching. Patterns do two jobs at once: they match values (or don't); and they bind names. The left-hand side of the examples just above in the `let` binding is not just a word: it is a pattern. Patterns also arise in conditional forms and function declarations.
|
||||
|
||||
### The placeholder: `_`
|
||||
The simplest pattern is the placeholder: it matches against anything, and does not bind a name. It is written as a single underscore: `_`, e.g., `let _ = :foo`.
|
||||
|
||||
#### Ignored names
|
||||
If you wish to be a bit more explict than using a placeholder, you can use an ignored name, which is a name that starts with an underscore: `_foo`. This is not bound, is not a valid name, and can be used however much you wish, even multiple times in the same pattern. It is, in fact, a placeholder, plus a reader-facing description.
|
||||
|
||||
### Literal patterns
|
||||
Patterns can be literal atomic values or strings: `0`, `false`, `nil`, `:foo`, etc. That means you can write `let 0 = 0` or `let :foo = :foo`, and, while nothing will happen, everything will be just fine.
|
||||
|
||||
Literals match against, well, literal values: if the pattern and the value are the same, they match! If not, they don't match.
|
||||
|
||||
Literal values do not bind anything.
|
||||
|
||||
### Word patterns
|
||||
Word patterns match against any value, and bind that value to the word as a name. The scope of that binding depends on the form the pattern is in. `let foo = :bar` binds `:bar` to `foo` for the rest of the scope.
|
||||
|
||||
#### Typed patterns
|
||||
Word patterns can, optionally, take a type, using the `as` reserved word, and the keyword representing the desired type: `let foo as :number = 42`.
|
||||
|
||||
### String patterns
|
||||
Ludus has a simple but powerful form of string pattern matching that mirrors string interpolation. Any word inside curly braces in a string will match against a substring of a string passed into a pattern.
|
||||
|
||||
```
|
||||
let i_am = "I am the walrus"
|
||||
|
||||
let "I {verb} the {noun}" = i_am
|
||||
(verb, noun) &=> ("am", "walrus")
|
||||
```
|
||||
|
||||
Note that such names may well be bound to empty strings: a match does not guarantee that there will be anything in the string. This is particularly relevant at the beginning and end of string patterns:
|
||||
|
||||
```
|
||||
let we_are = "We are the eggmen"
|
||||
let "{first}We {what}" = we_are
|
||||
(first, what) &=> ("", "are the eggmen")
|
||||
```
|
||||
|
||||
### Collection patterns
|
||||
Tuples, lists, and dicts can be destructured using patterns. They are written nearly identically to their literal counterparts. Collection patterns are composed of any number of simpler patterns or other collection patterns. They bind any names nested in them, match literals in them, etc.
|
||||
|
||||
#### Tuple patterns
|
||||
Tuple patterns are delimited using parens, using commas or newlines to separate any number of other patterns. Consider `let (x, y, z) = (1, 2, 3)`. `x`, `y`, and `z` are now bound to 1, 2, and 3, respectively.
|
||||
|
||||
The last item in a tuple pattern can be a splat--`...`--which either discards any remaining unenumerated values in a tuple, or binds them to a list. Without a splat, tuples patterns only match against tuples of the same length.
|
||||
|
||||
```
|
||||
let mytup = (1, 2, 3)
|
||||
let (x, _, y) = mytup & x is now 1, y is now 3
|
||||
let (a, ...) = mytup & a is now 1; a bare splat (without a name) is just fine
|
||||
let (_, ...cs) = mytup & cs is now [2, 3]
|
||||
let (p, q) = mytup & panic! no match
|
||||
let () = () & empty tuples are also patterns
|
||||
```
|
||||
|
||||
#### List patterns
|
||||
List patterns are identical to tuple patterns, but they are written using square braces. They also match against a specific number of members, and may take a splat in the last position, e.g. `let [first, ...rest] = [1, 2, 3]`.
|
||||
|
||||
Note that list patterns, like tuple patterns, match on explicit length. That means that if you are matching only the first items of a list, you must explicitly include a splat pattern, e.g. `let [first, second, ...] = [1, 2, 3, 4]`.
|
||||
|
||||
#### Dict patterns
|
||||
Dict patterns are written either with shorthand words, or keyword-pattern pairs. Consider: `let #{:a foo, :b 12, c} = #{:a 1, :b 12, :c 4}`. `foo` is now 1; `b` is now 12, `c` is now 4. If a dict does not hold a value at a particular key, there is no match.
|
||||
|
||||
Dict patterns may also use a splat as their last member: `let #{:a 1, ...b} = #{:a 1, :b 2, :c 3}` will bind `b` to `#{:b 2, :c 3}`.
|
||||
|
||||
Like tuple and list patterns, dict patterns without a splat at the end match only on exact equivalence on all keys.
|
||||
|
||||
## `let` bindings
|
||||
`let` bindings are the basic form of matching and binding in Ludus. It is written `let {pattern} = {non-binding expression}`. The pattern can be arbitrarily complex. If the left-hand side of a `let` binding does not match, Ludus will raise a panic, halting evaluation of the script.
|
||||
|
||||
## Scope and blocks
|
||||
Ludus is lexically scoped. Bindings are valid for the remainder of the scope they act on. To introduce a new scope, Ludus uses a block, a collection of expressions delimited by curly braces and separated by semicolons or newlines. The value of a block, as well as the value of a script, is the last expression in it. In `let foo = {:this; :set; :of; :expressions; "is actually"; :ignored }`, `foo` will be bound to `:ignored`.
|
||||
|
||||
That said, you can use bindings in blocks, which will not be available outside that block--but blocks can use bidnings from their parent scope:
|
||||
|
||||
```
|
||||
let outer = 42
|
||||
|
||||
let first = {
|
||||
let inner = 23
|
||||
add (outer, inner)
|
||||
} & first is now bound to 65
|
||||
|
||||
inner & Validation error: unbound name inner
|
||||
|
||||
```
|
||||
|
||||
### Shadowing
|
||||
Even though names are bound permanently in Ludus, it is perfectly possible (and occasionally quite useful) to "shadow" names from an enclosing scope.
|
||||
|
||||
```
|
||||
let x = 42
|
||||
|
||||
let y = {
|
||||
let first = x
|
||||
let x = 23 & this is fine
|
||||
let second = x
|
||||
add (first, second)
|
||||
} & y is now 65
|
||||
|
||||
```
|
||||
|
||||
## Conditional forms
|
||||
Ludus has a robust set of conditional forms, all of which are expressions and resolve to a single value.
|
||||
|
||||
### `if`
|
||||
`if` evaluates a condition; if the result of the condition is truthy, it evaluates is `then` branch; if the condition is falsy, it evaluates its `else` branch. Both branches must always be present. Newlines may come before `then` and `else`.
|
||||
|
||||
`if {simple expression} then {non-binding expression} else {non-binding expression}`
|
||||
|
||||
### `when`
|
||||
`when` is like Lisp's `cond`: it takes a series of clauses, separated by semicolons or newlines, delimited by curly braces. Clauses are written `lhs -> rhs`. `when` expressions are extremely useful for avoiding nested `if`s.
|
||||
|
||||
The left hand of a clause is a simple expression; the right hand of a clause is any expression. When the left hand is truthy, the right hand is evaluated, and the result of that evaluation is returned; no further clauses are evaluated. If no clause has a truthy left-hand value, then a panic is raised. In the example below, not the use of literal `true` as an always-matching clause.
|
||||
|
||||
```
|
||||
when {
|
||||
maybe () -> :something
|
||||
mabye_not () -> :something_else
|
||||
true -> :always
|
||||
}
|
||||
```
|
||||
|
||||
### `match`
|
||||
A `match` form is the most powerful conditional form in Ludus. It consists of a test expression, and a series of clauses. The test expression must be a simple expression, followed by `with`, and then a series of clauses separated by a semicolon or newline, delimited by curly braces.
|
||||
|
||||
```
|
||||
match may_fail () with {
|
||||
(:ok, value) -> calculate_result (value)
|
||||
(:err, msg) -> { log! (msg); recover_somehow () }
|
||||
}
|
||||
```
|
||||
|
||||
The left hand of a match clause is a pattern; the right hand is an expression: `pattern -> expression`. If the pattern matches the test expression of a clause, the expression is evaluated with any bindings from the pattern, and `match` form evaluates to the result of that expression.
|
||||
|
||||
If a test expression does not match against any clause's pattern, a panic is raised.
|
||||
|
||||
Ludus does not attempt to do any exhaustiveness checking on match forms; match errors are always runtime errors.
|
||||
|
||||
#### Guards
|
||||
`match` clauses may have a _guard expression_, which allows a clause only to match if the expression's result is truthy. In the previous example, consider that we might want different behaviour depending on the value of the number:
|
||||
```
|
||||
match may_fail () with {
|
||||
(:ok, value) if pos? (value) -> calculate_positive_result (value)
|
||||
(:ok, value) if neg? (value) -> calculate_negative_result (value)
|
||||
(:ok, 0) -> do_something_with_zero ()
|
||||
(:err, msg) -> { log! (msg); recover_somehow () }
|
||||
}
|
||||
```
|
||||
|
||||
## Functions
|
||||
Ludus is an emphatically functional language. Almost everything in Ludus is accomplished by applying functions to values, or calling functions with arguments. (These are precise synonyms.)
|
||||
|
||||
Functions have the type `:fn`.
|
||||
|
||||
### Calling functions
|
||||
Functions are called by placing a tuple with arguments immediately after a function name, e.g. `add (1, 2)` adds `1` and `2`. Because they are represented as tuples, arguments must be explicitly written; splats cannot be used to pass an arbitrary number of arguments to a function.
|
||||
|
||||
### Defining functions
|
||||
Functions have three increasingly complex forms to define them. All of them include the concept of a function clause, which is just a match clause whose left hand side must be a _tuple_ pattern.
|
||||
|
||||
#### Anonymous lambda
|
||||
An anonymous lambda is written `fn {tuple pattern} -> {expression}`, `fn (x, y) -> if gt? (x, y) then x else add (x, y)`. Lambdas may only have one clause.
|
||||
|
||||
#### Named functions
|
||||
A named function is identical to a lambda, with the one change that a word follows the `fn` reserved word: `fn {name} {tuple pattern} -> {expression}`. E.g., `fn add_1 (x) -> add (x, 1)`. The name of the function is bound for the remainder of the scope.
|
||||
|
||||
#### Compound functions
|
||||
Compound functions are functions that have multiple clauses. They must be named, and in place of a single clause after a name, they consist in one or more clauses, separated by semicolons or newlines, delimited by curly braces. Optionally, compound functions may have a docstring as their first element after the opening curly brace. The docstring explains the function's purpose and use, before any of the function clauses.
|
||||
|
||||
An example from Ludus's Prelude:
|
||||
|
||||
```
|
||||
fn some {
|
||||
"Takes a possibly `nil` value and a default value. Returns the value if it's not `nil`, returns the default if it's `nil`."
|
||||
(nil, default) -> default
|
||||
(value, _) -> value
|
||||
}
|
||||
```
|
||||
|
||||
### Closures
|
||||
Functions in Ludus are closures: function bodies have access not only to their specific scope, but any enclosing scope. That said, functions only have access to names bound _before_ they are defined; nothing is hoisted in Ludus.
|
||||
|
||||
### Mutual recursion and forward declaration
|
||||
If you try the following, you'll get a validation error:
|
||||
|
||||
```
|
||||
fn stupid_odd? {
|
||||
(0) -> false
|
||||
(x) -> supid_even? (dec (x)) & Validation error: unbound name stupid_even?
|
||||
}
|
||||
|
||||
fn stupid_even? {
|
||||
(0) -> true
|
||||
(x) -> stupid_odd? (dec (x))
|
||||
}
|
||||
```
|
||||
|
||||
To allow for mutual recursion, Ludus allows forward declarations, which are written `fn name` without any clauses. In the example above, we would simply put `fn stupid_even?` before we define `stupid_odd?`.
|
||||
|
||||
If you declare a function without defining it, however, Ludus will raise a validation error.
|
||||
|
||||
### The Prelude
|
||||
The Prelude is a substantial set of functions that is available in any given Ludus script. (It is, itself, just a Ludus file that has special access to host functions.) Because of that, a large number of functions are always available. The prelude documentation is [here](prelude.md).
|
||||
|
||||
### Partial application
|
||||
Functions in Ludus can be partially applied by using a placeholder in the arguments. Partial application may only use a single placeholder (partially applied functions are always unary), but it can be anywhere in the arguments: `let add_1 = add(1, _)` or `let double = mult(_, 2)`.
|
||||
|
||||
Unary functions and called keywords may _not_ be partially applied: it is redundant.
|
||||
|
||||
Because of "partial application," Ludus has a concept of an "argument tuple" (which may include a single placeholder) in addition to a tuple literal (which may not include a placeholder).
|
||||
|
||||
### Function pipelines, or `do` forms
|
||||
In place of nesting function calls inside other function calls, Ludus allows for a more streamlined version of function application: the `do` form or function pipeline. `do` is followed by an initial expression. `do` expressions use `>` as an operator: whatever is on the left hand side of the `>` is passed in as a single argument to whatever is on its right hand side. For example:
|
||||
|
||||
```
|
||||
let silly_result = do 23 >
|
||||
mult (_, 2) > add (1, _) >
|
||||
sub (_, 2) > div (_, 9) & silly_result is 5
|
||||
```
|
||||
Newlines may appear after any instance of `>` in a `do` expression. That does, however, mean that you must be careful not to accidentally include any trailing `>`s.
|
||||
|
||||
### Called keywords
|
||||
Keywords may be called as functions, in which case they extract the value stored at that key in the value passed in:
|
||||
|
||||
```
|
||||
let foo = #{:a 1, :b 2}
|
||||
let bar = :a (foo) & `bar` is now 1
|
||||
```
|
||||
|
||||
Called keywords can be used in pipelines.
|
||||
|
||||
In addition, keywords may be called when they are bound to names:
|
||||
|
||||
```
|
||||
let foo = #{:a 1, :b 2}
|
||||
let bar = :a
|
||||
bar (foo) & => 1
|
||||
```
|
||||
|
||||
## Synthetic expressions
|
||||
Synthetic expressions are valid combinations of words, keywords, package names, and argument tuples which allow for calling functions and extracting values from associative collections. The root--first term--of a synthetic expression must be a word or a keyword; subsequent terms must be either argument tuples or keywords.
|
||||
|
||||
```
|
||||
let foo = #{:a 1, :b #{:c "bar" :d "baz"}}
|
||||
|
||||
let bar = foo :b :c & `bar` is now "bar"
|
||||
|
||||
let baz = :b (foo) :d & `baz` is now "baz"
|
||||
|
||||
```
|
||||
|
||||
## Looping forms
|
||||
Ludus has optimized tail calls--the most straightforward way to accomplish repeating behaviour is function recursion. There are two additional looping forms, `repeat` and `loop`.
|
||||
|
||||
### `repeat`
|
||||
`repeat` is a help to learners, and is useful for executing side effects multiple times. It is written `repeat {number|word} { {exprs} }`. From turtle graphics:
|
||||
|
||||
```
|
||||
repeat 4 {
|
||||
forward! (100)
|
||||
right! (0.25)
|
||||
}
|
||||
```
|
||||
Note that `repeat` does two interesting things:
|
||||
|
||||
1. It never returns a value other than `nil`. If it's in the block, it disappears. This is a unique (and frankly, undesirable) property in Ludus.
|
||||
2. Unlike everything else in Ludus, repeate _requires_ a block, and not simply an expression. You cannot write `repeat 4 forward! (100)`.
|
||||
|
||||
### `loop`/`recur`
|
||||
`loop` and `recur` are largely identical to recursive functions for repetition, but use a special form to allow an anonymous construction and a few guard rails.
|
||||
|
||||
The syntax here is `loop <tuple> with { <function clauses> }`. (Or, you can have a single function clause instead of a set of clauses.) The tuple is passed in as the first set of arguments.
|
||||
|
||||
```
|
||||
let xs = [1, 2, 3, 4]
|
||||
loop (xs, 0) with {
|
||||
([x], sum) -> add (x, sum) & matches against the last element of the list
|
||||
([x, ...xs], sum) -> recur (xs, add (x, sum)) & recurs with the tail
|
||||
} &=> 10
|
||||
```
|
||||
|
||||
`recur` is the recursive call. It must be in tail position--`recur` must be the root of a synthetic expression, in return position. If `recur` is not in tail position, a validation error will be raised.
|
||||
|
||||
In addition, `recur` calls must have the same number of arguments as the original tuple passed to `loop`. While Ludus will allow you to write clauses in `loop` forms with a different arity than the original tuple, those will never match.
|
||||
|
||||
`recur` calls return to the nearest `loop`. Nested `loop`s are probably a bad idea and should be avoided when possible.
|
||||
|
||||
## Environment and context: the toplevel
|
||||
The "toplevel" of a script are the expressions that are not embedded in other expressions or forms: not inside a block, not a member of a collection, not on the right hand side of a binding, not inside a function body. The toplevel-only forms:
|
||||
|
||||
### `import`
|
||||
`import` allows for the evaluation of other Ludus scripts: `import "path/to/file" as name`. `import` just evaluates that file, and then binds a name to the result of evaluating that script. This, right now, is quite basic: circular imports are currently allowed but will lead to endless recursion; results are not cached, so each `import` in a chain re-evaluates the file; and so on.
|
||||
|
||||
Status: not yet implemented.
|
||||
|
||||
### `use`
|
||||
`use` loads the contents of a namespace into a script's context. To ensure that this is statically checkable, this must be at the toplevel.
|
||||
|
||||
Status: not yet implemented.
|
||||
|
||||
### `pkg`
|
||||
Packages, `pkg`es, may only be described at the toplevel of a script. This is to ensure they can be statically evaluatable.
|
||||
|
||||
### `test`
|
||||
A `test` expression is a way of ensuring things behave the way you want them to. Run the script in test mode, and these are evaluated. If the expression under `test` returns a truthy value, you're all good! If the expression under `test` returns a falsy value or raises a panic, then Ludus will report which test(s) failed.
|
||||
|
||||
```
|
||||
test "something goes right" eq? (:foo, :foo)
|
||||
|
||||
test "something goes wrong" {
|
||||
let foo = :foo
|
||||
let bar = :bar
|
||||
eq? (foo, bar)
|
||||
} &=> test failed: "something goes wrong" on line 3
|
||||
```
|
||||
|
||||
`test`s must be at the toplevel--or embedded within other tests in _their_ highest level.
|
||||
|
||||
Formally: `test <string> <expression>`.
|
||||
|
||||
Status: not yet implemented.
|
||||
|
||||
## Changing things: `box`es
|
||||
Ludus does not let you re-bind names. It does, however, have a type that allows for changing values over time: `box`. A box is a place to put things, it has its own identity, it can store whatever you put in it, but to get what's in it, you have to `unbox` it.
|
||||
|
||||
Syntactically and semantically, `box`es are straightforward, but do require a bit more overhead than `let` bindings. The idea is that Ludus makes it obvious where mutable state is in a program, as well as where that mutable state may change. It does so elegantly, but with some guardrails that may take a little getting used to.
|
||||
|
||||
The type of a `box` is, predictably, `:box`.
|
||||
|
||||
```
|
||||
box foo = 42 & foo is now bound to a _box that contains 42_
|
||||
add (1, foo) & panic! no match: foo is _not_ a number
|
||||
store! (foo, 23) & foo is now a box containing 23
|
||||
update! (foo, inc) & foo is now a ref containing 24
|
||||
unbox (foo) &=> 23; use unbox to get the value contained in a box
|
||||
```
|
||||
|
||||
### Ending with a bang!
|
||||
Ludus has a strong naming convention that functions that change state or could cause an explicit panic end in an exclamation point (or, in computer nerd parlance, a "bang"). So anything function that mutates the value held in a reference ends with a bang: `store!` and `update!` take bangs; `unbox` does not.
|
||||
|
||||
This convention also includes anything that prints to the console: `print!`, `report!`, `doc!`, `update!`, `store!`, etc.
|
||||
|
||||
(Note that there are a few counter-examples to this: math functions that could cause a panic [in place of returning NaN] do not end with bangs: `div`, `inv`, and `mod`; each of these has variants that allow for more graceful error handling).
|
||||
|
||||
### Ending with a whimper?
|
||||
Relatedly, just about any function that returns a boolean value is a predicate function--and has a name that ends with a question mark: `eq?` tests for equality; `box?` tells you if something is a ref or not; `lte?` is less-than-or-equal.
|
||||
|
||||
## Errors: panic! in the Ludus script
|
||||
A special form, `panic!`, halts script execution with the expression that follows as an error message.
|
||||
|
||||
```
|
||||
panic! :oops
|
||||
|
||||
if true then :ok else panic! "It's false!"
|
||||
```
|
||||
|
||||
Panics also happen in the following cases:
|
||||
* a `let` binding pattern has no match against the value of its expression
|
||||
* a `match` or `when` form has no matching clause
|
||||
* a function is called with arguments that do not match any of its clauses
|
||||
* something that is not a function or keyword is called as a function
|
||||
* a called keyword is partially applied
|
||||
* `div`, `inv`, or `mod` divides by zero
|
||||
* `sqrt` takes the square root of a negative number
|
||||
* certain error handling functions, like `unwrap!` or `assert!`, are invoked on values that cause them to panic
|
||||
|
||||
In fact, the only functions in the Prelude which explicitly cause panics are, at current, `div`, `inv`, `mod`, `sqrt`, `unwrap!`, and `assert!`.
|
||||
|
||||
### `nil`s, not errors
|
||||
Ludus, however, tries to return `nil` instead of panicking where it seems prudent. So, for example, attempting to get access a value at a keyword off a number or `nil`, while nonsensical, will return `nil` rather than panicking:
|
||||
|
||||
```
|
||||
let a = true
|
||||
a :b :c :d :e &=> nil
|
||||
|
||||
let b = [1, 2, 3]
|
||||
at (b, 12) &=> nil
|
||||
```
|
||||
|
||||
### Result tuples
|
||||
Operations that could fail--especially when you want some information about why--don't always return `nil` on failures. Instead of exceptions or special error values, recoverable errors in Ludus are handled instead by result tuples: `(:ok, value)` and `(:err, msg)`. So, for example, `unwrap!` takes a result tuple and either returns the value in the `:ok` case, or panics in the `:err` case.
|
||||
|
||||
Variants of some functions that may have undesirably inexplicit behaviour are written as `{name}/safe`. So, for example, you can get a variant of `div` that returns a result tuple in `div/safe`, which returns `(:ok, result)` when everything's good; and `(:err, "division by zero")` when the divisor is 0.
|
1751
doc/prelude.md
1751
doc/prelude.md
File diff suppressed because one or more lines are too long
64
flake.lock
64
flake.lock
|
@ -1,64 +0,0 @@
|
|||
{
|
||||
"nodes": {
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1751957021,
|
||||
"narHash": "sha256-2h9T/5Tyd2ounm3DAn+8LftRXctotQ/XD2aoZ9XBtsI=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "fcb0981c8fe996743fc57087e781017eab6e46f9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1751949589,
|
||||
"narHash": "sha256-mgFxAPLWw0Kq+C8P3dRrZrOYEQXOtKuYVlo9xvPntt8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9b008d60392981ad674e04016d25619281550a9d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"fenix": "fenix",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1751913732,
|
||||
"narHash": "sha256-h6rTTB4MBJSIr4xsXxCi8fk8LdiFbwOw/g3QqBHLpW4=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "778e08df16294e4a2c11a40136f69f908e9be879",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "rust-lang",
|
||||
"ref": "nightly",
|
||||
"repo": "rust-analyzer",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
27
flake.nix
27
flake.nix
|
@ -1,27 +0,0 @@
|
|||
{
|
||||
description = "Ludus dev flake (rust)";
|
||||
|
||||
inputs.fenix = {
|
||||
url = "github:nix-community/fenix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
outputs = {nixpkgs, fenix, ... }@inputs:
|
||||
let
|
||||
system = "x86_64-linux"; # your version
|
||||
pkgs = nixpkgs.legacyPackages.x86_64-linux;
|
||||
fx = fenix.packages.x86_64-linux;
|
||||
in
|
||||
{
|
||||
devShells.${system}.default = pkgs.mkShell rec {
|
||||
packages = [
|
||||
pkgs.wasm-pack
|
||||
(fx.combine [
|
||||
fx.latest.cargo
|
||||
fx.latest.rustc
|
||||
fx.targets.wasm32-unknown-unknown.latest.rust-std
|
||||
])
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
338
janet/base.janet
338
janet/base.janet
|
@ -1,338 +0,0 @@
|
|||
# A base library for Ludus
|
||||
# Only loaded in the prelude
|
||||
|
||||
(import /janet/scanner :as s)
|
||||
|
||||
(defn bool [x] (if (= :^nil x) nil x))
|
||||
|
||||
(defn ludus/and [& args] (every? (map bool args)))
|
||||
|
||||
(defn ludus/or [& args] (some bool args))
|
||||
|
||||
(defn ludus/type [value]
|
||||
(when (= :^nil value) (break :nil))
|
||||
(def typed? (when (dictionary? value) (value :^type)))
|
||||
(def the-type (if typed? typed? (type value)))
|
||||
(case the-type
|
||||
:buffer :string
|
||||
:boolean :bool
|
||||
:array :list
|
||||
:table :dict
|
||||
:cfunction :function
|
||||
the-type))
|
||||
|
||||
(var stringify nil)
|
||||
|
||||
(defn- dict-str [dict]
|
||||
(string/join
|
||||
(map
|
||||
(fn [[k v]] (string (stringify k) " " (stringify v)))
|
||||
(pairs dict))
|
||||
", "))
|
||||
|
||||
(defn- stringish? [x] (or (string? x) (buffer? x)))
|
||||
|
||||
(defn- stringify* [value]
|
||||
(when (stringish? value) (break value))
|
||||
(def type (ludus/type value))
|
||||
(case type
|
||||
:nil ""
|
||||
:number (string value)
|
||||
:bool (string value)
|
||||
:keyword (string ":" value)
|
||||
:tuple
|
||||
(string/join (map stringify value) ", ")
|
||||
:list
|
||||
(string/join (map stringify value) ", ")
|
||||
:dict (dict-str value)
|
||||
:set
|
||||
(string/join (map stringify (keys value)) ", ")
|
||||
:box (stringify (value :^value))
|
||||
:fn (string "fn " (value :name))
|
||||
:function (string "builtin " (string value))
|
||||
:pkg (dict-str value)
|
||||
))
|
||||
|
||||
(set stringify stringify*)
|
||||
|
||||
(var show nil)
|
||||
|
||||
(defn- show-pkg [x]
|
||||
(def tab (struct/to-table x))
|
||||
(set (tab :^name) nil)
|
||||
(set (tab :^type) nil)
|
||||
(string "pkg " (x :^name) " {" (stringify tab) "}")
|
||||
)
|
||||
|
||||
(defn- dict-show [dict]
|
||||
(string/join
|
||||
(map
|
||||
(fn [[k v]] (string (show k) " " (show v)))
|
||||
(pairs dict))
|
||||
", "))
|
||||
|
||||
(defn- set-show [sett]
|
||||
(def prepped (merge sett))
|
||||
(set (prepped :^type) nil)
|
||||
(def shown (map show (keys prepped)))
|
||||
(string/join shown ", ")
|
||||
)
|
||||
|
||||
(defn- show* [x]
|
||||
(case (ludus/type x)
|
||||
:nil "nil"
|
||||
:string (string "\"" x "\"")
|
||||
:tuple (string "(" (string/join (map show x) ", ") ")")
|
||||
:list (string "[" (string/join (map show x) ", ") "]")
|
||||
:dict (string "#{" (dict-show x) "}")
|
||||
:set (string "${" (set-show x) "}")
|
||||
:box (string "box " (x :name) " [ " (show (x :^value)) " ]")
|
||||
:pkg (show-pkg x)
|
||||
(stringify x)))
|
||||
|
||||
(set show show*)
|
||||
|
||||
# (var json nil)
|
||||
|
||||
# (defn- dict-json [dict]
|
||||
# (string/join
|
||||
# (map
|
||||
# (fn [[k v]] (string (json k) ": " (json v)))
|
||||
# (pairs dict))
|
||||
# ", "))
|
||||
|
||||
# (defn- json* [x]
|
||||
# (case (ludus/type x)
|
||||
# :nil "\"null\""
|
||||
# :number (string x)
|
||||
# :bool (if true "\"true\"" "\"false\"")
|
||||
# :string (string "\"" x "\"")
|
||||
# :keyword (string "\"" x "\"")
|
||||
# :tuple (string "[" (string/join (map json x) ", ") "]")
|
||||
# :list (string "[" (string/join (map json x) ", ")"]")
|
||||
# :dict (string "{" (dict-json x) "}")
|
||||
# :set (string "[" (string/join (map json (keys x)) ", ") "]")
|
||||
# (show x)))
|
||||
|
||||
# (set json json*)
|
||||
|
||||
(defn show-patt [x]
|
||||
(case (x :type)
|
||||
:nil "nil"
|
||||
:bool (string (x :data))
|
||||
:number (string (x :data))
|
||||
:keyword (string ":" (x :data))
|
||||
:word (x :data)
|
||||
:placeholder (get-in x [:token :lexeme])
|
||||
:tuple (string "(" (string/join (map show-patt (x :data)) ", ") ")")
|
||||
:list (string "[" (string/join (map show-patt (x :data)) ", ")"]")
|
||||
:dict (string "#{" (string/join (map show-patt (x :data)) ", ") "}")
|
||||
:pair (string (show-patt (get-in x [:data 0])) " " (show-patt (get-in x [:data 1])))
|
||||
:typed (string (show-patt (get-in x [:data 1])) " as " (show-patt (get-in x [:data 0])))
|
||||
:interpolated (get-in x [:token :lexeme])
|
||||
:string (get-in x [:token :lexeme])
|
||||
:splat (string "..." (when (x :data) (show-patt (x :data))))
|
||||
(error (string "cannot show pattern of unknown type " (x :type)))))
|
||||
|
||||
(defn pretty-patterns [fnn]
|
||||
(def {:body clauses} fnn)
|
||||
(string/join (map (fn [x] (-> x first show-patt)) clauses) "\n"))
|
||||
|
||||
(defn doc [fnn]
|
||||
(when (not= :fn (ludus/type fnn)) (break "No documentation available."))
|
||||
(def {:name name :doc docstring} fnn)
|
||||
(string/join [name
|
||||
(pretty-patterns fnn)
|
||||
(if docstring docstring "No docstring available.")]
|
||||
"\n"))
|
||||
|
||||
(defn- conj!-set [sett value]
|
||||
(set (sett value) true)
|
||||
sett)
|
||||
|
||||
(defn- conj-set [sett value]
|
||||
(def new (merge sett))
|
||||
(conj!-set new value))
|
||||
|
||||
(defn- conj!-list [list value]
|
||||
(array/push list value))
|
||||
|
||||
(defn- conj-list [list value]
|
||||
(def new (array/slice list))
|
||||
(conj!-list new value))
|
||||
|
||||
(defn conj! [x value]
|
||||
(case (ludus/type x)
|
||||
:list (conj!-list x value)
|
||||
:set (conj!-set x value)))
|
||||
|
||||
(defn conj [x value]
|
||||
(case (ludus/type x)
|
||||
:list (conj-list x value)
|
||||
:set (conj-set x value)
|
||||
(error (string "cannot conj onto " (show x)))))
|
||||
|
||||
(defn disj! [sett value]
|
||||
(set (sett value) nil)
|
||||
sett)
|
||||
|
||||
(defn disj [sett value]
|
||||
(def new (merge sett))
|
||||
(set (new value) nil)
|
||||
new)
|
||||
|
||||
(defn assoc! [dict key value]
|
||||
(set (dict key) value)
|
||||
dict)
|
||||
|
||||
(defn assoc [dict key value]
|
||||
(merge dict {key value}))
|
||||
|
||||
(defn dissoc! [dict key]
|
||||
(set (dict key) nil)
|
||||
dict)
|
||||
|
||||
(defn dissoc [dict key]
|
||||
(def new (merge dict))
|
||||
(set (new key) nil)
|
||||
new)
|
||||
|
||||
(defn ludus/get [key dict &opt def]
|
||||
(default def :^nil)
|
||||
(get dict key def))
|
||||
|
||||
(defn rest [indexed]
|
||||
(array/slice indexed 1))
|
||||
|
||||
(defn to_list [x]
|
||||
(case (ludus/type x)
|
||||
:list x
|
||||
:tuple @[;x]
|
||||
:dict (pairs x)
|
||||
:set (-> x (dissoc :^type) keys)
|
||||
@[x]))
|
||||
|
||||
(defn showprint [x]
|
||||
(if (= :string (ludus/type x))
|
||||
x
|
||||
(show x)))
|
||||
|
||||
(defn print! [args]
|
||||
(print ;(map showprint args)))
|
||||
|
||||
(defn prn [x]
|
||||
(pp x)
|
||||
x)
|
||||
|
||||
(defn concat [x y & zs]
|
||||
(case (ludus/type x)
|
||||
:string (string x y ;zs)
|
||||
:list (array/concat @[] x y ;zs)
|
||||
:set (merge x y ;zs)))
|
||||
|
||||
(defn unbox [b] (get b :^value))
|
||||
|
||||
(defn store! [b x] (set (b :^value) x))
|
||||
|
||||
(defn mod [x y]
|
||||
(% x y))
|
||||
|
||||
(defn- byte->ascii [c i]
|
||||
(if (< c 128)
|
||||
(string/from-bytes c)
|
||||
(error (string "non-ASCII character at index" i))))
|
||||
|
||||
(defn chars [str]
|
||||
(def out @[])
|
||||
(try
|
||||
(for i 0 (length str)
|
||||
(array/push out (byte->ascii (str i) i)))
|
||||
([e] (break [:err e])))
|
||||
[:ok out])
|
||||
|
||||
(defn to_number [str]
|
||||
(when (string/find "&" str)
|
||||
(break [:err (string "Could not parse `" str "` as a number")]))
|
||||
(def scanned (s/scan (string/trim str)))
|
||||
(when (< 0 (length (scanned :errors)))
|
||||
(break [:err (string "Could not parse `" str "` as a number")]))
|
||||
(def tokens (scanned :tokens))
|
||||
(when (< 3 (length tokens))
|
||||
(break [:err (string "Could not parse `" str "` as a number")]))
|
||||
(def fst (first tokens))
|
||||
(when (not= :number (fst :type))
|
||||
(break [:err (string "Could not parse `" str "` as a number")]))
|
||||
[:ok (fst :literal)])
|
||||
|
||||
(def ctx {
|
||||
"add" +
|
||||
"and" ludus/and
|
||||
"assoc!" assoc!
|
||||
"assoc" assoc
|
||||
"atan_2" math/atan2
|
||||
"bool" bool
|
||||
"ceil" math/ceil
|
||||
"chars" chars
|
||||
"concat" concat
|
||||
"conj!" conj!
|
||||
"conj" conj
|
||||
"cos" math/cos
|
||||
"count" length
|
||||
"dec" dec
|
||||
"disj!" disj!
|
||||
"disj" disj
|
||||
"dissoc!" dissoc!
|
||||
"dissoc" dissoc
|
||||
"div" /
|
||||
"doc" doc
|
||||
"downcase" string/ascii-lower
|
||||
"e" math/e
|
||||
"eq?" deep=
|
||||
"first" first
|
||||
"floor" math/floor
|
||||
"get" ludus/get
|
||||
"gt" >
|
||||
"gte" >=
|
||||
"inc" inc
|
||||
"last" last
|
||||
"lt" <
|
||||
"lte" <=
|
||||
"mod" mod
|
||||
"mult" *
|
||||
"not" not
|
||||
"nth" ludus/get
|
||||
"or" ludus/or
|
||||
"pi" math/pi
|
||||
"pow" math/pow
|
||||
"print!" print!
|
||||
"prn" prn
|
||||
"push" array/push
|
||||
"random" math/random
|
||||
"range" range
|
||||
"rest" rest
|
||||
"round" math/round
|
||||
"show" show
|
||||
"sin" math/sin
|
||||
"slice" array/slice
|
||||
"split" string/split
|
||||
"sqrt" math/sqrt
|
||||
"store!" store!
|
||||
"str_slice" string/slice
|
||||
"stringify" stringify
|
||||
"sub" -
|
||||
"tan" math/tan
|
||||
"to_list" to_list
|
||||
"to_number" to_number
|
||||
"trim" string/trim
|
||||
"triml" string/triml
|
||||
"trimr" string/trimr
|
||||
"type" ludus/type
|
||||
"unbox" unbox
|
||||
"upcase" string/ascii-upper
|
||||
})
|
||||
|
||||
(def base (let [b @{:^type :dict}]
|
||||
(each [k v] (pairs ctx)
|
||||
(set (b (keyword k)) v))
|
||||
b))
|
||||
|
133
janet/doc.janet
133
janet/doc.janet
|
@ -1,133 +0,0 @@
|
|||
(import /janet/base :as base)
|
||||
(import /janet/prelude :as prelude)
|
||||
|
||||
(defn map-values [f dict]
|
||||
(from-pairs (map (fn [[k v]] [k (f v)]) (pairs dict))))
|
||||
|
||||
(def with-docs (map-values base/doc prelude/ctx))
|
||||
|
||||
(def sorted-names (-> with-docs keys sort))
|
||||
|
||||
(defn escape-underscores [str] (string/replace "_" "\\_" str))
|
||||
|
||||
(defn escape-punctuation [str] (->> str
|
||||
(string/replace "?" "")
|
||||
(string/replace "!" "")
|
||||
(string/replace "/" "")))
|
||||
|
||||
(defn toc-entry [name]
|
||||
(def escaped (escape-underscores name))
|
||||
(string "[" escaped "](#" (escape-punctuation escaped) ")"))
|
||||
|
||||
(def alphabetical-list
|
||||
(string/join (map toc-entry sorted-names) " "))
|
||||
|
||||
(def topics {
|
||||
"math" ["abs" "add" "angle" "atan/2" "between?" "ceil" "cos" "dec" "deg/rad" "deg/turn" "dist" "div" "div/0" "div/safe" "even?" "floor" "gt?" "gte?" "heading/vector" "inc" "inv" "inv/0" "inv/safe" "lt?" "lte?" "max" "min" "mod" "mod/0" "mod/safe" "mult" "neg" "neg?" "odd?" "pi" "pos?" "pow" "rad/deg" "rad/turn" "random" "random_int" "range" "round" "sin" "sqrt" "sqrt/safe" "square" "sub" "sum_of_squares" "tan" "tau" "to_number" "turn/deg" "turn/rad" "zero?"]
|
||||
"bools" ["and" "bool" "bool?" "false?" "not" "or" "true?"]
|
||||
"dicts" ["any?" "assoc" "coll?" "count" "dict" "dict?" "diff" "dissoc" "empty?" "get" "has?" "keys" "random" "update" "values"]
|
||||
"lists" ["any?" "append" "at" "butlast" "coll?" "concat" "count" "each!" "empty?" "filter" "first" "fold" "index_of" "indexed?" "indices_of" "join" "keep" "last" "list" "list?" "map" "random" "range" "rest" "second" "sentence" "slice"]
|
||||
"llists" ["car" "cdr" "cons" "llist"]
|
||||
# "sets" ["any?" "append" "coll?" "concat" "contains?" "count" "empty?" "omit" "random" "set" "set?"]
|
||||
"tuples" ["any?" "at" "coll?" "count" "empty?" "first" "last" "ordered?" "rest" "second" "tuple?"]
|
||||
"strings" ["any?" "at" "chars" "chars/safe" "concat" "count" "downcase" "empty?" "join" "sentence" "show" "slice" "slice_n" "split" "string" "string?" "strip" "to_number" "trim" "upcase" "words"]
|
||||
"types and values" ["bool?" "box?" "coll?" "dict?" "eq?" "fn?" "indexed?" "keyword?" "list?" "nil?" "number?" "set?" "show" "some" "some?" "string?" "tuple?" "type"]
|
||||
"boxes" ["box?" "unbox" "store!" "update!"]
|
||||
"results" ["err" "err?" "ok" "ok?" "unwrap!" "unwrap_or"]
|
||||
"errors" ["assert!"]
|
||||
"turtle graphics" ["back!" "background!" "bk!" "clear!" "colors" "fd!" "forward!" "goto!" "heading" "heading/vector" "hideturtle!" "home!" "left!" "loadstate!" "lt!" "pc!" "pd!" "pencolor" "pencolour" "pencolor!" "pencolour!" "pendown!" "pendown?" "penup!" "penwidth" "penwidth!" "position" "pu!" "pw!" "render_turtle!" "reset_turtle!" "right!" "rt!" "setheading!" "showturtle!" "spawn_turtle" "turtle_state"]
|
||||
"environment and i/o" ["console" "doc!" "fetch_inbox" "fetch_outbox" "input" "key_down?" "keys_down" "print!" "read_input" "report!"]
|
||||
"processes" ["alive?" "await" "fledge" "flush" "heed" "hibernate!" "monitor" "self" "send!" "sleep!" "spawn" "unlink!" "yield!"]
|
||||
})
|
||||
|
||||
(defn capitalize [str]
|
||||
(def fst (slice str 0 1))
|
||||
(def rest (slice str 1))
|
||||
(def init_cap (string/ascii-upper fst))
|
||||
(def lower_rest (string/ascii-lower rest))
|
||||
(string init_cap lower_rest))
|
||||
|
||||
(defn topic-entry [topic]
|
||||
(string "### " (capitalize topic) "\n"
|
||||
(as-> topic _ (topics _) (array/slice _) (sort _) (map toc-entry _)
|
||||
(string/join _ " "))
|
||||
"\n"))
|
||||
|
||||
(def by-topic (let [the-topics (-> topics keys sort)
|
||||
topics-entries (map topic-entry the-topics)]
|
||||
(string/join topics-entries "\n")))
|
||||
|
||||
(defn compose-entry [name]
|
||||
(def header (string "\n### " name "\n"))
|
||||
(def the-doc (get with-docs name))
|
||||
(when (= "No documentation available." the-doc)
|
||||
(break (string header the-doc "\n")))
|
||||
(def lines (string/split "\n" the-doc))
|
||||
(def description (last lines))
|
||||
(def patterns (string/join (slice lines 1 (-> lines length dec)) "\n"))
|
||||
(def backto "[Back to top.](#ludus-prelude-documentation)\n")
|
||||
(string header description "\n```\n" patterns "\n```\n" backto))
|
||||
|
||||
(compose-entry "update")
|
||||
|
||||
(def entries (string/join (map compose-entry sorted-names) "\n---"))
|
||||
|
||||
(def doc-file (string
|
||||
```
|
||||
# Ludus prelude documentation
|
||||
These functions are available in every Ludus script.
|
||||
The documentation for any function can be found within Ludus by passing the function to `doc!`,
|
||||
e.g., running `doc! (add)` will send the documentation for `add` to the console.
|
||||
|
||||
For more information on the syntax & semantics of the Ludus language, see [language.md](./language.md).
|
||||
|
||||
The prelude itself is just a Ludus file, which you can see at [prelude.ld](../assets/prelude.ld).
|
||||
|
||||
## A few notes
|
||||
**Naming conventions.** Functions whose name ends with a question mark, e.g., `eq?`, return booleans.
|
||||
Functions whose name ends with an exclamation point, e.g., `make!`, change state in some way.
|
||||
In other words, they _do things_ rather than _calculating values_.
|
||||
Functions whose name includes a slash either convert from one value to another, e.g. `deg/rad`,
|
||||
or they are variations on a function, e.g. `div/0` as a variation on `div`.
|
||||
|
||||
**How entries are formatted.** Each entry has a brief (sometimes too brief!) description of what it does.
|
||||
It is followed by the patterns for each of its function clauses.
|
||||
This should be enough to indicate order of arguments, types, and so on.
|
||||
|
||||
**Patterns often, but do not always, indicate types.** Typed patterns are written as `foo as :bar`,
|
||||
where the type is indicated by the keyword.
|
||||
Possible ludus types are: `:nil`, `:boolean`, `:number`, `:keyword` (atomic values);
|
||||
`:string` (strings are their own beast); `:tuple` and `:list` (ordered collections), `:set`s, and `:dict`ionaries (the other collection types); `:pkg` (packages, which are quasi-collections); `:fn` (functions); and `:box`es.
|
||||
|
||||
**Conventional types.** Ludus has two types based on conventions.
|
||||
* _Result tuples._ Results are a way of modeling the result of a calculation that might fail.
|
||||
The two possible values are `(:ok, value)` and `(:err, msg)`.
|
||||
`msg` is usually a string describing what went wrong.
|
||||
To work with result tuples, see [`unwrap!`](#unwrap) and [`unwrap_or`](#unwrap_or).
|
||||
That said, usually you work with these using pattern matching.
|
||||
|
||||
* _Vectors._ Vectors are 2-element tuples of x and y coordinates.
|
||||
The origin is `(0, 0)`.
|
||||
Many math functions take vectors as well as numbers, e.g., `add` and `mult`.
|
||||
You will see vectors indicated in patterns by an `(x, y)` tuple.
|
||||
You can see what this looks like in the last clause of `add`: `((x1, y1), (x2, y2))`.
|
||||
|
||||
## Functions by topic
|
||||
|
||||
```
|
||||
by-topic
|
||||
```
|
||||
|
||||
## All functions, alphabetically
|
||||
|
||||
```
|
||||
alphabetical-list
|
||||
```
|
||||
|
||||
## Function documentation
|
||||
|
||||
```
|
||||
entries
|
||||
))
|
||||
|
||||
(spit "prelude.md" doc-file)
|
|
@ -1,140 +0,0 @@
|
|||
(import /janet/base :as b)
|
||||
|
||||
(defn- get-line [source line]
|
||||
((string/split "\n" source) (dec line)))
|
||||
|
||||
(defn- caret [source line start]
|
||||
(def lines (string/split "\n" source))
|
||||
(def the-line (lines (dec line)))
|
||||
(def prev-lines (slice lines 0 (dec line)))
|
||||
(def char-counts (map (fn [x] (-> x length inc)) prev-lines))
|
||||
(def prev-line-chars (sum char-counts))
|
||||
(def offset (- start prev-line-chars))
|
||||
(def indent (string/repeat "." (+ 6 offset)))
|
||||
(string indent "^")
|
||||
)
|
||||
|
||||
|
||||
(defn scan-error [e]
|
||||
(def {:line line-num :input input :source source :start start :msg msg} e)
|
||||
(print "Syntax error: " msg)
|
||||
(print " on line " line-num " in " input ":")
|
||||
(def source-line (get-line source line-num))
|
||||
(print " >>> " source-line)
|
||||
(print (caret source line-num start))
|
||||
e)
|
||||
|
||||
(defn parse-error [e]
|
||||
(def msg (e :msg))
|
||||
(def {:line line-num :input input :source source :start start} (e :token))
|
||||
(def source-line (get-line source line-num))
|
||||
(print "Syntax error: " msg)
|
||||
(print " on line " line-num " in " input ":")
|
||||
(print " >>> " source-line)
|
||||
(print (caret source line-num start))
|
||||
e)
|
||||
|
||||
(defn validation-error [e]
|
||||
(def msg (e :msg))
|
||||
(def {:line line-num :input input :source source :start start} (get-in e [:node :token]))
|
||||
(def source-line (get-line source line-num))
|
||||
(case msg
|
||||
"unbound name"
|
||||
(do
|
||||
(print "Validation error: " msg " " (get-in e [:node :data]))
|
||||
(print " on line " line-num " in " input ":")
|
||||
(print " >>> " source-line)
|
||||
(print (caret source line-num start)))
|
||||
(do
|
||||
(print "Validation error: " msg)
|
||||
(print " on line " line-num " in " input ":")
|
||||
(print " >>> " source-line)
|
||||
(print (caret source line-num start))))
|
||||
e)
|
||||
|
||||
(defn- fn-no-match [e]
|
||||
(print "Ludus panicked! no match")
|
||||
(def {:line line-num :source source :input input :start start} (get-in e [:node :token]))
|
||||
(def source-line (get-line source line-num))
|
||||
(print " on line " line-num " in " input ", ")
|
||||
(def called (e :called))
|
||||
(print " calling: " (slice (b/show called) 3))
|
||||
(def value (e :value))
|
||||
(print " with arguments: " (b/show value))
|
||||
(print " expected match with one of:")
|
||||
(def patterns (b/pretty-patterns called))
|
||||
(def fmt-patt (do
|
||||
(def lines (string/split "\n" patterns))
|
||||
(def indented (map (fn [x] (string " " x)) lines))
|
||||
(string/join indented "\n")
|
||||
))
|
||||
(print fmt-patt)
|
||||
(print " >>> " source-line)
|
||||
(print (caret source line-num start))
|
||||
)
|
||||
|
||||
(defn- let-no-match [e]
|
||||
(print "Ludus panicked! no match")
|
||||
(def {:line line-num :source source :input input :start start} (get-in e [:node :token]))
|
||||
(def source-line (get-line source line-num))
|
||||
(print " on line " line-num " in " input ", ")
|
||||
(print " matching: " (b/show (e :value)))
|
||||
(def pattern (get-in e [:node :data 0]))
|
||||
(print " with pattern: " (b/show-patt pattern))
|
||||
(print " >>> " source-line)
|
||||
(print (caret source line-num start))
|
||||
e)
|
||||
|
||||
(defn- match-no-match [e]
|
||||
(print "Ludus panicked! no match")
|
||||
(def {:line line-num :source source :input input :start start} (get-in e [:node :token]))
|
||||
(print " on line " line-num " in " input ", ")
|
||||
(def value (e :value))
|
||||
(print " matching: " (b/show value))
|
||||
(print " with patterns:")
|
||||
(def clauses (get-in e [:node :data 1]))
|
||||
(def patterns (b/pretty-patterns {:body clauses}))
|
||||
(def fmt-patt (do
|
||||
(def lines (string/split "\n" patterns))
|
||||
(def indented (map (fn [x] (string " " x)) lines))
|
||||
(string/join indented "\n")
|
||||
))
|
||||
(print fmt-patt)
|
||||
(def source-line (get-line source line-num))
|
||||
(print " >>> " source-line)
|
||||
(print (caret source line-num start))
|
||||
e)
|
||||
|
||||
(defn- generic-panic [e]
|
||||
(def msg (e :msg))
|
||||
(def {:line line-num :source source :input input :start start} (get-in e [:node :token]))
|
||||
(def source-line (get-line source line-num))
|
||||
(print "Ludus panicked! " msg)
|
||||
(print " on line " line-num " in " input ":")
|
||||
(print " >>> " source-line)
|
||||
(print (caret source line-num start))
|
||||
e)
|
||||
|
||||
(defn- unbound-name [e]
|
||||
(def {:line line-num :source source :lexeme name :input input :start start} (get-in e [:node :token]))
|
||||
(def source-line (get-line source line-num))
|
||||
(print "Ludus panicked! unbound name " name)
|
||||
(print " on line " line-num " in " input ":")
|
||||
(print " >>> " source-line)
|
||||
(print (caret source line-num start))
|
||||
e)
|
||||
|
||||
(defn runtime-error [e]
|
||||
(when (= :string (type e))
|
||||
(print (string "Internal Ludus error: " e))
|
||||
(print "Please file an issue at https://alea.ludus.dev/twc/ludus/issues")
|
||||
(break e))
|
||||
(def msg (e :msg))
|
||||
(case msg
|
||||
"no match: function call" (fn-no-match e)
|
||||
"no match: let binding" (let-no-match e)
|
||||
"no match: match form" (match-no-match e)
|
||||
"no match: when form" (generic-panic e)
|
||||
"unbound name" (unbound-name e)
|
||||
(generic-panic e))
|
||||
e)
|
|
@ -1,657 +0,0 @@
|
|||
# A tree walk interpreter for ludus
|
||||
|
||||
(import /janet/base :as b)
|
||||
|
||||
(var interpret nil)
|
||||
(var match-pattern nil)
|
||||
|
||||
(defn- todo [msg] (error (string "not yet implemented: " msg)))
|
||||
|
||||
(defn- resolve-name [name ctx]
|
||||
# # (print "resolving " name " in:")
|
||||
# # (pp ctx)
|
||||
(when (not ctx) (break :^not-found))
|
||||
(if (has-key? ctx name)
|
||||
(ctx name)
|
||||
(resolve-name name (ctx :^parent))))
|
||||
|
||||
(defn- match-word [word value ctx]
|
||||
(def name (word :data))
|
||||
# # (print "matched " (b/show value) " to " name)
|
||||
(set (ctx name) value)
|
||||
{:success true :ctx ctx})
|
||||
|
||||
(defn- typed [pattern value ctx]
|
||||
(def [type-ast word] (pattern :data))
|
||||
(def type (type-ast :data))
|
||||
(if (= type (b/ludus/type value))
|
||||
(match-word word value ctx)
|
||||
{:success false :miss [pattern value]}))
|
||||
|
||||
(defn- match-tuple [pattern value ctx]
|
||||
(when (not (tuple? value))
|
||||
(break {:success false :miss [pattern value]}))
|
||||
(def val-len (length value))
|
||||
(var members (pattern :data))
|
||||
(when (empty? members)
|
||||
(break (if (empty? value)
|
||||
{:success true :ctx ctx}
|
||||
{:success false :miss [pattern value]})))
|
||||
(def patt-len (length members))
|
||||
(var splat nil)
|
||||
(def splat? (= :splat ((last members) :type)))
|
||||
(when splat?
|
||||
(when (< val-len patt-len)
|
||||
# (print "mismatched splatted tuple lengths")
|
||||
(break {:success false :miss [pattern value]}))
|
||||
# (print "splat!")
|
||||
(set splat (last members))
|
||||
(set members (slice members 0 (dec patt-len))))
|
||||
(when (and (not splat?) (not= val-len patt-len))
|
||||
# (print "mismatched tuple lengths")
|
||||
(break {:success false :miss [pattern value]}))
|
||||
(var curr-mem :^nothing)
|
||||
(var curr-val :^nothing)
|
||||
(var success true)
|
||||
(for i 0 (length members)
|
||||
(set curr-mem (get members i))
|
||||
(set curr-val (get value i))
|
||||
# (print "in tuple, matching " curr-val " with ")
|
||||
# (pp curr-mem)
|
||||
(def match? (match-pattern curr-mem curr-val ctx))
|
||||
# (pp match?)
|
||||
(when (not (match? :success))
|
||||
(set success false)
|
||||
(break)))
|
||||
(when (and splat? (splat :data))
|
||||
(def rest (array/slice value (length members)))
|
||||
(match-word (splat :data) rest ctx))
|
||||
(if success
|
||||
{:success true :ctx ctx}
|
||||
{:success false :miss [pattern value]}))
|
||||
|
||||
(defn- match-list [pattern value ctx]
|
||||
(when (not (array? value))
|
||||
(break {:success false :miss [pattern value]}))
|
||||
(def val-len (length value))
|
||||
(var members (pattern :data))
|
||||
(when (empty? members)
|
||||
(break (if (empty? value)
|
||||
{:success true :ctx ctx}
|
||||
{:success false :miss [pattern value]})))
|
||||
(def patt-len (length members))
|
||||
(var splat nil)
|
||||
(def splat? (= :splat ((last members) :type)))
|
||||
(when splat?
|
||||
(when (< val-len patt-len)
|
||||
# (print "mismatched splatted list lengths")
|
||||
(break {:success false :miss [pattern value]}))
|
||||
# (print "splat!")
|
||||
(set splat (last members))
|
||||
(set members (slice members 0 (dec patt-len))))
|
||||
(when (and (not splat?) (not= val-len patt-len))
|
||||
# (print "mismatched list lengths")
|
||||
(break {:success false :miss [pattern value]}))
|
||||
(var curr-mem :^nothing)
|
||||
(var curr-val :^nothing)
|
||||
(var success true)
|
||||
(for i 0 (length members)
|
||||
(set curr-mem (get members i))
|
||||
(set curr-val (get value i))
|
||||
# (print "in list, matching " curr-val " with ")
|
||||
# (pp curr-mem)
|
||||
(def match? (match-pattern curr-mem curr-val ctx))
|
||||
# (pp match?)
|
||||
(when (not (match? :success))
|
||||
(set success false)
|
||||
(break)))
|
||||
(when (and splat? (splat :data))
|
||||
(def rest (array/slice value (length members)))
|
||||
(match-word (splat :data) rest ctx))
|
||||
(if success
|
||||
{:success true :ctx ctx}
|
||||
{:success false :miss [pattern value]}))
|
||||
|
||||
(defn- match-string [pattern value ctx]
|
||||
(when (not (string? value))
|
||||
(break {:success false :miss [pattern value]}))
|
||||
(def {:compiled compiled :bindings bindings} pattern)
|
||||
# (print "matching " value " with")
|
||||
# (pp (pattern :grammar))
|
||||
(def matches (peg/match compiled value))
|
||||
(when (not matches)
|
||||
(break {:success false :miss [pattern value]}))
|
||||
(when (not= (length matches) (length bindings))
|
||||
(error "oops: different number of matches and bindings"))
|
||||
(for i 0 (length matches)
|
||||
(set (ctx (bindings i)) (matches i)))
|
||||
{:success true :ctx ctx})
|
||||
|
||||
(defn- match-dict [pattern value ctx]
|
||||
(when (not (table? value))
|
||||
(break {:success false :miss [pattern value]}))
|
||||
(def val-size (length value))
|
||||
(var members (pattern :data))
|
||||
(def patt-len (length members))
|
||||
(when (empty? members)
|
||||
(break (if (empty? value)
|
||||
{:success true :ctx ctx}
|
||||
{:success false :miss [pattern value]})))
|
||||
(var splat nil)
|
||||
(def splat? (= :splat ((last members) :type)))
|
||||
(when splat?
|
||||
(when (< val-size patt-len)
|
||||
# (print "mismatched splatted dict lengths")
|
||||
(break {:success false :miss [pattern value]}))
|
||||
# (print "splat!")
|
||||
(set splat (last members))
|
||||
(set members (slice members 0 (dec patt-len))))
|
||||
(when (and (not splat?) (not= val-size patt-len))
|
||||
# (print "mismatched dict lengths")
|
||||
(break {:success false :miss [pattern value]}))
|
||||
(var success true)
|
||||
(def matched-keys @[])
|
||||
(for i 0 (length members)
|
||||
(def curr-pair (get members i))
|
||||
(def [curr-key curr-patt] (curr-pair :data))
|
||||
(def key (interpret curr-key ctx))
|
||||
(def curr-val (value key))
|
||||
(def match? (match-pattern curr-patt curr-val ctx))
|
||||
(array/push matched-keys key)
|
||||
(when (not (match? :success))
|
||||
(set success false)
|
||||
(break)))
|
||||
(when (and splat? (splat :data) success)
|
||||
(def rest (merge value))
|
||||
(each key matched-keys
|
||||
(set (rest key) nil))
|
||||
(match-word (splat :data) rest ctx))
|
||||
(if success
|
||||
{:success true :ctx ctx}
|
||||
{:success false :miss [pattern value]}))
|
||||
|
||||
|
||||
(defn- match-pattern* [pattern value &opt ctx]
|
||||
# (print "in match-pattern, matching " value " with:")
|
||||
# (pp pattern)
|
||||
(default ctx @{})
|
||||
(def data (pattern :data))
|
||||
(case (pattern :type)
|
||||
# always match
|
||||
:placeholder {:success true :ctx ctx}
|
||||
:ignored {:success true :ctx ctx}
|
||||
:word (match-word pattern value ctx)
|
||||
|
||||
# match on equality
|
||||
:nil {:success (= :^nil value) :ctx ctx}
|
||||
:bool {:success (= data value) :ctx ctx}
|
||||
:number {:success (= data value) :ctx ctx}
|
||||
:string {:success (= data value) :ctx ctx}
|
||||
:keyword {:success (= data value) :ctx ctx}
|
||||
|
||||
# TODO: lists, dicts
|
||||
:tuple (match-tuple pattern value ctx)
|
||||
:list (match-list pattern value ctx)
|
||||
:dict (match-dict pattern value ctx)
|
||||
|
||||
:interpolated (match-string pattern value ctx)
|
||||
|
||||
:typed (typed pattern value ctx)
|
||||
))
|
||||
|
||||
(set match-pattern match-pattern*)
|
||||
|
||||
(defn- lett [ast ctx]
|
||||
# (print "lett!")
|
||||
# (pp ast)
|
||||
(def [patt expr] (ast :data))
|
||||
(def value (interpret expr ctx))
|
||||
(def match? (match-pattern patt value))
|
||||
(if (match? :success)
|
||||
(do
|
||||
(merge-into ctx (match? :ctx))
|
||||
value)
|
||||
(error {:node ast :value value :msg "no match: let binding"})))
|
||||
|
||||
(defn- matchh [ast ctx]
|
||||
(def [to-match clauses] (ast :data))
|
||||
(def value (interpret to-match ctx))
|
||||
(def len (length clauses))
|
||||
(when (ast :match) (break ((ast :match) 0 value ctx)))
|
||||
(defn match-fn [i value ctx]
|
||||
(when (= len i)
|
||||
(error {:node ast :value value :msg "no match: match form"}))
|
||||
(def clause (clauses i))
|
||||
(def [patt guard expr] clause)
|
||||
(def match? (match-pattern patt value @{:^parent ctx}))
|
||||
(when (not (match? :success))
|
||||
(break (match-fn (inc i) value ctx)))
|
||||
(def body-ctx (match? :ctx))
|
||||
(def guard? (if guard
|
||||
(b/bool (interpret guard body-ctx)) true))
|
||||
(when (not guard?)
|
||||
(break (match-fn (inc i) value ctx)))
|
||||
(interpret expr body-ctx))
|
||||
(set (ast :match) match-fn)
|
||||
(match-fn 0 value ctx))
|
||||
|
||||
(defn- script [ast ctx]
|
||||
(def lines (ast :data))
|
||||
(def last-line (last lines))
|
||||
(for i 0 (-> lines length dec)
|
||||
(interpret (lines i) ctx))
|
||||
(interpret last-line ctx))
|
||||
|
||||
(defn- block [ast parent]
|
||||
(def lines (ast :data))
|
||||
(def last-line (last lines))
|
||||
(def ctx @{:^parent parent})
|
||||
(for i 0 (-> lines length dec)
|
||||
(interpret (lines i) ctx))
|
||||
(interpret last-line ctx))
|
||||
|
||||
(defn- to_string [ctx] (fn [x]
|
||||
(if (buffer? x)
|
||||
(string x)
|
||||
(b/stringify (interpret x ctx)))))
|
||||
|
||||
(defn- interpolated [ast ctx]
|
||||
(def terms (ast :data))
|
||||
(def interpolations (map (to_string ctx) terms))
|
||||
(string/join interpolations))
|
||||
|
||||
(defn- iff [ast ctx]
|
||||
(def [condition then else] (ast :data))
|
||||
(if (b/bool (interpret condition ctx))
|
||||
(interpret then ctx)
|
||||
(interpret else ctx)))
|
||||
|
||||
# TODO: use a tail call here
|
||||
(defn- whenn [ast ctx]
|
||||
(def clauses (ast :data))
|
||||
(var result :^nothing)
|
||||
(each clause clauses
|
||||
(def [lhs rhs] clause)
|
||||
(when (b/bool (interpret lhs ctx))
|
||||
(set result (interpret rhs ctx))
|
||||
(break)))
|
||||
(when (= result :^nothing)
|
||||
(error {:node ast :msg "no match: when form"}))
|
||||
result)
|
||||
|
||||
(defn- word [ast ctx]
|
||||
(def resolved (resolve-name (ast :data) ctx))
|
||||
(if (= :^not-found resolved)
|
||||
(error {:node ast :msg "unbound name"})
|
||||
resolved))
|
||||
|
||||
(defn- tup [ast ctx]
|
||||
(def members (ast :data))
|
||||
(def the-tup @[])
|
||||
(each member members
|
||||
(array/push the-tup (interpret member ctx)))
|
||||
[;the-tup])
|
||||
|
||||
(defn- args [ast ctx]
|
||||
(def members (ast :data))
|
||||
(def the-args @[])
|
||||
(each member members
|
||||
(array/push the-args (interpret member ctx)))
|
||||
(if (ast :partial)
|
||||
{:^type :partial :args the-args}
|
||||
[;the-args]))
|
||||
|
||||
(defn- sett [ast ctx]
|
||||
(def members (ast :data))
|
||||
(def the-set @{:^type :set})
|
||||
(each member members
|
||||
(def value (interpret member ctx))
|
||||
(set (the-set value) true))
|
||||
the-set)
|
||||
|
||||
(defn- list [ast ctx]
|
||||
(def members (ast :data))
|
||||
(def the-list @[])
|
||||
(each member members
|
||||
(if (= :splat (member :type))
|
||||
(do
|
||||
(def splatted (interpret (member :data) ctx))
|
||||
(when (not= :array (type splatted))
|
||||
(error {:node member :msg "cannot splat non-list into list"}))
|
||||
(array/concat the-list splatted))
|
||||
(array/push the-list (interpret member ctx))))
|
||||
the-list)
|
||||
|
||||
(defn- dict [ast ctx]
|
||||
(def members (ast :data))
|
||||
(def the-dict @{})
|
||||
(each member members
|
||||
(if (= :splat (member :type))
|
||||
(do
|
||||
(def splatted (interpret (member :data) ctx))
|
||||
(when (or
|
||||
(not= :table (type splatted))
|
||||
(:^type splatted))
|
||||
(error {:node member :msg "cannot splat non-dict into dict"}))
|
||||
(merge-into the-dict splatted))
|
||||
(do
|
||||
(def [key-ast value-ast] (member :data))
|
||||
# (print "dict key")
|
||||
# (pp key-ast)
|
||||
# (print "dict value")
|
||||
# (pp value-ast)
|
||||
(def key (interpret key-ast ctx))
|
||||
(def value (interpret value-ast ctx))
|
||||
(set (the-dict key) value))))
|
||||
the-dict)
|
||||
|
||||
(defn- box [ast ctx]
|
||||
(def {:data value-ast :name name} ast)
|
||||
(def value (interpret value-ast ctx))
|
||||
(def box @{:^type :box :^value value :name name})
|
||||
(set (ctx name) box)
|
||||
box)
|
||||
|
||||
(defn- repeatt [ast ctx]
|
||||
(def [times-ast body] (ast :data))
|
||||
(def times (interpret times-ast ctx))
|
||||
(when (not (number? times))
|
||||
(error {:node times-ast :msg (string "repeat needs a `number` of times; you gave me a " (type times))}))
|
||||
(repeat times (interpret body ctx)))
|
||||
|
||||
(defn- panic [ast ctx]
|
||||
(def info (interpret (ast :data) ctx))
|
||||
(error {:node ast :msg info}))
|
||||
|
||||
# TODO: add docstrings & pattern docs to fns
|
||||
# Depends on: good string representation of patterns
|
||||
# For now, this should be enough to tall the thing
|
||||
(defn- fnn [ast ctx]
|
||||
(def {:name name :data clauses :doc doc} ast)
|
||||
# (print "defining fn " name)
|
||||
(def closure (merge ctx))
|
||||
(def the-fn @{:name name :^type :fn :body clauses :ctx closure :doc doc})
|
||||
(when (not= :^not-found (resolve-name name ctx))
|
||||
# (print "fn "name" was forward declared")
|
||||
(def fwd (resolve-name name ctx))
|
||||
(set (fwd :body) clauses)
|
||||
(set (fwd :ctx) closure)
|
||||
(set (fwd :doc) doc)
|
||||
# (print "fn " name " has been defined")
|
||||
# (pp fwd)
|
||||
(break fwd))
|
||||
# (pp the-fn)
|
||||
(set (closure name) the-fn)
|
||||
(set (ctx name) the-fn)
|
||||
the-fn)
|
||||
|
||||
(defn- is_placeholder [x] (= x :_))
|
||||
|
||||
(var call-fn nil)
|
||||
|
||||
(defn- partial [root-ast the-fn partial-args]
|
||||
(when (the-fn :applied)
|
||||
(error {:msg "cannot partially apply a partially applied function"
|
||||
:node root-ast :called the-fn :args partial-args}))
|
||||
# (print "calling partially applied function")
|
||||
(def args (partial-args :args))
|
||||
# (pp args)
|
||||
(def pos (find-index is_placeholder args))
|
||||
(def name (string (the-fn :name) " *partial*"))
|
||||
(defn partial-fn [root-ast missing]
|
||||
# (print "calling function with arg " (b/show missing))
|
||||
# (pp partial-args)
|
||||
(def full-args (array/slice args))
|
||||
(set (full-args pos) missing)
|
||||
# (print "all args: " (b/show full-args))
|
||||
(call-fn root-ast the-fn [;full-args]))
|
||||
{:^type :fn :applied true :name name :body partial-fn})
|
||||
|
||||
(defn- call-fn* [root-ast the-fn args]
|
||||
# (print "on line " (get-in root-ast [:token :line]))
|
||||
# (print "calling " (b/show the-fn))
|
||||
# (print "with args " (b/show args))
|
||||
# (pp args)
|
||||
(when (or
|
||||
(= :function (type the-fn))
|
||||
(= :cfunction (type the-fn)))
|
||||
# (print "Janet function")
|
||||
(break (the-fn ;args)))
|
||||
(def clauses (the-fn :body))
|
||||
(when (= :nothing clauses)
|
||||
(error {:node root-ast :called the-fn :value args :msg "cannot call function before it is defined"}))
|
||||
(when (= :function (type clauses))
|
||||
(break (clauses root-ast ;args)))
|
||||
(def len (length clauses))
|
||||
(when (the-fn :match) (break ((the-fn :match) root-ast 0 args)))
|
||||
(defn match-fn [root-ast i args]
|
||||
(when (= len i)
|
||||
(error {:node root-ast :called the-fn :value args :msg "no match: function call"}))
|
||||
(def clause (clauses i))
|
||||
(def [patt guard expr] clause)
|
||||
(def match?
|
||||
(match-pattern patt args @{:^parent (the-fn :ctx)}))
|
||||
(when (not (match? :success))
|
||||
(break (match-fn root-ast (inc i) args)))
|
||||
# (print "matched!")
|
||||
(def body-ctx (match? :ctx))
|
||||
(def guard? (if guard
|
||||
(b/bool (interpret guard body-ctx)) true))
|
||||
# (print "passed guard")
|
||||
(when (not guard?)
|
||||
(break (match-fn root-ast (inc i) args)))
|
||||
(interpret expr body-ctx))
|
||||
(set (the-fn :match) match-fn)
|
||||
(match-fn root-ast 0 args))
|
||||
|
||||
(set call-fn call-fn*)
|
||||
|
||||
(defn- call-partial [root-ast the-fn arg] ((the-fn :body) root-ast ;arg))
|
||||
|
||||
(defn- apply-synth-term [root-ast prev curr]
|
||||
# (print "applying " (b/show prev))
|
||||
# (print "to" (b/show curr))
|
||||
(def types [(b/ludus/type prev) (b/ludus/type curr)])
|
||||
# (print "typle:")
|
||||
# (pp types)
|
||||
(match types
|
||||
[:fn :tuple] (call-fn root-ast prev curr)
|
||||
[:fn :partial] (partial root-ast prev curr)
|
||||
[:function :tuple] (call-fn root-ast prev curr)
|
||||
# [:applied :tuple] (call-partial root-ast prev curr)
|
||||
[:keyword :args] (get (first curr) prev :^nil)
|
||||
[:keyword :tuple] (get (first curr) prev :^nil)
|
||||
[:dict :keyword] (get prev curr :^nil)
|
||||
[:nil :keyword] :^nil
|
||||
[:pkg :keyword] (get prev curr :^nil)
|
||||
[:pkg :pkg-kw] (get prev curr :^nil)
|
||||
(error (string "cannot call " (b/ludus/type prev) " `" (b/show prev) "`"))))
|
||||
|
||||
(defn- synthetic [ast ctx]
|
||||
(def terms (ast :data))
|
||||
# (print "interpreting synthetic")
|
||||
# (pp ast)
|
||||
# (pp terms)
|
||||
(def first-term (first terms))
|
||||
(def last-term (last terms))
|
||||
(var prev (interpret first-term ctx))
|
||||
# (print "root term: ")
|
||||
# (pp prev)
|
||||
(for i 1 (-> terms length dec)
|
||||
(def curr (interpret (terms i) ctx))
|
||||
# (print "term " i ": " curr)
|
||||
(set prev (apply-synth-term first-term prev curr)))
|
||||
# (print "done with inner terms, applying last term")
|
||||
(apply-synth-term first-term prev (interpret last-term ctx)))
|
||||
|
||||
(defn- doo [ast ctx]
|
||||
(def terms (ast :data))
|
||||
(var prev (interpret (first terms) ctx))
|
||||
(def last-term (last terms))
|
||||
(for i 1 (-> terms length dec)
|
||||
(def curr (interpret (terms i) ctx))
|
||||
(set prev (apply-synth-term (first terms) curr [prev])))
|
||||
(def last-fn (interpret last-term ctx))
|
||||
(apply-synth-term (first terms) last-fn [prev]))
|
||||
|
||||
(defn- pkg [ast ctx]
|
||||
(def members (ast :data))
|
||||
(def the-pkg @{:^name (ast :name) :^type :pkg})
|
||||
(each member members
|
||||
(def [key-ast value-ast] (member :data))
|
||||
(def key (interpret key-ast ctx))
|
||||
(def value (interpret value-ast ctx))
|
||||
(set (the-pkg key) value))
|
||||
# (pp the-pkg)
|
||||
(def out (table/to-struct the-pkg))
|
||||
(set (ctx (ast :name)) out)
|
||||
out)
|
||||
|
||||
(defn- loopp [ast ctx]
|
||||
# (print "looping!")
|
||||
(def data (ast :data))
|
||||
(def args (interpret (data 0) ctx))
|
||||
# this doesn't work: context persists between different interpretations
|
||||
# we want functions to work this way, but not loops (I think)
|
||||
# (when (ast :match) (break ((ast :match) 0 args)))
|
||||
(def clauses (data 1))
|
||||
(def len (length clauses))
|
||||
(var loop-ctx @{:^parent ctx})
|
||||
(defn match-fn [i args]
|
||||
(when (= len i)
|
||||
(error {:node ast :value args :msg "no match: loop"}))
|
||||
(def clause (clauses i))
|
||||
(def [patt guard expr] clause)
|
||||
(def match?
|
||||
(match-pattern patt args loop-ctx))
|
||||
(when (not (match? :success))
|
||||
# (print "no match")
|
||||
(break (match-fn (inc i) args)))
|
||||
# (print "matched!")
|
||||
(def body-ctx (match? :ctx))
|
||||
(def guard? (if guard
|
||||
(b/bool (interpret guard body-ctx)) true))
|
||||
# (print "passed guard")
|
||||
(when (not guard?)
|
||||
(break (match-fn (inc i) args)))
|
||||
(interpret expr body-ctx))
|
||||
(set (ast :match) match-fn)
|
||||
(set (loop-ctx :^recur) match-fn)
|
||||
# (print "ATTACHED MATCH-FN")
|
||||
(match-fn 0 args))
|
||||
|
||||
(defn- recur [ast ctx]
|
||||
# (print "recurring!")
|
||||
(def passed (ast :data))
|
||||
(def args (interpret passed ctx))
|
||||
(def match-fn (resolve-name :^recur ctx))
|
||||
# (print "match fn in ctx:")
|
||||
# (pp (ctx :^recur))
|
||||
# (pp match-fn)
|
||||
# (pp ctx)
|
||||
(match-fn 0 args))
|
||||
|
||||
# TODO for 0.1.0
|
||||
(defn- testt [ast ctx] (todo "test"))
|
||||
|
||||
(defn- ns [ast ctx] (todo "nses"))
|
||||
|
||||
(defn- importt [ast ctx] (todo "imports"))
|
||||
|
||||
(defn- withh [ast ctx] (todo "with"))
|
||||
|
||||
(defn- usee [ast ctx] (todo "use"))
|
||||
|
||||
(defn- interpret* [ast ctx]
|
||||
# (print "interpreting node " (ast :type))
|
||||
(case (ast :type)
|
||||
# literals
|
||||
:nil :^nil
|
||||
:number (ast :data)
|
||||
:bool (ast :data)
|
||||
:string (ast :data)
|
||||
:keyword (ast :data)
|
||||
:placeholder :_
|
||||
|
||||
# collections
|
||||
:tuple (tup ast ctx)
|
||||
:args (args ast ctx)
|
||||
:list (list ast ctx)
|
||||
:set (sett ast ctx)
|
||||
:dict (dict ast ctx)
|
||||
|
||||
# composite forms
|
||||
:if (iff ast ctx)
|
||||
:block (block ast ctx)
|
||||
:when (whenn ast ctx)
|
||||
:script (script ast ctx)
|
||||
:panic (panic ast ctx)
|
||||
|
||||
# looping forms
|
||||
:loop (loopp ast ctx)
|
||||
:recur (recur ast ctx)
|
||||
:repeat (repeatt ast ctx)
|
||||
|
||||
# named/naming forms
|
||||
:word (word ast ctx)
|
||||
:interpolated (interpolated ast ctx)
|
||||
:box (box ast ctx)
|
||||
:pkg (pkg ast ctx)
|
||||
:pkg-name (word ast ctx)
|
||||
|
||||
# patterned forms
|
||||
:let (lett ast ctx)
|
||||
:match (matchh ast ctx)
|
||||
|
||||
# functions
|
||||
:fn (fnn ast ctx)
|
||||
|
||||
# synthetic
|
||||
:synthetic (synthetic ast ctx)
|
||||
|
||||
# do
|
||||
:do (doo ast ctx)
|
||||
|
||||
# deferred until after computer class
|
||||
# :with (withh ast ctx)
|
||||
# :import (importt ast ctx)
|
||||
# :ns (ns ast ctx)
|
||||
# :use (usee ast ctx)
|
||||
# :test (testt ast ctx)
|
||||
|
||||
))
|
||||
|
||||
(set interpret interpret*)
|
||||
|
||||
# # repl
|
||||
# (import /src/scanner :as s)
|
||||
# (import /src/parser :as p)
|
||||
# (import /src/validate :as v)
|
||||
|
||||
# (var source nil)
|
||||
|
||||
# (defn- has-errors? [{:errors errors}] (and errors (not (empty? errors))))
|
||||
|
||||
# (defn run []
|
||||
# (def scanned (s/scan source))
|
||||
# (when (has-errors? scanned) (break (scanned :errors)))
|
||||
# (def parsed (p/parse scanned))
|
||||
# (when (has-errors? parsed) (break (parsed :errors)))
|
||||
# (def validated (v/valid parsed b/ctx))
|
||||
# # (when (has-errors? validated) (break (validated :errors)))
|
||||
# # (def cleaned (get-in parsed [:ast :data 1]))
|
||||
# # # (pp cleaned)
|
||||
# (interpret (parsed :ast) @{:^parent b/lett})
|
||||
# # (try (interpret (parsed :ast) @{:^parent b/ctx})
|
||||
# # ([e] (if (struct? e) (error (e :msg)) (error e))))
|
||||
# )
|
||||
|
||||
# # (do
|
||||
# (comment
|
||||
# (set source `
|
||||
# let foo = 42
|
||||
# "{foo} bar baz"
|
||||
# `)
|
||||
# (def result (run))
|
||||
# )
|
||||
|
131
janet/json.janet
131
janet/json.janet
|
@ -1,131 +0,0 @@
|
|||
# pulled from cfiggers/jayson
|
||||
|
||||
(defmacro- letv [bindings & body]
|
||||
~(do ,;(seq [[k v] :in (partition 2 bindings)] ['var k v]) ,;body))
|
||||
|
||||
(defn- read-hex [n]
|
||||
(scan-number (string "0x" n)))
|
||||
|
||||
(defn- check-utf-16 [capture]
|
||||
(let [u (read-hex capture)]
|
||||
(if (and (>= u 0xD800)
|
||||
(<= u 0xDBFF))
|
||||
capture
|
||||
false)))
|
||||
|
||||
(def- utf-8->bytes
|
||||
(peg/compile
|
||||
~{:double-u-esc (/ (* "\\u" (cmt (<- 4) ,|(check-utf-16 $)) "\\u" (<- 4))
|
||||
,|(+ (blshift (- (read-hex $0) 0xD800) 10)
|
||||
(- (read-hex $1) 0xDC00) 0x10000))
|
||||
:single-u-esc (/ (* "\\u" (<- 4)) ,|(read-hex $))
|
||||
:unicode-esc (/ (+ :double-u-esc :single-u-esc)
|
||||
,|(string/from-bytes
|
||||
;(cond
|
||||
(<= $ 0x7f) [$]
|
||||
(<= $ 0x7ff)
|
||||
[(bor (band (brshift $ 6) 0x1F) 0xC0)
|
||||
(bor (band (brshift $ 0) 0x3F) 0x80)]
|
||||
(<= $ 0xffff)
|
||||
[(bor (band (brshift $ 12) 0x0F) 0xE0)
|
||||
(bor (band (brshift $ 6) 0x3F) 0x80)
|
||||
(bor (band (brshift $ 0) 0x3F) 0x80)]
|
||||
# Otherwise
|
||||
[(bor (band (brshift $ 18) 0x07) 0xF0)
|
||||
(bor (band (brshift $ 12) 0x3F) 0x80)
|
||||
(bor (band (brshift $ 6) 0x3F) 0x80)
|
||||
(bor (band (brshift $ 0) 0x3F) 0x80)])))
|
||||
:escape (/ (* "\\" (<- (set "avbnfrt\"\\/")))
|
||||
,|(get {"a" "\a" "v" "\v" "b" "\b"
|
||||
"n" "\n" "f" "\f" "r" "\r"
|
||||
"t" "\t"} $ $))
|
||||
:main (+ (some (+ :unicode-esc :escape (<- 1))) -1)}))
|
||||
|
||||
(defn decode
|
||||
``
|
||||
Returns a janet object after parsing JSON. If `keywords` is truthy,
|
||||
string keys will be converted to keywords. If `nils` is truthy, `null`
|
||||
will become `nil` instead of the keyword `:json/null`.
|
||||
``
|
||||
[json-source &opt keywords nils]
|
||||
|
||||
(def json-parser
|
||||
{:null (if nils
|
||||
~(/ (<- (+ "null" "Null")) nil)
|
||||
~(/ (<- (+ "null" "Null")) :json/null))
|
||||
:bool-t ~(/ (<- (+ "true")) true)
|
||||
:bool-f ~(/ (<- (+ "false")) false)
|
||||
:number ~(/ (<- (* (? "-") :d+ (? (* "." :d+)))) ,|(scan-number $))
|
||||
:string ~(/ (* "\"" (<- (to (* (> -1 (not "\\")) "\"")))
|
||||
(* (> -1 (not "\\")) "\""))
|
||||
,|(string/join (peg/match utf-8->bytes $)))
|
||||
:array ~(/ (* "[" :s* (? (* :value (any (* :s* "," :value)))) "]") ,|(array ;$&))
|
||||
:key-value (if keywords
|
||||
~(* :s* (/ :string ,|(keyword $)) :s* ":" :value)
|
||||
~(* :s* :string :s* ":" :value))
|
||||
:object ~(/ (* "{" :s* (? (* :key-value (any (* :s* "," :key-value)))) "}")
|
||||
,|(from-pairs (partition 2 $&)))
|
||||
:value ~(* :s* (+ :null :bool-t :bool-f :number :string :array :object) :s*)
|
||||
:unmatched ~(/ (<- (to (+ :value -1))) ,|[:unmatched $])
|
||||
:main ~(some (+ :value "\n" :unmatched))})
|
||||
|
||||
(first (peg/match (peg/compile json-parser) json-source)))
|
||||
|
||||
(def- bytes->utf-8
|
||||
(peg/compile
|
||||
~{:four-byte (/ (* (<- (range "\xf0\xff")) (<- 1) (<- 1) (<- 1))
|
||||
,|(bor (blshift (band (first $0) 0x07) 18)
|
||||
(blshift (band (first $1) 0x3F) 12)
|
||||
(blshift (band (first $2) 0x3F) 6)
|
||||
(blshift (band (first $3) 0x3F) 0)))
|
||||
:three-byte (/ (* (<- (range "\xe0\xef")) (<- 1) (<- 1))
|
||||
,|(bor (blshift (band (first $0) 0x0F) 12)
|
||||
(blshift (band (first $1) 0x3F) 6)
|
||||
(blshift (band (first $2) 0x3F) 0)))
|
||||
:two-byte (/ (* (<- (range "\x80\xdf")) (<- 1))
|
||||
,|(bor (blshift (band (first $0) 0x1F) 6)
|
||||
(blshift (band (first $1) 0x3F) 0)))
|
||||
:multi-byte (/ (+ :two-byte :three-byte :four-byte)
|
||||
,|(if (< $ 0x10000)
|
||||
(string/format "\\u%04X" $)
|
||||
(string/format "\\u%04X\\u%04X"
|
||||
(+ (brshift (- $ 0x10000) 10) 0xD800)
|
||||
(+ (band (- $ 0x10000) 0x3FF) 0xDC00))))
|
||||
:one-byte (<- (range "\x20\x7f"))
|
||||
:0to31 (/ (<- (range "\0\x1F"))
|
||||
,|(or ({"\a" "\\u0007" "\b" "\\u0008"
|
||||
"\t" "\\u0009" "\n" "\\u000A"
|
||||
"\v" "\\u000B" "\f" "\\u000C"
|
||||
"\r" "\\u000D"} $)
|
||||
(string/format "\\u%04X" (first $))))
|
||||
:backslash (/ (<- "\\") "\\\\")
|
||||
:quote (/ (<- "\"") "\\\"")
|
||||
:main (+ (some (+ :0to31 :backslash :quote :one-byte :multi-byte)) -1)}))
|
||||
|
||||
(defn- encodeone [x depth]
|
||||
(if (> depth 1024) (error "recurred too deeply"))
|
||||
(cond
|
||||
(= x :json/null) "null"
|
||||
(= x nil) "null"
|
||||
(bytes? x) (string "\"" (string/join (peg/match bytes->utf-8 x)) "\"")
|
||||
(indexed? x) (string "[" (string/join (map |(encodeone $ (inc depth)) x) ",") "]")
|
||||
(dictionary? x) (string "{" (string/join
|
||||
(seq [[k v] :in (pairs x)]
|
||||
(string "\"" (string/join (peg/match bytes->utf-8 k)) "\"" ":" (encodeone v (inc depth)))) ",") "}")
|
||||
(case (type x)
|
||||
:nil "null"
|
||||
:boolean (string x)
|
||||
:number (string x)
|
||||
(error "type not supported"))))
|
||||
|
||||
(defn encode
|
||||
``
|
||||
Encodes a janet value in JSON (utf-8). If `buf` is provided, the formated
|
||||
JSON is append to `buf` instead of a new buffer. Returns the modifed buffer.
|
||||
``
|
||||
[x &opt buf]
|
||||
|
||||
(letv [ret (encodeone x 0)]
|
||||
(if (and buf (buffer? buf))
|
||||
(buffer/push ret)
|
||||
(thaw ret))))
|
|
@ -1,110 +0,0 @@
|
|||
# an integrated Ludus interpreter
|
||||
# devised in order to run under wasm
|
||||
# takes a string, returns a string with a json object
|
||||
# (try (os/cd "janet") ([_] nil)) # for REPL
|
||||
(import /janet/scanner :as s)
|
||||
(import /janet/parser :as p)
|
||||
(import /janet/validate :as v)
|
||||
(import /janet/interpreter :as i)
|
||||
(import /janet/errors :as e)
|
||||
(import /janet/base :as b)
|
||||
(import /janet/prelude :as prelude)
|
||||
(import /janet/json :as j)
|
||||
|
||||
(defn ludus [source]
|
||||
# if we can't load prelude, bail
|
||||
(when (= :error prelude/pkg) (error "could not load prelude"))
|
||||
|
||||
# get us a clean working slate
|
||||
(def ctx @{:^parent prelude/ctx})
|
||||
(def errors @[])
|
||||
(var result @"")
|
||||
(def console @"")
|
||||
|
||||
# capture all `print`s
|
||||
(setdyn :out console)
|
||||
|
||||
# an output table
|
||||
# this will change: the shape of our output
|
||||
# at the moment, there's only one stack of turtle graphics
|
||||
# we will be getting more
|
||||
(def out @{:errors errors :result result
|
||||
:io @{
|
||||
:stdout @{:proto [:text-stream "0.1.0"] :data console}
|
||||
:turtle @{:proto [:turtle-graphics "0.1.0"] :data @[]}}})
|
||||
|
||||
### start the program
|
||||
# first, scanning
|
||||
(def scanned (s/scan source))
|
||||
(when (any? (scanned :errors))
|
||||
(each err (scanned :errors)
|
||||
(e/scan-error err))
|
||||
(break (-> out j/encode string)))
|
||||
# then, parsing
|
||||
(def parsed (p/parse scanned))
|
||||
(when (any? (parsed :errors))
|
||||
(each err (parsed :errors)
|
||||
(e/parse-error err))
|
||||
(break (-> out j/encode string)))
|
||||
# then, validation
|
||||
(def validated (v/valid parsed ctx))
|
||||
(when (any? (validated :errors))
|
||||
(each err (validated :errors)
|
||||
(e/validation-error err))
|
||||
(break (-> out j/encode string)))
|
||||
# and, finally, try interpreting the program
|
||||
(try (do
|
||||
# we need to do this every run or we get the very same sequence of "random" numbers every time we run a program
|
||||
(math/seedrandom (os/cryptorand 8))
|
||||
(set result (i/interpret (parsed :ast) ctx)))
|
||||
([err]
|
||||
(e/runtime-error err)
|
||||
(break (-> out j/encode string))))
|
||||
|
||||
# stop capturing output
|
||||
(setdyn :out stdout)
|
||||
|
||||
# update our output table with our output
|
||||
(set (out :result) (b/show result))
|
||||
(set (((out :io) :turtle) :data) (get-in prelude/pkg [:turtle_commands :^value]))
|
||||
|
||||
# run the "postlude": any Ludus code that needs to run after each program
|
||||
# right now this is just resetting the boxes that hold turtle commands and state
|
||||
(try
|
||||
(i/interpret prelude/post/ast ctx)
|
||||
([err] (e/runtime-error err)))
|
||||
|
||||
# json-encode our output table, and convert it from a buffer to a string (which we require for playing nice with WASM/C)
|
||||
(-> out j/encode string))
|
||||
|
||||
#### REPL
|
||||
(comment
|
||||
# (do
|
||||
(def start (os/clock))
|
||||
(def source `
|
||||
|
||||
fn fib {
|
||||
(1) -> 1
|
||||
(2) -> 1
|
||||
(n) -> add (
|
||||
fib (sub (n, 1))
|
||||
fib (sub (n, 2))
|
||||
)
|
||||
}
|
||||
|
||||
fib (30)
|
||||
`)
|
||||
(def out (-> source
|
||||
ludus
|
||||
j/decode
|
||||
))
|
||||
(def end (os/clock))
|
||||
(setdyn :out stdout)
|
||||
(pp out)
|
||||
(def console (out "console"))
|
||||
(print console)
|
||||
(def result (out "result"))
|
||||
(print result)
|
||||
(print (- end start))
|
||||
)
|
||||
|
1203
janet/parser.janet
1203
janet/parser.janet
File diff suppressed because it is too large
Load Diff
|
@ -1,42 +0,0 @@
|
|||
(import /janet/base :as b)
|
||||
(import /janet/scanner :as s)
|
||||
(import /janet/parser :as p)
|
||||
(import /janet/validate :as v)
|
||||
(import /janet/interpreter :as i)
|
||||
(import /janet/errors :as e)
|
||||
|
||||
(def pkg (do
|
||||
(def pre-ctx @{:^parent {"base" b/base}})
|
||||
(def pre-src (slurp "./assets/prelude.ld"))
|
||||
(def pre-scanned (s/scan pre-src :prelude))
|
||||
(def pre-parsed (p/parse pre-scanned))
|
||||
(def parse-errors (pre-parsed :errors))
|
||||
(when (any? parse-errors) (each err parse-errors (e/parse-error err)) (break :error))
|
||||
# (def pre-validated (v/valid pre-parsed pre-ctx))
|
||||
# (def validation-errors (pre-validated :errors))
|
||||
# (when (any? validation-errors) (each err validation-errors (e/validation-error err)) (break :error))
|
||||
(try
|
||||
(i/interpret (pre-parsed :ast) pre-ctx)
|
||||
([err] (e/runtime-error err) :error))))
|
||||
|
||||
(def ctx (do
|
||||
(def ctx @{})
|
||||
(each [k v] (pairs pkg)
|
||||
(set (ctx (string k)) v))
|
||||
(set (ctx "^name") nil)
|
||||
(set (ctx "^type") nil)
|
||||
ctx))
|
||||
|
||||
# (def post/src (slurp "postlude.ld"))
|
||||
|
||||
# (def post/ast (do
|
||||
# (def post-ctx @{:^parent ctx})
|
||||
# (def post-scanned (s/scan post/src :postlude))
|
||||
# (def post-parsed (p/parse post-scanned))
|
||||
# (def parse-errors (post-parsed :errors))
|
||||
# (when (any? parse-errors) (each err parse-errors (e/parse-error err)) (break :error))
|
||||
# # (def post-validated (v/valid post-parsed post-ctx))
|
||||
# # (def validation-errors (post-validated :errors))
|
||||
# # (when (any? validation-errors) (each err validation-errors (e/validation-error err)) (break :error))
|
||||
# (post-parsed :ast)))
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
(declare-project
|
||||
:dependencies [
|
||||
{:url "https://github.com/ianthehenry/judge.git"
|
||||
:tag "v2.8.1"}
|
||||
{:url "https://github.com/janet-lang/spork"}
|
||||
])
|
||||
|
||||
(declare-source
|
||||
:source ["ludus.janet"])
|
|
@ -1,356 +0,0 @@
|
|||
(def reserved-words
|
||||
"List of Ludus reserved words."
|
||||
## see ludus-spec repo for more info
|
||||
{
|
||||
"as" :as ## impl
|
||||
"box" :box
|
||||
"do" :do ## impl
|
||||
"else" :else ## impl
|
||||
"false" :false ## impl -> literal word
|
||||
"fn" :fn ## impl
|
||||
"if" :if ## impl
|
||||
# "import" :import ## impl
|
||||
"let" :let ## impl
|
||||
"loop" :loop ## impl
|
||||
"match" :match ## impl
|
||||
"nil" :nil ## impl -> literal word
|
||||
# "ns" :ns ## impl
|
||||
"panic!" :panic ## impl (should _not_ be a function)
|
||||
# "pkg" :pkg
|
||||
"receive" :receive
|
||||
"recur" :recur ## impl
|
||||
"repeat" :repeat ## impl
|
||||
# "test" :test
|
||||
"then" :then ## impl
|
||||
"true" :true ## impl -> literal word
|
||||
# "use" :use ## wip
|
||||
"when" :when ## impl, replaces cond
|
||||
"with" :with ## impl
|
||||
})
|
||||
|
||||
(def literal-words {"true" true
|
||||
"false" false
|
||||
"nil" nil
|
||||
})
|
||||
|
||||
(defn- new-scanner
|
||||
"Creates a new scanner."
|
||||
[source input]
|
||||
@{:source source
|
||||
:input input
|
||||
:length (length source)
|
||||
:errors @[]
|
||||
:start 0
|
||||
:current 0
|
||||
:line 1
|
||||
:tokens @[]})
|
||||
|
||||
(defn- at-end?
|
||||
"Tests if a scanner is at end of input."
|
||||
[scanner]
|
||||
(>= (get scanner :current) (get scanner :length)))
|
||||
|
||||
(defn- current-char
|
||||
"Gets the current character of the scanner."
|
||||
[scanner]
|
||||
(let [source (get scanner :source)
|
||||
current (get scanner :current)
|
||||
length (length source)]
|
||||
(if (>= current length)
|
||||
nil
|
||||
(string/from-bytes (get source current)))))
|
||||
|
||||
(defn- advance
|
||||
"Advances the scanner by a single character."
|
||||
[scanner]
|
||||
(update scanner :current inc))
|
||||
|
||||
(defn- next-char
|
||||
"Gets the next character from the scanner."
|
||||
[scanner]
|
||||
(let [source (get scanner :source)
|
||||
current (get scanner :current)
|
||||
next (inc current)
|
||||
length (length source)]
|
||||
(if (>= next length)
|
||||
nil
|
||||
(string/from-bytes (get source next)))))
|
||||
|
||||
(defn- current-lexeme
|
||||
[scanner]
|
||||
(slice (get scanner :source) (get scanner :start) (get scanner :current)))
|
||||
|
||||
(defn- char-code [char] (get char 0))
|
||||
|
||||
(defn- char-in-range? [start end char]
|
||||
(and char
|
||||
(>= (char-code char) (char-code start))
|
||||
(<= (char-code char) (char-code end))))
|
||||
|
||||
(defn- digit? [c]
|
||||
(char-in-range? "0" "9" c))
|
||||
|
||||
(defn- nonzero-digit? [c]
|
||||
(char-in-range? "1" "9" c))
|
||||
|
||||
## for now, use very basic ASCII charset in words
|
||||
## TODO: research the implications of using the whole
|
||||
## (defn- alpha? [c] (boolean (re-find #"\p{L}" (string c))))
|
||||
(defn- alpha? [c]
|
||||
(or (char-in-range? "a" "z" c) (char-in-range? "A" "Z" c)))
|
||||
|
||||
(defn- lower? [c] (char-in-range? "a" "z" c))
|
||||
|
||||
(defn- upper? [c] (char-in-range? "A" "Z" c))
|
||||
|
||||
## legal characters in words
|
||||
(def word-chars {"_" true "?" true "!" true "*" true "/" true})
|
||||
|
||||
(defn- word-char? [c]
|
||||
(or (alpha? c) (digit? c) (get word-chars c)))
|
||||
|
||||
(defn- whitespace? [c]
|
||||
(or (= c " ") (= c "\t")))
|
||||
|
||||
(def terminators {
|
||||
":" true
|
||||
";" true
|
||||
"\n" true
|
||||
"{" true
|
||||
"}" true
|
||||
"(" true
|
||||
")" true
|
||||
"[" true
|
||||
"]" true
|
||||
"$" true
|
||||
"#" true
|
||||
"-" true
|
||||
"=" true
|
||||
"&" true
|
||||
"," true
|
||||
">" true
|
||||
"\"" true})
|
||||
|
||||
(defn- terminates? [c]
|
||||
(or (nil? c) (whitespace? c) (get terminators c)))
|
||||
|
||||
(defn- add-token
|
||||
[scanner token-type &opt literal]
|
||||
(update scanner :tokens array/push
|
||||
{:type token-type
|
||||
:lexeme (current-lexeme scanner)
|
||||
:literal literal
|
||||
:line (get scanner :line)
|
||||
:start (get scanner :start)
|
||||
:source (get scanner :source)
|
||||
:input (get scanner :input)}))
|
||||
|
||||
## TODO: errors should also be in the vector of tokens
|
||||
## The goal is to be able to be able to hand this to an LSP?
|
||||
## Do we need a different structure
|
||||
(defn- add-error [scanner msg]
|
||||
(let [token {:type :error
|
||||
:lexeme (current-lexeme scanner)
|
||||
:literal nil
|
||||
:line (get scanner :line)
|
||||
:start (get scanner :start)
|
||||
:source (get scanner :source)
|
||||
:input (get scanner :input)
|
||||
:msg msg}]
|
||||
(-> scanner
|
||||
(update :errors array/push token)
|
||||
(update :tokens array/push token))))
|
||||
|
||||
(defn- add-keyword
|
||||
[scanner]
|
||||
(defn recur [scanner key]
|
||||
(let [char (current-char scanner)]
|
||||
(cond
|
||||
(terminates? char) (add-token scanner :keyword (keyword key))
|
||||
(word-char? char) (recur (advance scanner) (string key char))
|
||||
:else (add-error scanner (string "Unexpected " char "after keyword :" key)))))
|
||||
(recur scanner ""))
|
||||
|
||||
(defn- add-pkg-kw [scanner]
|
||||
(defn recur [scanner key]
|
||||
(let [char (current-char scanner)]
|
||||
(cond
|
||||
(terminates? char) (add-token scanner :pkg-kw (keyword key))
|
||||
(word-char? char) (recur (advance scanner) (string key char))
|
||||
:else (add-error scanner (string "Unexpected " char " after pkg keyword :" key)))))
|
||||
(recur scanner ""))
|
||||
|
||||
(defn- read-literal [lit] (-> lit parse-all first))
|
||||
|
||||
### TODO: consider whether Janet's number rules are right for Ludus
|
||||
(defn- add-number [char scanner]
|
||||
(defn recur [scanner num float?]
|
||||
(let [curr (current-char scanner)]
|
||||
(cond
|
||||
(= curr "_") (recur (advance scanner) num float?) ## consume underscores unharmed
|
||||
(= curr ".") (if float?
|
||||
(add-error scanner (string "Unexpected second decimal point after " num "."))
|
||||
(recur (advance scanner) (buffer/push num curr) true))
|
||||
(terminates? curr) (add-token scanner :number (read-literal num))
|
||||
(digit? curr) (recur (advance scanner) (buffer/push num curr) float?)
|
||||
:else (add-error scanner (string "Unexpected " curr " after number " num ".")))))
|
||||
(recur scanner (buffer char) false))
|
||||
|
||||
(def escape {
|
||||
"\"" "\""
|
||||
"n" "\n"
|
||||
"{" "{"
|
||||
"t" "\t"
|
||||
"r" "\r"
|
||||
"\\" "\\"
|
||||
})
|
||||
|
||||
(defn- add-string
|
||||
[scanner]
|
||||
(defn recur [scanner buff interpolate?]
|
||||
(let [char (current-char scanner)]
|
||||
(case char
|
||||
"{" (recur (advance scanner) (buffer/push buff char) true)
|
||||
# allow multiline strings
|
||||
"\n" (recur (update (advance scanner) :line inc) (buffer/push buff char) interpolate?)
|
||||
"\"" (add-token (advance scanner) (if interpolate? :interpolated :string) (string buff))
|
||||
"\\" (let [next (next-char scanner)]
|
||||
(recur
|
||||
(advance (advance scanner))
|
||||
(buffer/push buff (get escape next next))
|
||||
interpolate?))
|
||||
(if (at-end? scanner)
|
||||
(add-error scanner "Unterminated string.")
|
||||
(recur (advance scanner) (buffer/push buff char) interpolate?)))))
|
||||
(recur scanner @"" false))
|
||||
|
||||
(defn- add-word
|
||||
[char scanner]
|
||||
(defn recur [scanner word]
|
||||
(let [curr (current-char scanner)]
|
||||
(cond
|
||||
(terminates? curr) (add-token scanner
|
||||
(get reserved-words (string word) :word)
|
||||
(get literal-words (string word) :none))
|
||||
(word-char? curr) (recur (advance scanner) (buffer/push word curr))
|
||||
:else (add-error scanner (string "Unexpected " curr " after word " word ".")))))
|
||||
(recur scanner (buffer char)))
|
||||
|
||||
(defn- add-pkg
|
||||
[char scanner]
|
||||
(defn recur [scanner pkg]
|
||||
(let [curr (current-char scanner)]
|
||||
(cond
|
||||
(terminates? curr) (add-token scanner :pkg-name :none)
|
||||
(word-char? curr) (recur (advance scanner) (buffer/push pkg curr))
|
||||
:else (add-error scanner (string "unexpected " curr " after pkg name " pkg)))))
|
||||
(recur scanner (buffer char)))
|
||||
|
||||
(defn- add-ignored
|
||||
[scanner]
|
||||
(defn recur [scanner ignored]
|
||||
(let [char (current-char scanner)]
|
||||
(cond
|
||||
(terminates? char) (add-token scanner :ignored)
|
||||
(word-char? char) (recur (advance scanner) (buffer/push ignored char))
|
||||
:else (add-error scanner (string "Unexpected " char " after word " ignored ".")))))
|
||||
(recur scanner @"_"))
|
||||
|
||||
(defn- add-comment [char scanner]
|
||||
(defn recur [scanner comm]
|
||||
(let [char (current-char scanner)]
|
||||
(if (or (= "\n" char) (at-end? scanner))
|
||||
scanner # for now, we don't do anything with comments; can be added later
|
||||
(recur (advance scanner) (buffer/push comm char)))))
|
||||
(recur scanner (buffer char)))
|
||||
|
||||
(defn- scan-token [scanner]
|
||||
(let [char (current-char scanner)
|
||||
scanner (advance scanner)
|
||||
next (current-char scanner)]
|
||||
(case char
|
||||
## one-character tokens
|
||||
## :break is a special zero-char token before closing braces
|
||||
## it makes parsing much simpler
|
||||
"(" (add-token scanner :lparen)
|
||||
")" (add-token (add-token scanner :break) :rparen)
|
||||
"{" (add-token scanner :lbrace)
|
||||
"}" (add-token (add-token scanner :break) :rbrace)
|
||||
"[" (add-token scanner :lbracket)
|
||||
"]" (add-token (add-token scanner :break) :rbracket)
|
||||
";" (add-token scanner :semicolon)
|
||||
"," (add-token scanner :comma)
|
||||
"\n" (add-token (update scanner :line inc) :newline)
|
||||
"\\" (add-token scanner :backslash)
|
||||
"=" (add-token scanner :equals)
|
||||
">" (add-token scanner :pipeline)
|
||||
|
||||
## two-character tokens
|
||||
## ->
|
||||
"-" (cond
|
||||
(= next ">") (add-token (advance scanner) :arrow)
|
||||
(digit? next) (add-number char scanner)
|
||||
:else (add-error scanner (string "Expected > or negative number after `-`. Got `" char next "`")))
|
||||
|
||||
## dict #{
|
||||
"#" (if (= next "{")
|
||||
(add-token (advance scanner) :startdict)
|
||||
(add-error scanner (string "Expected beginning of dict: #{. Got " char next)))
|
||||
|
||||
## set ${
|
||||
"$" (if (= next "{")
|
||||
(add-token (advance scanner) :startset)
|
||||
(add-error scanner (string "Expected beginning of set: ${. Got " char next)))
|
||||
|
||||
## placeholders
|
||||
## there's a flat _, and then ignored words
|
||||
"_" (cond
|
||||
(terminates? next) (add-token scanner :placeholder)
|
||||
(alpha? next) (add-ignored scanner)
|
||||
:else (add-error scanner (string "Expected placeholder: _. Got " char next)))
|
||||
|
||||
## comments
|
||||
## & starts an inline comment
|
||||
"&" (add-comment char scanner)
|
||||
|
||||
## keywords
|
||||
# XXX: make sure we want only lower-only keywords
|
||||
":" (cond
|
||||
(lower? next) (add-keyword scanner)
|
||||
(upper? next) (add-pkg-kw scanner)
|
||||
:else (add-error scanner (string "Expected keyword or pkg keyword. Got " char next)))
|
||||
|
||||
## splats
|
||||
"." (let [after_next (current-char (advance scanner))]
|
||||
(if (= ".." (string next after_next))
|
||||
(add-token (advance scanner) :splat)
|
||||
(add-error scanner (string "Expected splat: ... . Got " (string "." next after_next)))))
|
||||
|
||||
## strings
|
||||
"\"" (add-string scanner)
|
||||
|
||||
## word matches
|
||||
(cond
|
||||
(whitespace? char) scanner ## for now just skip whitespace characters
|
||||
(digit? char) (add-number char scanner)
|
||||
(upper? char) (add-pkg char scanner)
|
||||
(lower? char) (add-word char scanner)
|
||||
:else (add-error scanner (string "Unexpected character: " char))))))
|
||||
|
||||
(defn- next-token [scanner]
|
||||
(put scanner :start (get scanner :current)))
|
||||
|
||||
(defn scan [source &opt input]
|
||||
(default input :input)
|
||||
(defn recur [scanner]
|
||||
(if (at-end? scanner)
|
||||
(let [scanner (add-token (add-token scanner :break) :eof)]
|
||||
{:tokens (get scanner :tokens)
|
||||
:errors (get scanner :errors [])})
|
||||
(recur (-> scanner (scan-token) (next-token)))))
|
||||
(recur (new-scanner source input)))
|
||||
|
||||
# (comment
|
||||
(do
|
||||
(def source " -123 ")
|
||||
(length ((scan source) :tokens)))
|
|
@ -1,801 +0,0 @@
|
|||
### A validator for a Ludus AST
|
||||
|
||||
(comment
|
||||
|
||||
Tracking here, before I start writing this code, the kinds of validation we're hoping to accomplish:
|
||||
|
||||
* [x] ensure called keywords are only called w/ one arg
|
||||
* [x] first-level property access with pkg, e.g. `Foo :bar`--bar must be on Foo
|
||||
- [x] accept pkg-kws
|
||||
* [x] validate dict patterns
|
||||
* [x] compile string-patterns
|
||||
* [x] `loop` form arity checking
|
||||
* [x] arity checking of explicit named function calls
|
||||
* [x] flag tail calls
|
||||
* [x] no re-bound names
|
||||
* [x] no unbound names
|
||||
* [x] no unbound names with `use` forms
|
||||
* [x] recur in tail position in `loop` forms
|
||||
* [x] recur not called outside of `loop` forms
|
||||
* [x] splats come at the end of list, tuple, and dict patterns
|
||||
|
||||
Deferred until a later iteration of Ludus:
|
||||
* [ ] no circular imports DEFERRED
|
||||
* [ ] correct imports DEFERRED
|
||||
* [ ] validate `with` forms
|
||||
)
|
||||
|
||||
(def- package-registry @{})
|
||||
|
||||
# (try (os/cd "janet") ([_] nil))
|
||||
(import ./scanner :as s)
|
||||
(import ./parser :as p)
|
||||
|
||||
(defn- new-validator [parser]
|
||||
(def ast (parser :ast))
|
||||
@{:ast ast
|
||||
:errors @[]
|
||||
:ctx @{}
|
||||
:status @{}}
|
||||
)
|
||||
|
||||
(var validate nil)
|
||||
|
||||
(def terminals [:number :string :bool :nil :placeholder])
|
||||
|
||||
(def simple-colls [:list :tuple :set :args])
|
||||
|
||||
(defn- simple-coll [validator]
|
||||
(def ast (validator :ast))
|
||||
(def data (ast :data))
|
||||
(each node data
|
||||
(set (validator :ast) node)
|
||||
(validate validator))
|
||||
validator)
|
||||
|
||||
(defn- iff [validator]
|
||||
(def ast (validator :ast))
|
||||
(def data (ast :data))
|
||||
(each node data
|
||||
(set (validator :ast) node)
|
||||
(validate validator))
|
||||
validator)
|
||||
|
||||
(defn- script [validator]
|
||||
(def ast (validator :ast))
|
||||
(def data (ast :data))
|
||||
(def status (validator :status))
|
||||
(set (status :toplevel) true)
|
||||
(each node data
|
||||
(set (validator :ast) node)
|
||||
(validate validator))
|
||||
validator)
|
||||
|
||||
(defn- block [validator]
|
||||
(def ast (validator :ast))
|
||||
(def data (ast :data))
|
||||
(when (= 0 (length data))
|
||||
(array/push (validator :errors)
|
||||
{:node ast :msg "blocks may not be empty"})
|
||||
(break validator))
|
||||
(def status (validator :status))
|
||||
(set (status :toplevel) nil)
|
||||
(def tail? (status :tail))
|
||||
(set (status :tail) false)
|
||||
(def parent (validator :ctx))
|
||||
(def ctx @{:^parent parent})
|
||||
(set (validator :ctx) ctx)
|
||||
(for i 0 (-> data length dec)
|
||||
(set (validator :ast) (data i))
|
||||
(validate validator))
|
||||
(set (status :tail) tail?)
|
||||
(set (validator :ast) (last data))
|
||||
(validate validator)
|
||||
(set (validator :ctx) parent)
|
||||
validator)
|
||||
|
||||
(defn- resolve-local [ctx name]
|
||||
(get ctx name))
|
||||
|
||||
(defn- resolve-name [ctx name]
|
||||
(when (nil? ctx) (break nil))
|
||||
(def node (get ctx name))
|
||||
(if node node (resolve-name (get ctx :^parent) name)))
|
||||
|
||||
(defn- resolve-name-in-script [ctx name]
|
||||
(when (ctx :^toplevel) (break nil))
|
||||
(def node (ctx name))
|
||||
(if node node (resolve-name-in-script (ctx :^parent) name)))
|
||||
|
||||
(defn- word [validator]
|
||||
(def ast (validator :ast))
|
||||
(def name (ast :data))
|
||||
(def ctx (validator :ctx))
|
||||
(def resolved (resolve-name ctx name))
|
||||
(when (not resolved)
|
||||
(array/push (validator :errors)
|
||||
{:node ast :msg "unbound name"}))
|
||||
validator)
|
||||
|
||||
|
||||
### patterns
|
||||
(var pattern nil)
|
||||
|
||||
(defn- lett [validator]
|
||||
(def ast (validator :ast))
|
||||
(def [lhs rhs] (ast :data))
|
||||
# evaluate the expression first
|
||||
# otherwise lhs names will appear bound
|
||||
(set (validator :ast) rhs)
|
||||
(validate validator)
|
||||
(set (validator :ast) lhs)
|
||||
(pattern validator)
|
||||
validator)
|
||||
|
||||
(defn- splattern [validator]
|
||||
(def ast (validator :ast))
|
||||
(def status (validator :status))
|
||||
(when (not (status :last))
|
||||
(array/push (validator :errors)
|
||||
{:node ast :msg "splats may only come last in collection patterns"}))
|
||||
(def data (ast :data))
|
||||
(when data
|
||||
(set (validator :ast) data)
|
||||
(pattern validator))
|
||||
validator)
|
||||
|
||||
(defn- simple-coll-pattern [validator]
|
||||
(def ast (validator :ast))
|
||||
(def data (ast :data))
|
||||
(when (empty? data) (break validator))
|
||||
(def status (validator :status))
|
||||
(for i 0 (-> data length dec)
|
||||
(set (validator :ast) (get data i))
|
||||
(pattern validator))
|
||||
(set (status :last) true)
|
||||
(set (validator :ast) (last data))
|
||||
(pattern validator)
|
||||
(set (status :last) nil)
|
||||
validator)
|
||||
|
||||
(defn- word-pattern [validator]
|
||||
(def ast (validator :ast))
|
||||
(def name (ast :data))
|
||||
(def ctx (validator :ctx))
|
||||
### XXX TODO: this resolution should ONLY be for userspace, NOT prelude
|
||||
(def resolved (resolve-name-in-script ctx name))
|
||||
(when resolved
|
||||
(def {:line line :input input} resolved)
|
||||
(array/push (validator :errors)
|
||||
{:node ast :msg (string "name " name " is already bound on line "
|
||||
line " of " input)}))
|
||||
(set (ctx name) ast)
|
||||
# (pp ctx)
|
||||
validator)
|
||||
|
||||
(def types [
|
||||
:nil
|
||||
:bool
|
||||
:number
|
||||
:keyword
|
||||
:string
|
||||
:set
|
||||
:tuple
|
||||
:dict
|
||||
:list
|
||||
:fn
|
||||
:box
|
||||
:pkg
|
||||
])
|
||||
|
||||
(defn typed [validator]
|
||||
(def ast (validator :ast))
|
||||
(def [kw-type word] (ast :data))
|
||||
(def type (kw-type :data))
|
||||
(when (not (has-value? types type))
|
||||
(array/push (validator :errors)
|
||||
{:node kw-type :msg "unknown type"}))
|
||||
(set (validator :ast) word)
|
||||
(pattern validator))
|
||||
|
||||
(defn- str-pattern [validator]
|
||||
(def ast (validator :ast))
|
||||
(def data (ast :data))
|
||||
(def last-term (-> data array/pop string))
|
||||
(def grammar @{})
|
||||
(def bindings @[])
|
||||
(var current 0)
|
||||
(each node data
|
||||
(when (not (buffer? node))
|
||||
(set (validator :ast) node)
|
||||
(pattern validator))
|
||||
(if (buffer? node)
|
||||
(set (grammar (keyword current)) (string node))
|
||||
(do
|
||||
(set (grammar (keyword current))
|
||||
~(<- (to ,(keyword (inc current)))))
|
||||
(array/push bindings (node :data))))
|
||||
(set current (inc current)))
|
||||
(set (grammar (keyword current)) ~(* ,last-term -1))
|
||||
(def rules (map keyword (range (length grammar))))
|
||||
(set (grammar :main) ~(* ,;rules))
|
||||
(set (ast :grammar) grammar)
|
||||
(set (ast :compiled) (peg/compile grammar))
|
||||
(set (ast :bindings) bindings))
|
||||
|
||||
(defn- pair [validator]
|
||||
(def ast (validator :ast))
|
||||
(def [_ patt] (ast :data))
|
||||
(set (validator :ast) patt)
|
||||
(pattern validator))
|
||||
|
||||
(defn- pattern* [validator]
|
||||
# (print "PATTERN*")
|
||||
(def ast (validator :ast))
|
||||
(def type (ast :type))
|
||||
# (print "validating pattern " type)
|
||||
(cond
|
||||
(has-value? terminals type) validator
|
||||
(case type
|
||||
:word (word-pattern validator)
|
||||
:placeholder validator
|
||||
:ignored validator
|
||||
:word (word-pattern validator)
|
||||
:list (simple-coll-pattern validator)
|
||||
:tuple (simple-coll-pattern validator)
|
||||
:dict (simple-coll-pattern validator)
|
||||
:splat (splattern validator)
|
||||
:typed (typed validator)
|
||||
:interpolated (str-pattern validator)
|
||||
:pair (pair validator)
|
||||
)))
|
||||
|
||||
(set pattern pattern*)
|
||||
|
||||
# XXX: ensure guard includes only allowable names
|
||||
# XXX: what to include here? (cf Elixir)
|
||||
(defn- guard [validator])
|
||||
|
||||
(defn- match-clauses [validator clauses]
|
||||
# (print "validating clauses in match-clauses")
|
||||
(each clause clauses
|
||||
(def parent (validator :ctx))
|
||||
(def ctx @{:^parent parent})
|
||||
(set (validator :ctx) ctx)
|
||||
(def [lhs guard rhs] clause)
|
||||
(set (validator :ast) lhs)
|
||||
(pattern validator)
|
||||
# (pp (validator :ctx))
|
||||
# (pp (validator :ctx))
|
||||
(when guard
|
||||
(set (validator :ast) guard)
|
||||
(validate validator))
|
||||
(set (validator :ast) rhs)
|
||||
(validate validator)
|
||||
(set (validator :ctx) parent)))
|
||||
|
||||
(defn- matchh [validator]
|
||||
# (print "validating in matchh")
|
||||
(def ast (validator :ast))
|
||||
(def [to-match clauses] (ast :data))
|
||||
# (print "validating expression:")
|
||||
# (pp to-match)
|
||||
(set (validator :ast) to-match)
|
||||
(validate validator)
|
||||
# (print "validating clauses")
|
||||
(match-clauses validator clauses)
|
||||
validator)
|
||||
|
||||
(defn- receive [validator]
|
||||
(def ast (validator :ast))
|
||||
(def [clauses] (ast :data))
|
||||
(match-clauses validator clauses)
|
||||
validator)
|
||||
|
||||
|
||||
(defn- declare [validator fnn]
|
||||
(def status (validator :status))
|
||||
(def declared (get status :declared @{}))
|
||||
(set (declared fnn) true)
|
||||
(set (status :declared) declared)
|
||||
# (print "declared function " (fnn :name))
|
||||
# (pp declared)
|
||||
validator)
|
||||
|
||||
(defn- define [validator fnn]
|
||||
(def status (validator :status))
|
||||
(def declared (get status :declared @{}))
|
||||
(set (declared fnn) nil)
|
||||
(set (status :declared) declared)
|
||||
# (print "defined function " (fnn :name))
|
||||
# (pp declared)
|
||||
validator)
|
||||
|
||||
(defn- fnn [validator]
|
||||
(def ast (validator :ast))
|
||||
(def name (ast :name))
|
||||
# (print "function name: " name)
|
||||
(def status (validator :status))
|
||||
(def tail? (status :tail))
|
||||
(set (status :tail) true)
|
||||
(when name
|
||||
(def ctx (validator :ctx))
|
||||
(def resolved (ctx name))
|
||||
(when (and resolved (not= :nothing (resolved :data)))
|
||||
(def {:line line :input input} (get-in ctx [name :token]))
|
||||
(array/push (validator :errors)
|
||||
{:node ast :msg (string "name is already bound on line " line " of " input)}))
|
||||
(when (and resolved (= :nothing (resolved :data)))
|
||||
(define validator resolved))
|
||||
(set (ctx name) ast))
|
||||
(def data (ast :data))
|
||||
(when (= data :nothing)
|
||||
(break (declare validator ast)))
|
||||
(match-clauses validator data)
|
||||
(set (status :tail) tail?)
|
||||
(def rest-arities @{})
|
||||
(def arities @{:rest rest-arities})
|
||||
(each clause data
|
||||
# (print "CLAUSE:")
|
||||
# (pp clause)
|
||||
(def patt (first clause))
|
||||
(def params (patt :data))
|
||||
(def arity (length params))
|
||||
# (print "checking clause with arity " arity)
|
||||
(def rest-param? (and (> arity 0) (= :splat ((last params) :type))))
|
||||
(if rest-param?
|
||||
(set (rest-arities arity) true)
|
||||
(set (arities arity) true)))
|
||||
# (pp arities)
|
||||
(set (ast :arities) arities)
|
||||
validator)
|
||||
|
||||
(defn- box [validator]
|
||||
(def ast (validator :ast))
|
||||
(def ctx (validator :ctx))
|
||||
(def expr (ast :data))
|
||||
(set (validator :ast) expr)
|
||||
(validate validator)
|
||||
(def name (ast :name))
|
||||
(def resolved (ctx name))
|
||||
(when resolved
|
||||
(def {:line line :input input} (get-in ctx [name :token]))
|
||||
(array/push (validator :errors)
|
||||
{:node ast :msg (string "name is already bound on line " line " of " input)}))
|
||||
(set (ctx name) ast)
|
||||
validator)
|
||||
|
||||
(defn- interpolated [validator]
|
||||
(def ast (validator :ast))
|
||||
(def data (ast :data))
|
||||
(each node data
|
||||
(when (not (buffer? node))
|
||||
(set (validator :ast) node)
|
||||
(validate validator))))
|
||||
|
||||
### TODO:
|
||||
# * [ ] ensure properties are on pkgs (if *only* pkgs from root)
|
||||
|
||||
(defn- pkg-root [validator]
|
||||
# (print "validating pkg-root access")
|
||||
(def ast (validator :ast))
|
||||
(def ctx (validator :ctx))
|
||||
(def terms (ast :data))
|
||||
(def pkg-name ((first terms) :data))
|
||||
(def the-pkg (resolve-name ctx pkg-name))
|
||||
(when (not the-pkg)
|
||||
(array/push (validator :errors)
|
||||
{:node ast :msg "unbound pkg name"})
|
||||
(break validator))
|
||||
(def member (get terms 1))
|
||||
(def accessed (case (member :type)
|
||||
:keyword (get-in the-pkg [:pkg (member :data)])
|
||||
:pkg-kw (get-in the-pkg [:pkg (member :data)])
|
||||
:args (do
|
||||
(array/push (validator :errors)
|
||||
{:node member :msg "cannot call a pkg"}
|
||||
(break validator)))))
|
||||
(when (not accessed)
|
||||
# (print "no member " (member :data) " on " pkg-name)
|
||||
(array/push (validator :errors)
|
||||
{:node member :msg "invalid pkg access"})
|
||||
(break validator))
|
||||
# TODO: validate nested pkg access
|
||||
)
|
||||
|
||||
# (defn- tail-call [validator]
|
||||
# (def ast (validator :ast))
|
||||
# (when (ast :partial) (break validator))
|
||||
# (def status (validator :status))
|
||||
# (when (not (status :tail)) (break validator))
|
||||
# (def data (ast :data))
|
||||
# (def args (last data))
|
||||
# (set (args :tail-call) true))
|
||||
|
||||
(defn- check-arity [validator]
|
||||
# (print "CHECKING ARITY")
|
||||
(def ast (validator :ast))
|
||||
# (when (ast :partial) (break validator))
|
||||
(def ctx (validator :ctx))
|
||||
(def data (ast :data))
|
||||
(def fn-word (first data))
|
||||
# (pp fn-word)
|
||||
(def the-fn (resolve-name ctx (fn-word :data)))
|
||||
# (print "the called function: " the-fn)
|
||||
# (pp the-fn)
|
||||
(when (not the-fn) (break validator))
|
||||
# (print "the function is not nil")
|
||||
# (print "the function type is " (type the-fn))
|
||||
(when (= :function (type the-fn)) (break validator))
|
||||
(when (= :cfunction (type the-fn)) (break validator))
|
||||
# (print "the function is not a janet fn")
|
||||
# (print "fn type: " (the-fn :type))
|
||||
(when (not= :fn (the-fn :type)) (break validator))
|
||||
# (print "fn name: " (the-fn :name))
|
||||
(def arities (the-fn :arities))
|
||||
# when there aren't arities yet, break, since that means we're making a recursive function call
|
||||
# TODO: enahnce this so that we can determine arities *before* all function bodies; this ensures arity-checking for self-recursive calls
|
||||
(when (not arities) (break validator))
|
||||
# (print "arities: ")
|
||||
# (pp arities)
|
||||
(def args (get data 1))
|
||||
(def num-args (length (args :data)))
|
||||
# (print "called with #args " num-args)
|
||||
# (pp (get (validator :ctx) "bar"))
|
||||
(when (has-key? arities num-args) (break validator))
|
||||
# (print "arities: ")
|
||||
# (pp arities)
|
||||
(when (not arities) (break validator))
|
||||
(def rest-arities (keys (arities :rest)))
|
||||
(when (empty? rest-arities)
|
||||
(array/push (validator :errors)
|
||||
{:node ast :msg "wrong number of arguments"})
|
||||
(break validator))
|
||||
(def rest-min (min ;rest-arities))
|
||||
(when (< num-args rest-min)
|
||||
(array/push (validator :errors)
|
||||
{:node ast :msg "wrong number of arguments"}))
|
||||
validator)
|
||||
|
||||
(defn- kw-root [validator]
|
||||
(def ast (validator :ast))
|
||||
(def data (ast :data))
|
||||
(def [_ args] data)
|
||||
(when (not= :args (args :type))
|
||||
(break (array/push (validator :errors)
|
||||
{:node args :msg "called keyword expects an argument"})))
|
||||
(when (not= 1 (length (args :data)))
|
||||
(array/push (validator :errors)
|
||||
{:node args :msg "called keywords take one argument"})))
|
||||
|
||||
(defn- synthetic [validator]
|
||||
(def ast (validator :ast))
|
||||
(def data (ast :data))
|
||||
(def status (validator :status))
|
||||
(def ftype ((first data) :type))
|
||||
(def stype ((get data 1) :type))
|
||||
(def ltype ((last data) :type))
|
||||
(set (status :pkg-access?) nil)
|
||||
(when (= ftype :pkg-name)
|
||||
(set (status :pkg-access?) true))
|
||||
(each node data
|
||||
(set (validator :ast) node)
|
||||
(validate validator))
|
||||
(set (validator :ast) ast)
|
||||
# (print "ftype " ftype)
|
||||
# (print "stype " stype)
|
||||
# (print "ltype " ltype)
|
||||
(when (= ftype :pkg-name) (pkg-root validator))
|
||||
(when (= ftype :keyword) (kw-root validator))
|
||||
# (when (= ltype :args) (tail-call validator))
|
||||
(when (and (= ftype :word) (= stype :args))
|
||||
(check-arity validator))
|
||||
validator)
|
||||
|
||||
(defn- pair [validator]
|
||||
(def ast (validator :ast))
|
||||
(def [k v] (ast :data))
|
||||
(set (validator :ast) k)
|
||||
(validate validator)
|
||||
(set (validator :ast) v)
|
||||
(validate validator))
|
||||
|
||||
(defn- splat [validator]
|
||||
(def ast (validator :ast))
|
||||
(when (get-in validator [:status :pkg])
|
||||
(array/push (validator :errors)
|
||||
{:node ast :msg "splats are not allowed in pkgs"})
|
||||
(break validator))
|
||||
(def data (ast :data))
|
||||
(when data
|
||||
(set (validator :ast) data)
|
||||
(validate validator))
|
||||
validator)
|
||||
|
||||
(defn- dict [validator]
|
||||
(def ast (validator :ast))
|
||||
(def data (ast :data))
|
||||
(each node data
|
||||
(set (validator :ast) node)
|
||||
(validate validator))
|
||||
validator)
|
||||
|
||||
(defn- whenn [validator]
|
||||
(def ast (validator :ast))
|
||||
(def data (ast :data))
|
||||
(each node data
|
||||
(def [lhs rhs] node)
|
||||
(set (validator :ast) lhs)
|
||||
(validate validator)
|
||||
(set (validator :ast) rhs)
|
||||
(validate validator))
|
||||
validator)
|
||||
|
||||
# XXX: do this!
|
||||
(defn- withh [validator])
|
||||
|
||||
# XXX: tail calls in last position
|
||||
(defn- doo [validator]
|
||||
(def ast (validator :ast))
|
||||
(def data (ast :data))
|
||||
(each node data
|
||||
(set (validator :ast) node)
|
||||
(validate validator))
|
||||
validator)
|
||||
|
||||
(defn- usee [validator]
|
||||
(def ast (validator :ast))
|
||||
(def data (ast :data))
|
||||
(set (validator :ast) data)
|
||||
(validate validator)
|
||||
(def name (data :data))
|
||||
(def ctx (validator :ctx))
|
||||
(def pkg (get-in ctx [name :pkg] @{}))
|
||||
(loop [[k v] :pairs pkg]
|
||||
(set (ctx (string k)) v))
|
||||
validator)
|
||||
|
||||
(defn- pkg-entry [validator pkg]
|
||||
(def ast (validator :ast))
|
||||
(def status (validator :status))
|
||||
(when (= :pkg-pair (ast :type))
|
||||
(set (status :pkg-access?) true))
|
||||
(def data (ast :data))
|
||||
(def [key value] (ast :data))
|
||||
# (print "PKG ENTRY***")
|
||||
# (pp key)
|
||||
# (pp value)
|
||||
(set (validator :ast) key)
|
||||
(validate validator)
|
||||
(set (validator :ast) value)
|
||||
(validate validator)
|
||||
(def entry (if (= :pkg-name (value :type))
|
||||
(resolve-name (validator :ctx) (string (value :data)))
|
||||
value))
|
||||
# (print "entry at " (key :data))
|
||||
# (pp entry)
|
||||
(set (status :pkg-access?) nil)
|
||||
(def kw (key :data))
|
||||
# (pp kw)
|
||||
(set (pkg kw) entry)
|
||||
# (pp pkg)
|
||||
validator)
|
||||
|
||||
(defn- pkg [validator]
|
||||
(def ast (validator :ast))
|
||||
(def data (ast :data))
|
||||
(def name (ast :name))
|
||||
(def pkg @{})
|
||||
(each node data
|
||||
(set (validator :ast) node)
|
||||
(pkg-entry validator pkg))
|
||||
(set (ast :pkg) pkg)
|
||||
# (print "THE PACKAGE")
|
||||
# (pp pkg)
|
||||
(def ctx (validator :ctx))
|
||||
(set (ctx name) ast)
|
||||
validator)
|
||||
|
||||
(defn- ns [validator]
|
||||
(def ast (validator :ast))
|
||||
(def data (ast :data))
|
||||
(def name (ast :name))
|
||||
(def parent (validator :ctx))
|
||||
(def ctx @{:^parent parent})
|
||||
(def block (data :data))
|
||||
(each node block
|
||||
(set (validator :ast) node)
|
||||
(validate validator))
|
||||
(set (ast :pkg) ctx)
|
||||
(set (parent name) ast)
|
||||
validator)
|
||||
|
||||
(defn- loopp [validator]
|
||||
(def ast (validator :ast))
|
||||
(def status (validator :status))
|
||||
(def data (ast :data))
|
||||
(def input (first data))
|
||||
# (print "LOOP INPUT")
|
||||
# (pp input)
|
||||
(def clauses (get data 1))
|
||||
(def input-arity (length (input :data)))
|
||||
(set (ast :arity) input-arity)
|
||||
# (print "input arity to loop " input-arity)
|
||||
(set (validator :ast) input)
|
||||
(validate validator)
|
||||
# harmonize arities
|
||||
(def rest-arities @{})
|
||||
(each clause clauses
|
||||
# (print "CLAUSE:")
|
||||
# (pp clause)
|
||||
(def patt (first clause))
|
||||
(def params (patt :data))
|
||||
(def clause-arity (length params))
|
||||
# (print "checking clause with arity " clause-arity)
|
||||
(def rest-param? (= :splat (get (last params) :type)))
|
||||
(when (and
|
||||
(not rest-param?) (not= clause-arity input-arity))
|
||||
(array/push (validator :errors)
|
||||
{:node patt :msg "arity mismatch"}))
|
||||
(when rest-param?
|
||||
(set (rest-arities clause-arity) patt)))
|
||||
# (pp rest-arities)
|
||||
(loop [[arity patt] :pairs rest-arities]
|
||||
(when (< input-arity arity)
|
||||
(array/push (validator :errors)
|
||||
{:node patt :msg "arity mismatch"})))
|
||||
(def loop? (status :loop))
|
||||
(set (status :loop) input-arity)
|
||||
(def tail? (status :tail))
|
||||
(set (status :tail) true)
|
||||
(match-clauses validator clauses)
|
||||
(set (status :loop) loop?)
|
||||
(set (status :tail) tail?)
|
||||
validator)
|
||||
|
||||
(defn- recur [validator]
|
||||
(def ast (validator :ast))
|
||||
(def status (validator :status))
|
||||
(def loop-arity (status :loop))
|
||||
(when (not loop-arity)
|
||||
(array/push (validator :errors)
|
||||
{:node ast :msg "recur may only be used inside a loop"})
|
||||
(break validator))
|
||||
(def called-with (get-in ast [:data :data]))
|
||||
(def recur-arity (length called-with))
|
||||
# (print "loop arity " loop-arity)
|
||||
# (print "recur arity" recur-arity)
|
||||
(when (not= recur-arity loop-arity)
|
||||
(array/push (validator :errors)
|
||||
{:node ast :msg "recur must have the same number of args as its loop"}))
|
||||
(when (not (status :tail))
|
||||
(array/push (validator :errors)
|
||||
{:node ast :msg "recur must be in tail position"}))
|
||||
(set (validator :ast) (ast :data))
|
||||
(validate validator))
|
||||
|
||||
(defn- repeatt [validator]
|
||||
(def ast (validator :ast))
|
||||
(def [times body] (ast :data))
|
||||
(set (validator :ast) times)
|
||||
(validate validator)
|
||||
(set (validator :ast) body)
|
||||
(validate validator))
|
||||
|
||||
(defn- panic [validator]
|
||||
(def ast (validator :ast))
|
||||
(def data (ast :data))
|
||||
(set (validator :ast) data)
|
||||
(validate validator))
|
||||
|
||||
(defn- testt [validator]
|
||||
(def ast (validator :ast))
|
||||
(def [_ body] (ast :data))
|
||||
(set (validator :ast) body)
|
||||
(validate validator))
|
||||
|
||||
(defn- pkg-name [validator]
|
||||
(def ast (validator :ast))
|
||||
(def name (ast :data))
|
||||
(def ctx (validator :ctx))
|
||||
(def pkg (resolve-name ctx name))
|
||||
(when (not pkg)
|
||||
(array/push (validator :errors)
|
||||
{:node ast :msg "unbound name"}))
|
||||
validator)
|
||||
|
||||
(defn- pkg-kw [validator]
|
||||
# (print "validating pkg-kw")
|
||||
(def ast (validator :ast))
|
||||
(def pkg-access? (get-in validator [:status :pkg-access?]))
|
||||
# (print "pkg-access? " pkg-access?)
|
||||
(when (not pkg-access?)
|
||||
(array/push (validator :errors)
|
||||
{:node ast :msg "cannot use pkg-kw here"}))
|
||||
validator)
|
||||
|
||||
(defn- pkg-pair [validator]
|
||||
# (print "validating pkg-pair")
|
||||
(def ast (validator :ast))
|
||||
(def status (validator :status))
|
||||
(def [_ pkg] (ast :data))
|
||||
(set (status :pkg-access?) true)
|
||||
(set (validator :ast) pkg)
|
||||
(validate validator)
|
||||
(set (status :pkg-access?) nil)
|
||||
validator)
|
||||
|
||||
(defn- kw [validator]
|
||||
(def status (validator :status))
|
||||
(set (status :pkg-access?) nil)
|
||||
validator)
|
||||
|
||||
(defn- validate* [validator]
|
||||
(def ast (validator :ast))
|
||||
(def type (ast :type))
|
||||
# (print "validating node " type)
|
||||
(cond
|
||||
(has-value? terminals type) validator
|
||||
(has-value? simple-colls type) (simple-coll validator)
|
||||
(case type
|
||||
:keyword (kw validator)
|
||||
:if (iff validator)
|
||||
:let (lett validator)
|
||||
:script (script validator)
|
||||
:block (block validator)
|
||||
:word (word validator)
|
||||
:fn (fnn validator)
|
||||
:match (matchh validator)
|
||||
:interpolated (interpolated validator)
|
||||
:synthetic (synthetic validator)
|
||||
:do (doo validator)
|
||||
:dict (dict validator)
|
||||
:test (testt validator)
|
||||
:panic (panic validator)
|
||||
:repeat (repeatt validator)
|
||||
:when (whenn validator)
|
||||
:splat (splat validator)
|
||||
:pair (pair validator)
|
||||
:pkg-pair (pkg-pair validator)
|
||||
:ns (ns validator)
|
||||
:pkg (pkg validator)
|
||||
:pkg-name (pkg-name validator)
|
||||
:pkg-kw (pkg-kw validator)
|
||||
:use (usee validator)
|
||||
:loop (loopp validator)
|
||||
:recur (recur validator)
|
||||
:box (box validator)
|
||||
:receive (receive validator)
|
||||
(error (string "unknown node type " type)))))
|
||||
|
||||
(set validate validate*)
|
||||
|
||||
(defn- cleanup [validator]
|
||||
(def declared (get-in validator [:status :declared] {}))
|
||||
(when (any? declared)
|
||||
(each declaration (keys declared)
|
||||
(array/push (validator :errors) {:node declaration :msg "declared fn, but not defined"})))
|
||||
validator)
|
||||
|
||||
(defn valid [ast &opt ctx]
|
||||
(default ctx @{})
|
||||
(set (ctx :^toplevel) true)
|
||||
(def validator (new-validator ast))
|
||||
(def base-ctx @{:^parent ctx})
|
||||
(set (validator :ctx) base-ctx)
|
||||
(validate validator)
|
||||
(cleanup validator))
|
||||
|
||||
(import ./base :as b)
|
||||
|
||||
# (do
|
||||
(comment
|
||||
(def source `
|
||||
dec (12)
|
||||
`)
|
||||
(def scanned (s/scan source))
|
||||
(def parsed (p/parse scanned))
|
||||
(def validated (valid parsed b/ctx))
|
||||
# (get-in validated [:status :declared])
|
||||
# (validated :ctx)
|
||||
)
|
45
justfile
45
justfile
|
@ -1,45 +0,0 @@
|
|||
default:
|
||||
@just --list
|
||||
|
||||
# build optimized wasm
|
||||
build: && clean-wasm-pack
|
||||
# build with wasm-pack
|
||||
wasm-pack build --target web
|
||||
|
||||
# build dev wasm
|
||||
dev: && clean-wasm-pack
|
||||
wasm-pack build --dev --target web
|
||||
|
||||
# clean up after wasm-pack
|
||||
clean-wasm-pack:
|
||||
# delete cruft from wasm-pack
|
||||
rm pkg/.gitignore pkg/package.json pkg/README.md
|
||||
rm -rf pkg/snippets
|
||||
# fix imports of rudus.js
|
||||
cp pkg/rudus.js pkg/rudus.js.backup
|
||||
echo 'import { io } from "./worker.js"' > pkg/rudus.js
|
||||
cat pkg/rudus.js.backup | tail -n+2>> pkg/rudus.js
|
||||
rm pkg/rudus.js.backup
|
||||
|
||||
from_branch := `git branch --show-current`
|
||||
git_status := `git status -s`
|
||||
|
||||
# publish this branch into release
|
||||
release:
|
||||
echo {{ if git_status == "" {"git status ok"} else {error("please commit changes first")} }}
|
||||
just build
|
||||
-git commit -am "built for release"
|
||||
git checkout release
|
||||
git merge main
|
||||
git push
|
||||
git checkout main
|
||||
|
||||
# serve the pkg directory
|
||||
serve:
|
||||
live-server pkg
|
||||
|
||||
# build the documentation
|
||||
doc:
|
||||
janet janet/doc.janet
|
||||
-rm doc/prelude.md
|
||||
mv prelude.md doc/
|
|
@ -121,7 +121,7 @@ A few thoughts:
|
|||
* Function calls should be different from tuple pattern matching. Tuples are currently (and maybe forever?) allocated on the heap. Function calls should *not* have to pass through the heap. The good news: `Arguments` is already a different AST node type than `Tuple`; we'll want an `ArgumentsPattern` pattern node type that's different from (and thus compiled differently than) `TuplePattern`. They'll be similar--the matching logic is the same, after all--but the arguments will be on the stack already, and won't need to be unpacked in the same way.
|
||||
- One difficulty will be matching against different arities? But actually, we should compile these arities as different functions.
|
||||
- Given splats, can we actually compile functions into different arities? Consider the following:
|
||||
```ludus
|
||||
```
|
||||
fn foo {
|
||||
(x) -> & arity 1
|
||||
(y, z) -> & arity 2
|
||||
|
@ -142,12 +142,12 @@ A few thoughts:
|
|||
* That will get me a lot of the way there. What's left after that which might be challenging?
|
||||
- [x] string interpolation
|
||||
- [x] splats
|
||||
- [x] splatterns
|
||||
- [ ] splatterns
|
||||
- [x] string patterns
|
||||
- [x] partial application
|
||||
- [x] tail calls
|
||||
- [-] stack traces in panics
|
||||
- [-] actually good lexing, parsing, and validation errors. I got some of the way there in the fall, but everything needs to be "good enough."
|
||||
- [ ] tail calls
|
||||
- [ ] stack traces in panics
|
||||
- [ ] actually good lexing, parsing, and validation errors. I got some of the way there in the fall, but everything needs to be "good enough."
|
||||
* After that, we're in integration hell: taking this thing and putting it together for Computer Class 1. Other things that I want (e.g., `test` forms) are for later on.
|
||||
* There's then a whole host of things I'll need to get done for CC2:
|
||||
- some kind of actual parsing strategy (that's good enough for "Dissociated Press"/Markov chains)
|
||||
|
@ -161,7 +161,7 @@ The `loop` compilation is _almost_ the same as a function body. That said, the t
|
|||
|
||||
A few possibilities:
|
||||
* Probably the best option: enforce a new requirement that splat patterns in function clauses *must* be longer than any explicit arity of the function. So, taking the above:
|
||||
```ludus
|
||||
```
|
||||
fn foo {
|
||||
(x) -> & arity 1
|
||||
(y, z) -> & arity 2
|
||||
|
@ -171,7 +171,7 @@ A few possibilities:
|
|||
```
|
||||
This would give you a validation error that splats must be longer than any other arity.
|
||||
Similarly, we could enforce this:
|
||||
```ludus
|
||||
```
|
||||
fn foo {
|
||||
(x) -> & arity 1
|
||||
(x, y) -> & arity 2
|
||||
|
@ -243,41 +243,41 @@ To reiterate the punch list that *I would have needed for Computer Class 1*:
|
|||
* [x] jump instructions need 16 bits of operand
|
||||
- Whew, that took longer than I expected
|
||||
* [x] splatterns
|
||||
- [-] validator should ensure splatterns are the longest patterns in a form
|
||||
* [-] improve validator
|
||||
- [-] Tuples may not be longer than n members
|
||||
- [-] Loops may not have splatterns
|
||||
- [-] Identify others
|
||||
- [ ] validator should ensure splatterns are the longest patterns in a form
|
||||
* [ ] improve validator
|
||||
- [ ] Tuples may not be longer than n members
|
||||
- [ ] Loops may not have splatterns
|
||||
- [ ] Identify others
|
||||
* [x] add guards to loop forms
|
||||
* [x] check loop forms against function calls: do they still work the way we want them to?
|
||||
* [x] tail call elimination
|
||||
* [x] stack traces in panics
|
||||
|
||||
* [-] actually good error messages
|
||||
- [-] parsing
|
||||
- [-] my memory is that validator messages are already good?
|
||||
- [-] panics, esp. no match panics
|
||||
* [-] getting to prelude
|
||||
- [-] `base` should load into Prelude
|
||||
- [-] prelude should run properly
|
||||
- [-] prelude should be loaded into every context
|
||||
* [-] packaging things up
|
||||
- [-] add a `to_json` method for values
|
||||
- [-] teach Rudus to speak our protocols (stdout and turtle graphics)
|
||||
- [-] there should be a Rust function that takes Ludus source and returns valid Ludus status json
|
||||
- [-] compile Rust to WASM
|
||||
- [-] wire Rust-based WASM into JS
|
||||
- [-] FINALLY, test Rudus against Ludus test cases
|
||||
* [ ] actually good error messages
|
||||
- [ ] parsing
|
||||
- [ ] my memory is that validator messages are already good?
|
||||
- [ ] panics, esp. no match panics
|
||||
* [ ] getting to prelude
|
||||
- [ ] `base` should load into Prelude
|
||||
- [ ] prelude should run properly
|
||||
- [ ] prelude should be loaded into every context
|
||||
* [ ] packaging things up
|
||||
- [ ] add a `to_json` method for values
|
||||
- [ ] teach Rudus to speak our protocols (stdout and turtle graphics)
|
||||
- [ ] there should be a Rust function that takes Ludus source and returns valid Ludus status json
|
||||
- [ ] compile Rust to WASM
|
||||
- [ ] wire Rust-based WASM into JS
|
||||
- [ ] FINALLY, test Rudus against Ludus test cases
|
||||
|
||||
So this is the work of the week of June 16, maybe?
|
||||
|
||||
Just trying to get a sense of what needs to happen for CC2:
|
||||
* [x] Actor model (objects, Spacewar!)
|
||||
* [-] Animation hooked into the web frontend (Spacewar!)
|
||||
* [-] Text input (Spacewar!)
|
||||
- [-] Makey makey for alternate input?
|
||||
* [-] Saving and loading data into Ludus (perceptrons, dissociated press)
|
||||
* [-] Finding corpuses for Dissociated Press
|
||||
* [ ] Actor model (objects, Spacewar!)
|
||||
* [ ] Animation hooked into the web frontend (Spacewar!)
|
||||
* [ ] Text input (Spacewar!)
|
||||
- [ ] Makey makey for alternate input?
|
||||
* [ ] Saving and loading data into Ludus (perceptrons, dissociated press)
|
||||
* [ ] Finding corpuses for Dissociated Press
|
||||
|
||||
### Final touches on semantics, or lots of bugs
|
||||
#### 2025-06-19
|
||||
|
@ -309,38 +309,38 @@ So this is my near-term TODO:
|
|||
- [x] `base` should load into Prelude
|
||||
- [x] write a mock prelude with a few key functions from real prelude
|
||||
- [x] a prelude should be loaded into every context
|
||||
- [?] the full prelude should run properly
|
||||
* [x] packaging things up
|
||||
- [x] add a `to_json` method for values
|
||||
- [x] teach Rudus to speak our protocols (stdout and turtle graphics)
|
||||
- [x] there should be a Rust function that takes Ludus source and returns valid Ludus status json
|
||||
- [x] compile Rust to WASM
|
||||
- [x] wire Rust-based WASM into JS
|
||||
- [-] FINALLY, test Rudus against Ludus test cases
|
||||
- [ ] the full prelude should run properly
|
||||
* [ ] packaging things up
|
||||
- [ ] add a `to_json` method for values
|
||||
- [ ] teach Rudus to speak our protocols (stdout and turtle graphics)
|
||||
- [ ] there should be a Rust function that takes Ludus source and returns valid Ludus status json
|
||||
- [ ] compile Rust to WASM
|
||||
- [ ] wire Rust-based WASM into JS
|
||||
- [ ] FINALLY, test Rudus against Ludus test cases
|
||||
|
||||
And then: quality of life improvements:
|
||||
* [-] refactor messes
|
||||
- [x] The compiler should abstract over some of the very titchy bytecode instruction code
|
||||
- [x] Pull apart some gargantuan modules into smaller chunks: e.g., `Op` and `Chunk` should be their own modules
|
||||
- [x] Identify code smells
|
||||
- [x] Fix some of them
|
||||
* [-] improve validator
|
||||
- [-] Tuples may not be longer than n members
|
||||
- [-] Loops may not have splatterns
|
||||
- [-] Identify others
|
||||
- [-] Splats in functions must be the same arity, and greater than any explicit arity
|
||||
* [-] actually good error messages
|
||||
- [-] parsing
|
||||
- [-] my memory is that validator messages are already good?
|
||||
- [-] panics, esp. no match panics
|
||||
* [-] panics should be able to refernce the line number where they fail
|
||||
* [-] that suggests that we need a mapping from bytecodes to AST nodes
|
||||
* [ ] refactor messes
|
||||
- [ ] The compiler should abstract over some of the very titchy bytecode instruction code
|
||||
- [ ] Pull apart some gargantuan modules into smaller chunks: e.g., `Op` and `Chunk` should be their own modules
|
||||
- [ ] Identify code smells
|
||||
- [ ] Fix some of them
|
||||
* [ ] improve validator
|
||||
- [ ] Tuples may not be longer than n members
|
||||
- [ ] Loops may not have splatterns
|
||||
- [ ] Identify others
|
||||
- [ ] Splats in functions must be the same arity, and greater than any explicit arity
|
||||
* [ ] actually good error messages
|
||||
- [ ] parsing
|
||||
- [ ] my memory is that validator messages are already good?
|
||||
- [ ] panics, esp. no match panics
|
||||
* [ ] panics should be able to refernce the line number where they fail
|
||||
* [ ] that suggests that we need a mapping from bytecodes to AST nodes
|
||||
* The way I had been planning on doing this is having a vec that moves in lockstep with bytecode that's just references to ast nodes, which are `'static`, so that shouldn't be too bad. But this is per-chunk, which means we need a reference to that vec in the VM. My sense is that what we want is actually a separate data structure that holds the AST nodes--we'll only need them in the sad path, which can be slow.
|
||||
|
||||
### Bugs discovered while trying to compile prelude
|
||||
#### 2025-06-20
|
||||
Consider the following code:
|
||||
```ludus
|
||||
```
|
||||
fn one {
|
||||
(x as :number) -> {
|
||||
fn two () -> :number
|
||||
|
@ -381,15 +381,15 @@ So here's a short punch list of things to do in that register:
|
|||
* [x] Hook validator back in to both source AND prelude code
|
||||
- [x] Validator should know about the environment for global/prelude function
|
||||
- [x] Run validator on current prelude to fix current known errors
|
||||
* [x] Do what it takes to compile this interpreter into Ludus's JS environment
|
||||
- [x] JSONify Ludus values
|
||||
- [x] Write a function that's source code to JSON result
|
||||
- [x] Expose this to a WASM compiler
|
||||
- [x] Patch this into a JS file
|
||||
- [-] Automate this build process
|
||||
* [-] Start testing against the cases in `ludus-test`
|
||||
* [-] Systematically debug prelude
|
||||
- [-] Bring it in function by function, testing each in turn
|
||||
* [ ] Do what it takes to compile this interpreter into Ludus's JS environment
|
||||
- [ ] JSONify Ludus values
|
||||
- [ ] Write a function that's source code to JSON result
|
||||
- [ ] Expose this to a WASM compiler
|
||||
- [ ] Patch this into a JS file
|
||||
- [ ] Automate this build process
|
||||
* [ ] Start testing against the cases in `ludus-test`
|
||||
* [ ] Systematically debug prelude
|
||||
- [ ] Bring it in function by function, testing each in turn
|
||||
|
||||
***
|
||||
I've started working on systematically going through the Prelude.
|
||||
|
@ -400,7 +400,7 @@ What we need to have happen is that if a function is closing over values _inside
|
|||
I think I need to consult Uncle Bob Nystrom to get a sense of what to do here.
|
||||
***
|
||||
So I found the minimal test case:
|
||||
```ludus
|
||||
```
|
||||
let foo = {
|
||||
let thing = :thing
|
||||
let bar = :bar
|
||||
|
@ -445,7 +445,7 @@ To the best of my ability to tell, `if` has proper stack behaviour.
|
|||
So the question is what's happening in the interaction between the `jump_if_false` instruction and `recur`.
|
||||
|
||||
To wit, the following code works just fine:
|
||||
```ludus
|
||||
```
|
||||
fn not {
|
||||
(false) -> true
|
||||
(nil) -> true
|
||||
|
@ -463,7 +463,7 @@ loop ([1, 2, 3]) with {
|
|||
```
|
||||
|
||||
But the following code does not:
|
||||
```ludus
|
||||
```
|
||||
let test = 2
|
||||
loop ([1, 2, 3]) with {
|
||||
([]) -> false
|
||||
|
@ -486,19 +486,19 @@ I may be surprised, though.
|
|||
|
||||
Currently fixing little bugs in prelude.
|
||||
Here's a list of things that need doing:
|
||||
* [-] Escape characters in strings: \n, \t, and \{, \}.
|
||||
* [-] `doc!` needs to print the patterns of a function.
|
||||
* [-] I need to return to the question of whether/how strings are ordered; do we use `at`, or do we want `char_at`? etc.
|
||||
* [-] Original implementation of `butlast` is breaking stack discipline; I don't know why. It ends up returning from evaluating one of the arguments straight into a `load` instruction. Something about tail calls and ternary synthetic expressions and base functions. (For now, I can call `slice` instead of `base :slice` and it works.)
|
||||
* [ ] Escape characters in strings: \n, \t, and \{, \}.
|
||||
* [ ] `doc!` needs to print the patterns of a function.
|
||||
* [ ] I need to return to the question of whether/how strings are ordered; do we use `at`, or do we want `char_at`? etc.
|
||||
* [ ] Original implementation of `butlast` is breaking stack discipline; I don't know why. It ends up returning from evaluating one of the arguments straight into a `load` instruction. Something about tail calls and ternary synthetic expressions and base functions. (For now, I can call `slice` instead of `base :slice` and it works.)
|
||||
- Original version of `update` also had this same problem with `assoc`; fixed it by calling the Ludus, rather than Rust, function.
|
||||
- I need this fixed for optimization reasons.
|
||||
- I _think_ I just fixed this by fixing tail position tracking in collections
|
||||
- [-] test this
|
||||
- [ ] test this
|
||||
- I did not fix it.
|
||||
* [x] Dict patterns are giving me stack discipline grief. Why is stack discipline so hard?
|
||||
* [x] This is in the service of getting turtle graphics working
|
||||
* [ ] This is in the service of getting turtle graphics working
|
||||
* Other forms in the language need help:
|
||||
* xx] repeat needs its stack discipline updated, it currently crashes the compiler
|
||||
* [ ] repeat needs its stack discipline updated, it currently crashes the compiler
|
||||
|
||||
### More closure problems
|
||||
#### 2025-06-23
|
||||
|
@ -522,7 +522,11 @@ SOLUTION: test to see if the function has been forward-declared, and if it has,
|
|||
NEW PROBLEM: a lot of instructions in the VM don't properly offset from the call frame's stack base, which leads to weirdness when doing things inside function calls.
|
||||
|
||||
NEW SOLUTION: create a function that does the offset properly, and replace everywhere we directly access the stack.
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< Updated upstream
|
||||
This is the thing I am about to do
|
||||
||||||| Stash base
|
||||
This is the thing I am about to do.
|
||||
|
||||
### I think the interpreter, uh, works?
|
||||
#### 2025-06-24
|
||||
|
@ -738,13 +742,13 @@ println!("line {line_no}: {}", lines[line_no - 1]);
|
|||
#### 2025-06-25
|
||||
* Web workers
|
||||
* My javascript wrapper needs to execute WASM in its own thread (ugh)
|
||||
- [-] is this a thing that can be done easily in a platform-independent way (node vs. bun vs. browser)?
|
||||
- [ ] is this a thing that can be done easily in a platform-independent way (node vs. bun vs. browser)?
|
||||
* Top priorities:
|
||||
- [-] Get a node package out
|
||||
- [-] Stand up actors + threads, etc.
|
||||
- [-] How to model keyboard input from p5?
|
||||
* [-] Model after the p5 keyboard input API
|
||||
* [-] ludus keyboard API: `key_is_down(), key_pressed(), key_released()`, key code values (use a dict)
|
||||
- [ ] Get a node package out
|
||||
- [ ] Stand up actors + threads, etc.
|
||||
- [ ] How to model keyboard input from p5?
|
||||
* [ ] Model after the p5 keyboard input API
|
||||
* [ ] ludus keyboard API: `key_is_down(), key_pressed(), key_released()`, key code values (use a dict)
|
||||
- Assets:
|
||||
* We don't (for now) need to worry about serialization formats, since we're not doing perceptrons
|
||||
* We do need to read from URLs, which need in a *.ludus.dev.
|
||||
|
@ -770,485 +774,4 @@ See https://github.com/vitejs/vite/discussions/12826.
|
|||
|
||||
Web, in some ways, is even more straightforward.
|
||||
It produces an ESM that just works in the browser.
|
||||
Put the rest of my thinking up in an issue on alea.
|
||||
|
||||
### Actor/Model
|
||||
#### 2025-06-26
|
||||
Okay, even though we're not fully hooked up with wasm yet, I've started working on the actor model.
|
||||
I believe I have things broadly right.
|
||||
The thing that I'm thinking about right now is how to connect ludus to the world and to processes.
|
||||
It's not clear how to expose that plumbing.
|
||||
|
||||
The most obvious way to do this would be to make all the process stuff special forms.
|
||||
|
||||
But first, before I get there, this is what Elixir does.
|
||||
|
||||
* `receive` is a special form. It is strictly equivalent to `match`, but with the caveats that (a) if there are no messages to receive, the process yields, and (b) if there's no match for the first message, it matches against the second, and so on (keeping messages in the mailbox), and (c) if no messages match, the process yields as well.
|
||||
|
||||
Everything else is functions:
|
||||
* `spawn/1` takes a function to execute
|
||||
* `send/3` takes a pid and a message (the third argument is for options, which we won't have in Ludus), returning a pid
|
||||
* `self/0` returns the current processes' pid
|
||||
* `exit/2` takes a pid and a message, and kills the process
|
||||
* `sleep/1` takes a duration in milliseconds and sleeps the process for a time. This isn't strictly speaking necessary, but it is a nice-to-have.
|
||||
|
||||
In Ludus, `receive` will need to be a special form.
|
||||
We could make `self` a reserved word, emitting the instruction to get the current pid.
|
||||
`spawn` I like as a special form: whatever the expression that comes after spawn is just deferred into the new process.
|
||||
So in place of Elixir's `spawn(fn -> :something end)` in Ludus, we'd have `spawn :something`.
|
||||
`send`, `exit`, and `sleep` are a little more interesting (difficult).
|
||||
|
||||
Each could be like `and` and `or`: special forms that _look_ like functions, but really aren't--implemented in the compiler.
|
||||
|
||||
Alternately, I could attempt to find a way to expose the vm to base.
|
||||
That seems, frankly, rather more daunting in Rust.
|
||||
|
||||
But we could also just build out syntax?
|
||||
For example, I had proposed the following desugaring:
|
||||
`foo ::bar (baz)` -> `send (foo, (:bar, baz))`
|
||||
I don't mind the sugar, but it feels like it's actually really important conceptually for Ludus that everything really is functions.
|
||||
(Then again, the sugar feels weird, because `foo` is just a name bound to a keyword, so it's possible to do: `:foo ::bar (baz)`, which makes sense and is weird.).
|
||||
|
||||
The only disadvantage with making `send` a special form is that you can't pass it as a higher-order function.
|
||||
`and` and `or` must be special forms, and don't make sense as higher-order functions because they have a different evaluation order than regular functions.
|
||||
But that's not true of all the things here.
|
||||
|
||||
So how do we connect Ludus function calls to handles in the rust interpreter?
|
||||
In place of base functions, we need something like messages to the process, that aren't mailbox processes but something else.
|
||||
So, like, a special sort of Ludus value, only available in base, that represents the VM.
|
||||
Call it `base :process`.
|
||||
We call it like a function, but the VM instead responds to the various prompts.
|
||||
Thus we can use it to communicate with the process.
|
||||
`receive` will be tricky.
|
||||
But everything else? Seems pretty straightforward.
|
||||
|
||||
|
||||
#### Some time later...
|
||||
I've implemented what I decribe above. It works! I'm low-key astonished.
|
||||
It perfectly handles an infinitely recurring process! What the fuck.
|
||||
Anyway, things left to do:
|
||||
* [x] `receive` forms are the big one: they require threading through the whole interpreter
|
||||
* [x] implement the missing process functions at the end of prelude
|
||||
* [-] research how Elixir/Erlang's abstractions over processes work (I'm especially interested in how to make synchronous-looking calls); take a look especially at https://medium.com/qixxit-development/build-your-own-genserver-in-49-lines-of-code-1a9db07b6f13
|
||||
* [-] write some examples just using these simple tools (not even GenServer, etc.) to see how they work, and to start building curriculum
|
||||
* [-] develop a design for how to deal with asynchronous io with js
|
||||
|
||||
```ludus
|
||||
fn agent/get (pid) -> {
|
||||
send (pid, (:get, self ()))
|
||||
receive {
|
||||
(:response, value) -> value
|
||||
}
|
||||
}
|
||||
|
||||
fn agent/store (pid, x) -> {
|
||||
send (pid, (:store, x))
|
||||
:ok
|
||||
}
|
||||
|
||||
fn agent/update (pix, f) -> {
|
||||
send (pid, (:update, f))
|
||||
}
|
||||
|
||||
fn agent (state) -> receive {
|
||||
(:get, pid) -> {
|
||||
send (pid, (:response, state))
|
||||
agent (state)
|
||||
}
|
||||
(:update, pid, f) -> {
|
||||
agent (f (state))
|
||||
}
|
||||
(:store, pid, x) -> {
|
||||
agent (x)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Two things that pop out to me:
|
||||
* The way this works is just to yield immediately. This actually makes a lot of sense. If we put them next to one another, there's no risk that there'll be backlogged `(:response, x)` messages in the mbx, right? But that makes me a little queasy.
|
||||
* The way `gen_server` works is pretty deep inversion of control; you effectively write callbacks for the `gen_server` to call. I'm not sure that's how we want to do things in Ludus; it's a handy pattern, and easy. But it's not simple. But also worth investigating. In any event, it's the foundation of all the other process patterns Elixir has developed. I need an intuiation around it.
|
||||
|
||||
### Rethinking reception
|
||||
#### 2025-06-27
|
||||
So one thing that's stuck with me is that in Elixir, ~`receive` isn't a special form: it's a function that takes a block~ (**EDIT**: it is indeed a special form in Elixir, and it has to be on in Ludus.).
|
||||
It may be a macro, but it's still mostly normalish, and doesn't invovle compiler shenanigans.
|
||||
|
||||
So, this is what I want to write:
|
||||
|
||||
```ludus
|
||||
fn receiver () -> receive {
|
||||
:foo -> :bar
|
||||
:bar -> :baz
|
||||
:baz -> :quux
|
||||
}
|
||||
```
|
||||
There's a way to model this without the magic of receive.
|
||||
Imagine instead a function that just receives a message and matches against it:
|
||||
```ludus
|
||||
fn receive_msg (msg) -> match msg with {
|
||||
:foo -> :bar
|
||||
:bar -> :baz
|
||||
:baz -> :quux
|
||||
}
|
||||
```
|
||||
But if there's no matching message clause, we get a panic.
|
||||
And panics stop the world.
|
||||
Meanwhile, we need to know whether the message matched.
|
||||
So this desugaring goes a little further:
|
||||
```ludus
|
||||
fn receive_msg (msg) -> match msg with {
|
||||
:foo -> :bar
|
||||
:bar -> :baz
|
||||
:baz -> :quux
|
||||
_ -> :does_not_understand
|
||||
}
|
||||
```
|
||||
This way we avoid a panic when there's no matching message.
|
||||
There's an easy wrapping function which looks like this:
|
||||
```ludus
|
||||
fn receive (receiver) -> {
|
||||
let my_msgs = msgs ()
|
||||
loop (my_msgs, 0) with {
|
||||
([], _) -> yield! ()
|
||||
(xs, i) -> match receiver(first (xs)) with {
|
||||
:does_not_understand -> recur (rest (xs), inc (i))
|
||||
x -> {
|
||||
flush_n! (i)
|
||||
x
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
receive (receive_msg)
|
||||
```
|
||||
There's a thing I both like and don't like about this.
|
||||
The fact that we use a magic keyword, `:does_not_understand`, means it's actually easy to hook into the behaviour of not understanding.
|
||||
I don't know if we should panic on a process receiving a message it doesn't understand.
|
||||
Maybe we do that for now?
|
||||
Crash early and often; thanks Erlang.
|
||||
And so we do have to worry about the last clause.
|
||||
|
||||
At an implementation level, it's worth noting that we're not optimizing for scanning through the messages--we'll scan through any messages we don't understand every time we call `receive`.
|
||||
That's probably fine!
|
||||
|
||||
So now the thing, without sugar, is just:
|
||||
```ludus
|
||||
fn agent (x) -> receive (fn (msg) {
|
||||
(:get, pid) -> {
|
||||
send (pid, (:response, x))
|
||||
agent (x)
|
||||
}
|
||||
(:set, y) -> agent(y)
|
||||
(:update, f) -> agent (f (x))
|
||||
_ -> :does_not_understand
|
||||
})
|
||||
```
|
||||
So I don't need any sugar to make `receive` work?
|
||||
And it doesn't even need to hook into the vm?
|
||||
What?
|
||||
|
||||
#### some time later
|
||||
It turns out I do.
|
||||
The problem is that the `flush_i` instruction never gets called from Ludus in a thing like an agent, because of the recursive call.
|
||||
So the flushing would need to happen in the receiver.
|
||||
|
||||
|
||||
A few things that are wrong right now:
|
||||
* [-] `loop`/`recur` is still giving me a very headache. It breaks stack discipline to avoid packing tuples into a heap-allocated vec. There may be a way to fix this in the current compiler scheme around how I do and don't handle arguments--make recur stupider and don't bother loading anything. A different solution would be to desugar loop into an anonymous function call. A final solution would be just to use a heap-allocated tuple.
|
||||
|
||||
Minimal failing case:
|
||||
```ludus
|
||||
match (nil) with {
|
||||
(x) -> match x with {
|
||||
(y) -> recur (y)
|
||||
}
|
||||
}
|
||||
```
|
||||
* [x] The amount of sugar needed to get to `receive` without making a special form is too great.
|
||||
|
||||
In particular, function arities need to be changed, flushes need to be inserted, anonymous lambdas need to be created (which can't be multi-clause), etc.
|
||||
|
||||
_This is now implemented._
|
||||
|
||||
~* [-] There was another bug that I was going to write down and fix, but I forgot what it was. Something with processes.~
|
||||
* [-] I remembered: I got some weird behaviour when `MAX_REDUCTIONS` was set to 100; I've increased it to 1000, but now need to test what happens when we yield because of reductions.
|
||||
|
||||
* [-] Also: the `butlast` bug is still outstanding: `base :slice` causes a panic in that function, but not the call to the Ludus function. Still have to investigate that one.
|
||||
|
||||
* [x] In testing this, it's looking like `match` is misbehaving; none of the matches that *should* happen in my fully sugarless `receive` testing are matching how they ought.
|
||||
- That is not what was happening. The mailbox
|
||||
|
||||
I haven't got to a minimal case, but here's what's not working:
|
||||
```ludus
|
||||
fn receive (receiver) -> {
|
||||
fn looper {
|
||||
([], _) -> yield! ()
|
||||
(xs, i) -> {
|
||||
print!("looping through messages:", xs)
|
||||
match receiver (first (xs), i) with {
|
||||
:does_not_understand -> looper (rest (xs), inc (i))
|
||||
x -> x
|
||||
}}
|
||||
}
|
||||
print! ("receiving in", self (), "with messages", msgs())
|
||||
looper (msgs (), 0)
|
||||
}
|
||||
|
||||
fn agent (x) -> receive (fn (msg, i) -> {
|
||||
print!("received msg in agent: ", msg)
|
||||
match msg with {
|
||||
(:get, pid) -> {
|
||||
flush_i! (i)
|
||||
print!("getted from {pid}")
|
||||
send (pid, (:response, x))
|
||||
agent (x)
|
||||
}
|
||||
(:set, y) -> {flush_i!(i); print!("setted! {y}"); agent (y)}
|
||||
(:update, f) -> {flush_i!(i);print!("updated: {f}"); agent (f (x))}
|
||||
y -> {print!("no agent reception match!!!! {y}");:does_not_understand}
|
||||
}
|
||||
})
|
||||
|
||||
fn agent/get (pid) -> {
|
||||
send (pid, (:get, self ()))
|
||||
yield! ()
|
||||
receive (fn (msg, i) -> match msg with {
|
||||
(:response, x) -> {flush_i! (i); x}
|
||||
})
|
||||
}
|
||||
|
||||
fn agent/set (pid, val) -> send (pid, (:set, val))
|
||||
|
||||
fn agent/update (pid, f) -> send (pid, (:update, f))
|
||||
|
||||
let counter = spawn! (fn () -> agent (0))
|
||||
agent/set (counter, 12)
|
||||
agent/update (counter, inc)
|
||||
agent/update (counter, mult(_, 3))
|
||||
agent/get (counter)
|
||||
```
|
||||
|
||||
_I haven't been able to recreate this, and the code above is subtle enough that the Ludus may be behaving as expected; the following works as expected:_
|
||||
|
||||
```ludus
|
||||
fn receive (receiver) -> {
|
||||
print! ("receiving in", self (), "with msgs", msgs())
|
||||
if empty? (msgs ())
|
||||
then {yield! (); receive (receiver)}
|
||||
else do msgs () > first > receiver
|
||||
}
|
||||
|
||||
fn foo? (val) -> receive (fn (msg) -> match report!("scrutinee is", msg) with {
|
||||
(:report) -> {
|
||||
print! ("LUDUS SAYS ==> value is {val}")
|
||||
flush! ()
|
||||
foo? (val)
|
||||
}
|
||||
(:set, x) -> {
|
||||
print! ("LUDUS SAYS ==> foo! was {val}, now is {x}")
|
||||
flush! ()
|
||||
foo? (x)
|
||||
}
|
||||
(:get, pid) -> {
|
||||
print! ("LUDUS SAYS ==> value is {val}")
|
||||
send (pid, (:response, val))
|
||||
flush! ()
|
||||
foo? (val)
|
||||
}
|
||||
x -> print! ("LUDUS SAYS ==> no match, got {x}")
|
||||
})
|
||||
|
||||
let foo = spawn! (fn () -> foo? (42))
|
||||
print! (foo)
|
||||
send (foo, (:set, 23))
|
||||
yield! ()
|
||||
send (foo, (:get, self ()))
|
||||
yield! ()
|
||||
fn id (x) -> x
|
||||
receive(id)
|
||||
```
|
||||
|
||||
#### some time later
|
||||
I've started work on `receive`.
|
||||
I've got all the stuff wired up, and it seems to all work (and was pretty straightforward!).
|
||||
EXCEPT: I've got a difficult off-by-one error.
|
||||
The problem is being in a receive in a tight loop/tail call, where the ip doesn't advance past the tail call back to the top of the function.
|
||||
Jumping back to the beginning of the loop advances the message counter by one.
|
||||
Basically, after the first time we've matched, we keep skipping item 0.
|
||||
So: what I need to do is to figure out the right order of operations for.
|
||||
This is just stepwise logic, and some titchy state management.
|
||||
|
||||
So the thing that's worth noting is that entering the receive afresh with the whole message queue and entering it with the next message in the queue are _different_ behaviours. This may involve mucking with the instruction pointer on a yield.
|
||||
This is subtle but will give me the feeling of "oh, why didn't I see that immediately" as soon as I get it.
|
||||
|
||||
### Step-by-step
|
||||
#### 2025-06-28
|
||||
Here's some pseudobytecode to get us to where we need to be:
|
||||
|
||||
010 reset the message counter
|
||||
020 load current message
|
||||
025 if no more messages, jump to 010 THEN yield
|
||||
030 test the message against a pattern
|
||||
040 if no match jump to 090
|
||||
050 reset message counter
|
||||
060 delete current message
|
||||
070 execute body
|
||||
# this may be the last instruction executed
|
||||
# recursive tail calls will jump to 010
|
||||
080 jump to 100
|
||||
085 increase the message counter
|
||||
090 jump to 025 (not really in bytecode; this will be unrolled)
|
||||
100 receive end
|
||||
|
||||
#### a short time later
|
||||
Well, that worked! The real issue was the jump back if we're out of messages.
|
||||
|
||||
That leaves the following list:
|
||||
* [a] research how Elixir/Erlang's abstractions over processes work (I'm especially interested in how to make synchronous-looking calls); take a look especially at https://medium.com/qixxit-development/build-your-own-genserver-in-49-lines-of-code-1a9db07b6f13
|
||||
* [ ] write some examples just using these simple tools (not even GenServer, etc.) to see how they work, and to start building curriculum
|
||||
* [a] develop a design for how to deal with asynchronous io with js
|
||||
* [a] I got some weird behaviour when `MAX_REDUCTIONS` was set to 100; I've increased it to 1000, but now need to test what happens when we yield because of reductions.
|
||||
* [a] Also: the `butlast` bug is still outstanding: `base :slice` causes a panic in that function, but not the call to the Ludus function. Still have to investigate that one.
|
||||
- Original version of `update` also had this same problem with `assoc`; fixed it by calling the Ludus, rather than Rust, function.
|
||||
* [a] `loop`/`recur` is still giving me a very headache. It breaks stack discipline to avoid packing tuples into a heap-allocated vec. There may be a way to fix this in the current compiler scheme around how I do and don't handle arguments--make recur stupider and don't bother loading anything. A different solution would be to desugar loop into an anonymous function call. A final solution would be just to use a heap-allocated tuple.
|
||||
* My javascript wrapper needs to execute WASM in its own thread (ugh)
|
||||
- [x] is this a thing that can be done easily in a platform-independent way (node vs. bun vs. browser)?
|
||||
- No, no it is not. I will need to build a separate node version for using at the command line (and, like, for testing with our test harness.)
|
||||
* Top priorities:
|
||||
- [-] Get a node package out
|
||||
- [x] Stand up actors + threads, etc.
|
||||
- [ ] How to model keyboard input from p5?
|
||||
* [ ] Model after the p5 keyboard input API
|
||||
* [ ] ludus keyboard API: `key_down?(), key_pressed?(), key_released?()`, key code values (use a dict)
|
||||
* [a] Escape characters in strings: \n, \t, and \{, \}.
|
||||
* [a] `doc!` needs to print the patterns of a function.
|
||||
* [a] I need to return to the question of whether/how strings are ordered; do we use `at`, or do we want `char_at`? etc.
|
||||
- MNL and I decided: yes, stings are indexable
|
||||
- [ ] implement `slice` and `at` and others for strings
|
||||
* [x] Automate this build process
|
||||
* [ ] Start testing against the cases in `ludus-test`
|
||||
* [ ] Systematically debug prelude
|
||||
* [ ] Bring it in function by function, testing each in turn
|
||||
* [x] Animation hooked into the web frontend (Spacewar!)
|
||||
* [ ] Text input (Spacewar!)
|
||||
* [ ] ~Makey makey for alternate input?~
|
||||
* [x] Saving and loading data into Ludus (perceptrons, dissociated press)
|
||||
* [ ] Finding corpuses for Dissociated Press
|
||||
* [a] improve validator
|
||||
- [a] Tuples may not be longer than n members
|
||||
- [a] Loops may not have splatterns
|
||||
- [ ] Identify others
|
||||
- [a] Splats in functions must be the same arity, and greater than any explicit arity
|
||||
* [a] actually good error messages
|
||||
- [a] parsing
|
||||
- [ ] my memory is that validator messages are already good?
|
||||
- [a] panics, esp. no match panics
|
||||
* [ ] panics should be able to refernce the line number where they fail
|
||||
* [ ] that suggests that we need a mapping from bytecodes to AST nodes
|
||||
* The way I had been planning on doing this is having a vec that moves in lockstep with bytecode that's just references to ast nodes, which are `'static`, so that shouldn't be too bad. But this is per-chunk, which means we need a reference to that vec in the VM. My sense is that what we want is actually a separate data structure that holds the AST nodes--we'll only need them in the sad path, which can be slow.
|
||||
|
||||
### Next steps in integration hell
|
||||
#### 2025-06-29
|
||||
* [x] improve build process for rudus+wasm_pack
|
||||
- [x] delete generated .gitignore
|
||||
- [x] edit first line of rudus.js to import the local `ludus.js`
|
||||
- On this, made a justfile, but I needed to like actually try the build and figure out what happens. I got carried away touching the js. Too many things at once.
|
||||
* [x] design & implement asynchronous i/o+runtime
|
||||
- [x] use `box`es for i/o: they can be reified in rust: making actors available is rather more complex (i.e. require message passing between the ludus and rust)
|
||||
* We also then don't have to have prelude run in the vm; that's good
|
||||
* We... maybe or maybe don't need processes in prelude, since we need to read and write from the boxes; we might be able to do that with closures and functions that call `spawn!` themselves
|
||||
- [x] start with ludus->rust->js pipeline
|
||||
* [x] console
|
||||
* [ ] turtle graphics
|
||||
* [x] completion
|
||||
- [ ] then js->rust->ludus
|
||||
* [x] kill
|
||||
* [x] text input
|
||||
* [ ] keypresses
|
||||
- [x] then ludus->rust->js->rust->ludus
|
||||
* [x] slurp
|
||||
- For the above, I've started hammering out a situation. I ought to have followed my instinct here: do a little at a time. I ended up doing all the things in one place all at once.
|
||||
- What I've done is work on a bespoke `to_json` method for values; and using serde deserialization to read a string delivered from js. I think this is easier and more straightforward than using `wasm_bindgen`. Or easier; I have no idea what the plumbing looks like.
|
||||
- Just to catch myself up, some additional decisions & thoughts:
|
||||
* No need to send a run event: we'll just start things with with a call to `run`, which we expose to JS.
|
||||
* One thing I hadn't quite grokked before is that we need to have a way of running the i/o events. Perhaps the simplest way to do this is to just to do it every so often, regardless of how long the ludus event loop is taking. That way even if things are getting weird in the VM, i/o still happens regularly.
|
||||
* The return to a `slurp` call is interesting.
|
||||
* I think the thing to do is to write to a slurp buffer/box as well.
|
||||
|
||||
### Finishing integration?
|
||||
#### 2025-07-01
|
||||
Happy Canada day!
|
||||
|
||||
After a really rough evening, I seem to have the actor model not only working in Ludus, but reasonably debugged in Rust.
|
||||
We've got one bug to address in Firefox before I continue:
|
||||
* [x] the event loop isn't returning once something is done, which makes no sense
|
||||
- What seems to be happening is that the javascript behaviour is subtly different
|
||||
- Current situation is that synchronous scripts work just fine
|
||||
- But async scripts work ONCE, and then not again
|
||||
- In FF, `do_io` doesn't return after `complete_main` in the `world` loop the second time.
|
||||
- Which is to say, that last call to `io` isn't completing.
|
||||
- Do I hack around this or do I try to find the source of the problem?
|
||||
|
||||
After that:
|
||||
* [ ] implement other verbs beside `console`:
|
||||
- [x] `command`
|
||||
- [x] `input`
|
||||
* [x] js->rust->ludus buffer (in Rust code)
|
||||
* [x] ludus abstractions around this buffer (in Ludus code)
|
||||
- [x] `fetch`--request & response
|
||||
* [x] request: ludus->rust->js->net
|
||||
* [x] response: js->rust->ludus
|
||||
- [ ] `keyboard`
|
||||
* [ ] still working on how to represent this
|
||||
* [x] hook this up to `web.ludus.dev`
|
||||
* [x] do some integration testing
|
||||
- [x] do synchronous programs still work?
|
||||
- [x] animations?
|
||||
- [x] read inputs?
|
||||
- [x] load url text?
|
||||
|
||||
### Following links
|
||||
#### 2025-07-05
|
||||
Doing some last minute design work on linking processes.
|
||||
Here's the erlang way:
|
||||
* `link` is `link/1`: we always link _from a process_.
|
||||
* links are always bidirectional
|
||||
* [ ] update the link API to reflect this
|
||||
* There's the concept of an exit signal
|
||||
- erlang has three ways of doing this (ish): `:normal` (returning), `:kill` (panic), or a reason (which is a value).
|
||||
- you can "trap" an exit signal that's `:normal` or a reason, but not if it's `:kill`, which will bring down the monitoring process
|
||||
* I think in ludus we want...
|
||||
|
||||
Well? What do we want?
|
||||
|
||||
Erlang and Elixir have designs that are meant for massively concurrent processes.
|
||||
In addition, I've been thinking about how the willy-nilly, anything goes, messages getting passed around all kinds of places, actually cuts against the spirit of Ludus.
|
||||
What sort of process management, abstraction, modelling, and mental-modelling do we want in ludus?
|
||||
My sense is that, actually, I have to build the things we're going to build before I have a sense of what needs doing.
|
||||
`link` may not even be a thing we need?
|
||||
|
||||
Anyway, here's the tl;dr for basic process linking:
|
||||
* `link/1` should establish a bidirectional link, bringing one process down if the other exits for any reason, causing a panic if the linked process exits unexpectedly, even on a normal return
|
||||
* `monitor/1` means we can monitor exit messages, these will be `(:exit, pid, (:ok, value))` in the case of successful return, or `(:exit, pid, (:err, msg))` in the case of a panic. These are sent as normal messages to the monitoring process.
|
||||
|
||||
Erlang/Elixir has a lot more, but that's basically it for Ludus for now.
|
||||
I don't think we need more than the binary: `:ok`/`:err`, or return/panic as how processes exit.
|
||||
`monitor/1` makes it easy to write an `await`.
|
||||
We would need a way of killing processes remotely if indeed we wanted to write robust distributed applications.
|
||||
But I'm less interested in that than in the cognitive modelling here.
|
||||
|
||||
As I'm thinking about this, I actually want to roll back `spawn` as a special form.
|
||||
I think it's actually a good cognitive shift to understand that functions are reified, deferred computations.
|
||||
And that processes take this a long way.
|
||||
We can revisit the idea of a special spawn form later.
|
||||
|
||||
=> done
|
||||
|
||||
And now, we build capacity for building projects.
|
||||
|
||||
* [ ] allow for multiple turtles
|
||||
- [x] rework p5 function in `ludus.js` to properly parse commands targeted at different turtles
|
||||
- [x] write ludus code to spawn & manipulate turtle actors
|
||||
- [ ] write linking logic so we can `await` turtles
|
||||
* [ ] do some sample multiturtle sketches
|
||||
And
|
1594
package-lock.json
generated
Normal file
1594
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
package.json
Normal file
20
package.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "@ludus/rudus",
|
||||
"version": "0.1.3",
|
||||
"description": "A Rust-based Ludus bytecode interpreter.",
|
||||
"type": "module",
|
||||
"main": "pkg/ludus.js",
|
||||
"directories": {},
|
||||
"keywords": [],
|
||||
"author": "Scott Richmond",
|
||||
"license": "GPL-3.0",
|
||||
"files": [
|
||||
"pkg/rudus.js",
|
||||
"pkg/ludus.js",
|
||||
"pkg/rudus_bg.wasm",
|
||||
"pkg/rudus_bg.wasm.d.ts",
|
||||
"pkg/rudus.d.ts"
|
||||
],
|
||||
"devDependencies": {
|
||||
}
|
||||
}
|
3
pkg/README.md
Normal file
3
pkg/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# rudus
|
||||
|
||||
A Rust implementation of Ludus.
|
|
@ -6,7 +6,13 @@
|
|||
|
||||
</head>
|
||||
<body>
|
||||
<script src="./ludus.js" type="module"></script>
|
||||
<script type="module">
|
||||
import {run} from "./ludus.js";
|
||||
|
||||
window.ludus = run;
|
||||
|
||||
console.log(run(":foobar"));
|
||||
</script>
|
||||
<p>
|
||||
Open the console. All the action's in there.
|
||||
</p>
|
||||
|
|
14
pkg/keys.js
14
pkg/keys.js
|
@ -1,14 +0,0 @@
|
|||
const codes = {
|
||||
"Tab": "tab",
|
||||
"Enter": "enter",
|
||||
"Escape": "esc",
|
||||
"ArrowLeft": "left",
|
||||
"ArrowRight": "right",
|
||||
"ArrowUp": "up",
|
||||
"ArrowDown": "down",
|
||||
"Delete": "delete",
|
||||
}
|
||||
|
||||
export function get_code (key) {
|
||||
return codes[key] ?? key
|
||||
}
|
502
pkg/ludus.js
502
pkg/ludus.js
|
@ -1,200 +1,378 @@
|
|||
import {p5} from "./p5.js"
|
||||
import {svg as svg_2} from "./svg.js"
|
||||
import {get_code} from "./keys.js"
|
||||
import init, {ludus} from "./rudus.js";
|
||||
|
||||
if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, flush_commands, commands, result, flush_result, input, is_running, key_down, key_up, is_starting_up, p5, svg}
|
||||
await init();
|
||||
|
||||
const worker_url = new URL("worker.js", import.meta.url)
|
||||
const worker = new Worker(worker_url, {type: "module"})
|
||||
let res = null
|
||||
|
||||
let outbox = []
|
||||
let ludus_console = ""
|
||||
let ludus_commands = []
|
||||
let ludus_result = null
|
||||
let code = null
|
||||
let running = false
|
||||
let ready = false
|
||||
let io_interval_id = null
|
||||
let keys_down = new Set();
|
||||
|
||||
worker.onmessage = handle_messages
|
||||
export function run (source) {
|
||||
code = source
|
||||
const output = ludus(source)
|
||||
res = JSON.parse(output)
|
||||
return res
|
||||
}
|
||||
|
||||
async function handle_messages (e) {
|
||||
let msgs
|
||||
try {
|
||||
msgs = JSON.parse(e.data)
|
||||
} catch {
|
||||
// console.log(e.data)
|
||||
throw Error("Main: bad json from Ludus")
|
||||
}
|
||||
for (const msg of msgs) {
|
||||
switch (msg.verb) {
|
||||
case "Complete": {
|
||||
// console.log("Main: ludus completed with => ", msg.data)
|
||||
ludus_result = msg.data
|
||||
running = false
|
||||
ready = false
|
||||
outbox = []
|
||||
break
|
||||
}
|
||||
case "Error": {
|
||||
// console.log("Main: ludus errored with => ", msg.data)
|
||||
ludus_result = msg.data
|
||||
running = false
|
||||
ready = false
|
||||
outbox = []
|
||||
break
|
||||
}
|
||||
// TODO: do more than report these
|
||||
case "Console": {
|
||||
let new_lines = msg.data.join("\n");
|
||||
ludus_console = ludus_console + new_lines
|
||||
// console.log("Main: ludus says => ", new_lines)
|
||||
break
|
||||
}
|
||||
case "Commands": {
|
||||
// console.log("Main: ludus commands => ", msg.data)
|
||||
for (const command of msg.data) {
|
||||
// commands will arrive asynchronously; ensure correct ordering
|
||||
ludus_commands[command[1]] = command
|
||||
}
|
||||
break
|
||||
}
|
||||
case "Fetch": {
|
||||
// console.log("Main: ludus requests => ", msg.data)
|
||||
const res = await fetch(msg.data, {mode: "cors"})
|
||||
const text = await res.text()
|
||||
// console.log("Main: js responds => ", text)
|
||||
outbox.push({verb: "Fetch", data: [msg.data, res.status, text]})
|
||||
}
|
||||
case "Ready": {
|
||||
// console.log("Main: ludus is ready")
|
||||
ready = true
|
||||
}
|
||||
export function stdout () {
|
||||
if (!res) return ""
|
||||
return res.io.stdout.data
|
||||
}
|
||||
|
||||
export function turtle_commands () {
|
||||
if (!res) return []
|
||||
return res.io.turtle.data
|
||||
}
|
||||
|
||||
export function result () {
|
||||
return res
|
||||
}
|
||||
|
||||
const turtle_init = {
|
||||
position: [0, 0],
|
||||
heading: 0,
|
||||
pendown: true,
|
||||
pencolor: "white",
|
||||
penwidth: 1,
|
||||
visible: true
|
||||
}
|
||||
|
||||
const colors = {
|
||||
black: [0, 0, 0, 255],
|
||||
silver: [192, 192, 192, 255],
|
||||
gray: [128, 128, 128, 255],
|
||||
white: [255, 255, 255, 255],
|
||||
maroon: [128, 0, 0, 255],
|
||||
red: [255, 0, 0, 255],
|
||||
purple: [128, 0, 128, 255],
|
||||
fuchsia: [255, 0, 255, 255],
|
||||
green: [0, 128, 0, 255],
|
||||
lime: [0, 255, 0, 255],
|
||||
olive: [128, 128, 0, 255],
|
||||
yellow: [255, 255, 0, 255],
|
||||
navy: [0, 0, 128, 255],
|
||||
blue: [0, 0, 255, 255],
|
||||
teal: [0, 128, 128, 255],
|
||||
aqua: [0, 255, 255, 255],
|
||||
}
|
||||
|
||||
function resolve_color (color) {
|
||||
if (typeof color === 'string') return colors[color]
|
||||
if (typeof color === 'number') return [color, color, color, 255]
|
||||
if (Array.isArray(color)) return color
|
||||
return [0, 0, 0, 255] // default to black?
|
||||
}
|
||||
|
||||
let background_color = "black"
|
||||
|
||||
function add (v1, v2) {
|
||||
const [x1, y1] = v1
|
||||
const [x2, y2] = v2
|
||||
return [x1 + x2, y1 + y2]
|
||||
}
|
||||
|
||||
function mult (vector, scalar) {
|
||||
const [x, y] = vector
|
||||
return [x * scalar, y * scalar]
|
||||
}
|
||||
|
||||
function unit_of (heading) {
|
||||
const turns = -heading + 0.25
|
||||
const radians = turn_to_rad(turns)
|
||||
return [Math.cos(radians), Math.sin(radians)]
|
||||
}
|
||||
|
||||
function command_to_state (prev_state, curr_command) {
|
||||
const verb = curr_command[0]
|
||||
switch (verb) {
|
||||
case "goto": {
|
||||
const [_, x, y] = curr_command
|
||||
return {...prev_state, position: [x, y]}
|
||||
}
|
||||
case "home": {
|
||||
return {...prev_state, position: [0, 0], heading: 0}
|
||||
}
|
||||
case "right": {
|
||||
const [_, angle] = curr_command
|
||||
const {heading} = prev_state
|
||||
return {...prev_state, heading: heading + angle}
|
||||
}
|
||||
case "left": {
|
||||
const [_, angle] = curr_command
|
||||
const {heading} = prev_state
|
||||
return {...prev_state, heading: heading - angle}
|
||||
}
|
||||
case "forward": {
|
||||
const [_, steps] = curr_command
|
||||
const {heading, position} = prev_state
|
||||
const unit = unit_of(heading)
|
||||
const move = mult(unit, steps)
|
||||
return {...prev_state, position: add(position, move)}
|
||||
}
|
||||
case "back": {
|
||||
const [_, steps] = curr_command
|
||||
const {heading, position} = prev_state
|
||||
const unit = unit_of(heading)
|
||||
const move = mult(unit, -steps)
|
||||
return {...prev_state, position: add(position, move)}
|
||||
}
|
||||
case "penup": {
|
||||
return {...prev_state, pendown: false}
|
||||
}
|
||||
case "pendown": {
|
||||
return {...prev_state, pendown: true}
|
||||
}
|
||||
case "penwidth": {
|
||||
const [_, width] = curr_command
|
||||
return {...prev_state, penwidth: width}
|
||||
}
|
||||
case "pencolor": {
|
||||
const [_, color] = curr_command
|
||||
return {...prev_state, pencolor: color}
|
||||
}
|
||||
case "setheading": {
|
||||
const [_, heading] = curr_command
|
||||
return {...prev_state, heading: heading}
|
||||
}
|
||||
case "loadstate": {
|
||||
// console.log("LOADSTATE: ", curr_command)
|
||||
const [_, [x, y], heading, visible, pendown, penwidth, pencolor] = curr_command
|
||||
return {position: [x, y], heading, visible, pendown, penwidth, pencolor}
|
||||
}
|
||||
case "show": {
|
||||
return {...prev_state, visible: true}
|
||||
}
|
||||
case "hide": {
|
||||
return {...prev_state, visible: false}
|
||||
}
|
||||
case "background": {
|
||||
background_color = curr_command[1]
|
||||
return prev_state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function io_poller () {
|
||||
if (io_interval_id && !running) {
|
||||
// flush the outbox one last time
|
||||
// (presumably, with the kill message)
|
||||
worker.postMessage(outbox)
|
||||
// cancel the poller
|
||||
clearInterval(io_interval_id)
|
||||
outbox = []
|
||||
function eq_vect (v1, v2) {
|
||||
const [x1, y1] = v1
|
||||
const [x2, y2] = v2
|
||||
return (x1 === x2) && (y1 === y2)
|
||||
}
|
||||
|
||||
function eq_color (c1, c2) {
|
||||
if (c1 === c2) return true
|
||||
const res1 = resolve_color(c1)
|
||||
const res2 = resolve_color(c2)
|
||||
for (let i = 0; i < res1.length; ++i) {
|
||||
if (res1[i] !== res2[i]) return false
|
||||
}
|
||||
if (ready && running) {
|
||||
bundle_keys()
|
||||
worker.postMessage(outbox)
|
||||
outbox = []
|
||||
return true
|
||||
}
|
||||
|
||||
function states_to_call (prev, curr) {
|
||||
const calls = []
|
||||
// whose state should we use?
|
||||
// pen states will only differ on more than one property
|
||||
// if we use `loadstate`
|
||||
// my sense is `prev`, but that may change
|
||||
if (prev.pendown && !eq_vect(prev.position, curr.position)) {
|
||||
calls.push(["line", prev.position[0], prev.position[1], curr.position[0], curr.position[1]])
|
||||
}
|
||||
}
|
||||
|
||||
function bundle_keys () {
|
||||
outbox.push({verb: "Keys", data: Array.from(keys_down)})
|
||||
}
|
||||
|
||||
function start_io_polling () {
|
||||
io_interval_id = setInterval(io_poller, 10)
|
||||
}
|
||||
|
||||
// runs a ludus script; does not return the result
|
||||
// the result must be explicitly polled with `result`
|
||||
export function run (source) {
|
||||
if (running || ready) {
|
||||
// console.log("Main: received bouncy `run` call");
|
||||
return "TODO: handle this? should not be running"
|
||||
if (!eq_color(curr.pencolor, prev.pencolor)) {
|
||||
calls.push(["stroke", ...resolve_color(curr.pencolor)])
|
||||
}
|
||||
// start the vm
|
||||
// wrapping the Run message in an array for the worker
|
||||
worker.postMessage([{verb: "Run", data: source}])
|
||||
// update state for this run
|
||||
code = source
|
||||
running = true
|
||||
// reset the rest of my state
|
||||
outbox = []
|
||||
ludus_console = ""
|
||||
ludus_commands = []
|
||||
ludus_result = null
|
||||
ready = false
|
||||
keys_down = new Set();
|
||||
// start the polling loop
|
||||
start_io_polling()
|
||||
if (curr.penwidth !== prev.penwidth) {
|
||||
calls.push(["strokeWeight", curr.penwidth])
|
||||
}
|
||||
return calls
|
||||
}
|
||||
|
||||
export function is_starting_up() {
|
||||
return running && !ready
|
||||
const turtle_radius = 20
|
||||
|
||||
const turtle_angle = 0.385
|
||||
|
||||
let turtle_color = [255, 255, 255, 150]
|
||||
|
||||
function p5_call_root () {
|
||||
return [
|
||||
["background", ...resolve_color(background_color)],
|
||||
["push"],
|
||||
["rotate", Math.PI],
|
||||
["scale", -1, 1],
|
||||
["stroke", ...resolve_color(turtle_init.pencolor)],
|
||||
]
|
||||
}
|
||||
|
||||
// tells if the ludus script is still running
|
||||
export function is_running() {
|
||||
return running && ready
|
||||
function rotate (vector, heading) {
|
||||
const radians = turn_to_rad(heading)
|
||||
const [x, y] = vector
|
||||
return [
|
||||
(x * Math.cos (radians)) - (y * Math.sin (radians)),
|
||||
(x * Math.sin (radians)) + (y * Math.cos (radians))
|
||||
]
|
||||
}
|
||||
|
||||
// kills a ludus script
|
||||
export function kill () {
|
||||
running = false
|
||||
outbox.push({verb: "Kill"})
|
||||
// console.log("Main: Killed Ludus")
|
||||
function turn_to_rad (heading) {
|
||||
return (heading % 1) * 2 * Math.PI
|
||||
}
|
||||
|
||||
// sends text into ludus (status: not working)
|
||||
export function input (text) {
|
||||
// console.log("Main: calling `input` with ", text)
|
||||
outbox.push({verb: "Input", data: text})
|
||||
function turn_to_deg (heading) {
|
||||
return (heading % 1) * 360
|
||||
}
|
||||
|
||||
// returns the contents of the ludus console and resets the console
|
||||
export function flush_stdout () {
|
||||
let out = ludus_console
|
||||
ludus_console = ""
|
||||
return out
|
||||
function hex (n) {
|
||||
return n.toString(16).padStart(2, "0")
|
||||
}
|
||||
|
||||
// returns the contents of the ludus console, retaining them
|
||||
export function stdout () {
|
||||
return ludus_console
|
||||
function svg_render_line (prev, curr) {
|
||||
if (!prev.pendown) return ""
|
||||
if (eq_vect(prev.position, curr.position)) return ""
|
||||
const {position: [x1, y1], pencolor, penwidth} = prev
|
||||
const {position: [x2, y2]} = curr
|
||||
const [r, g, b, a] = resolve_color(pencolor)
|
||||
return `
|
||||
<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="#${hex(r)}${hex(g)}${hex(b)}" stroke-linecap="square" stroke-opacity="${a/255}" stroke-width="${penwidth}"/>
|
||||
`
|
||||
}
|
||||
|
||||
// returns the array of turtle commands
|
||||
export function commands () {
|
||||
return ludus_commands
|
||||
function escape_svg (svg) {
|
||||
return svg
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'")
|
||||
}
|
||||
|
||||
// returns the array of turtle commands and clears it
|
||||
export function flush_commands () {
|
||||
let out = ludus_commands
|
||||
ludus_commands = []
|
||||
return out
|
||||
export function extract_ludus (svg) {
|
||||
const code = svg.split("<ludus>")[1]?.split("</ludus>")[0] ?? ""
|
||||
return code
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, `"`)
|
||||
.replace(/'/g, `'`)
|
||||
}
|
||||
|
||||
// returns the ludus result
|
||||
// this is effectively Option<String>:
|
||||
// null if no result has been returned, or
|
||||
// a string representation of the result
|
||||
export function result () {
|
||||
return ludus_result
|
||||
function svg_render_path (states) {
|
||||
const path = []
|
||||
for (let i = 1; i < states.length; ++i) {
|
||||
const prev = states[i - 1]
|
||||
const curr = states[i]
|
||||
path.push(svg_render_line(prev, curr))
|
||||
}
|
||||
return path.join("")
|
||||
}
|
||||
|
||||
export function flush_result () {
|
||||
let out = ludus_result
|
||||
ludus_result = null
|
||||
return out
|
||||
function svg_render_turtle (state) {
|
||||
if (!state.visible) return ""
|
||||
const [fr, fg, fb, fa] = turtle_color
|
||||
const fill_alpha = fa/255
|
||||
const {heading, pencolor, position: [x, y], pendown, penwidth} = state
|
||||
const origin = [0, turtle_radius]
|
||||
const [x1, y1] = origin
|
||||
const [x2, y2] = rotate(origin, turtle_angle)
|
||||
const [x3, y3] = rotate(origin, -turtle_angle)
|
||||
const [pr, pg, pb, pa] = resolve_color(pencolor)
|
||||
const pen_alpha = pa/255
|
||||
const ink = pendown ? `<line x1="${x1}" y1="${y1}" x2="0" y2="0" stroke="#${hex(pr)}${hex(pg)}${hex(pb)}" stroke-linecap="round" stroke-opacity="${pen_alpha}" stroke-width="${penwidth}" />` : ""
|
||||
return `
|
||||
<g transform="translate(${x}, ${y})rotate(${-turn_to_deg(heading)})">
|
||||
<polygon points="${x1} ${y1} ${x2} ${y2} ${x3} ${y3}" stroke="none" fill="#${hex(fr)}${hex(fg)}${hex(fb)})" fill-opacity="${fill_alpha}"/>
|
||||
${ink}
|
||||
</g>
|
||||
`
|
||||
}
|
||||
|
||||
export function key_down (key) {
|
||||
if (is_running()) keys_down.add(get_code(key))
|
||||
}
|
||||
|
||||
export function key_up (key) {
|
||||
if (is_running()) keys_down.delete(get_code(key))
|
||||
}
|
||||
|
||||
export {p5} from "./p5.js"
|
||||
|
||||
export function svg (commands) {
|
||||
// console.log(`generating svg for ${code}`)
|
||||
return svg_2(commands, code)
|
||||
// console.log(commands)
|
||||
const states = [turtle_init]
|
||||
commands.reduce((prev_state, command) => {
|
||||
const new_state = command_to_state(prev_state, command)
|
||||
states.push(new_state)
|
||||
return new_state
|
||||
}, turtle_init)
|
||||
// console.log(states)
|
||||
const {maxX, maxY, minX, minY} = states.reduce((accum, {position: [x, y]}) => {
|
||||
accum.maxX = Math.max(accum.maxX, x)
|
||||
accum.maxY = Math.max(accum.maxY, y)
|
||||
accum.minX = Math.min(accum.minX, x)
|
||||
accum.minY = Math.min(accum.minY, y)
|
||||
return accum
|
||||
|
||||
}, {maxX: 0, maxY: 0, minX: 0, minY: 0})
|
||||
const [r, g, b, a] = resolve_color(background_color)
|
||||
if ((r+g+b)/3 > 128) turtle_color = [0, 0, 0, 150]
|
||||
const view_width = (maxX - minX) * 1.2
|
||||
const view_height = (maxY - minY) * 1.2
|
||||
const margin = Math.max(view_width, view_height) * 0.1
|
||||
const x_origin = minX - margin
|
||||
const y_origin = -maxY - margin
|
||||
const path = svg_render_path(states)
|
||||
const turtle = svg_render_turtle(states[states.length - 1])
|
||||
return `<?xml version="1.0" standalone="no"?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="${x_origin} ${y_origin} ${view_width} ${view_height}" width="10in" height="8in">
|
||||
|
||||
<rect x="${x_origin - 5}" y="${y_origin - 5}" width="${view_width + 10}" height="${view_height + 10}" fill="#${hex(r)}${hex(g)}${hex(b)}" stroke-width="0" paint-order="fill" />
|
||||
|
||||
<g transform="scale(-1, 1) rotate(180)">
|
||||
${path}
|
||||
${turtle}
|
||||
</g>
|
||||
|
||||
<ludus>
|
||||
${escape_svg(code)}
|
||||
</ludus>
|
||||
</svg>
|
||||
`
|
||||
}
|
||||
|
||||
function p5_render_turtle (state, calls) {
|
||||
if (!state.visible) return
|
||||
calls.push(["push"])
|
||||
const [r, g, b, a] = turtle_color
|
||||
calls.push(["fill", r, g, b, a])
|
||||
const {heading, pencolor, position: [x, y], pendown, penwidth} = state
|
||||
const origin = [0, turtle_radius]
|
||||
const [x1, y1] = origin
|
||||
const [x2, y2] = rotate(origin, turtle_angle)
|
||||
const [x3, y3] = rotate(origin, -turtle_angle)
|
||||
calls.push(["translate", x, y])
|
||||
// need negative turtle rotation with the other p5 translations
|
||||
calls.push(["rotate", -turn_to_rad(heading)])
|
||||
calls.push(["noStroke"])
|
||||
calls.push(["beginShape"])
|
||||
calls.push(["vertex", x1, y1])
|
||||
calls.push(["vertex", x2, y2])
|
||||
calls.push(["vertex", x3, y3])
|
||||
calls.push(["endShape"])
|
||||
calls.push(["strokeWeight", penwidth])
|
||||
calls.push(["stroke", ...resolve_color(pencolor)])
|
||||
if (pendown) calls.push(["line", 0, 0, x1, y1])
|
||||
calls.push(["pop"])
|
||||
return calls
|
||||
}
|
||||
|
||||
export function p5 (commands) {
|
||||
const states = [turtle_init]
|
||||
commands.reduce((prev_state, command) => {
|
||||
const new_state = command_to_state(prev_state, command)
|
||||
states.push(new_state)
|
||||
return new_state
|
||||
}, turtle_init)
|
||||
// console.log(states)
|
||||
const [r, g, b, _] = resolve_color(background_color)
|
||||
if ((r + g + b)/3 > 128) turtle_color = [0, 0, 0, 150]
|
||||
const p5_calls = [...p5_call_root()]
|
||||
for (let i = 1; i < states.length; ++i) {
|
||||
const prev = states[i - 1]
|
||||
const curr = states[i]
|
||||
const calls = states_to_call(prev, curr)
|
||||
for (const call of calls) {
|
||||
p5_calls.push(call)
|
||||
}
|
||||
}
|
||||
p5_calls[0] = ["background", ...resolve_color(background_color)]
|
||||
p5_render_turtle(states[states.length - 1], p5_calls)
|
||||
p5_calls.push(["pop"])
|
||||
return p5_calls
|
||||
}
|
||||
|
||||
|
|
116
pkg/p5.js
116
pkg/p5.js
|
@ -1,116 +0,0 @@
|
|||
import {eq_vect, eq_color, resolve_color, get_turtle_color, set_turtle_color, turtle_radius, turtle_angle, turn_to_rad, turtle_init, command_to_state, background_color, rotate, last, sub} from "./turtle_geometry.js"
|
||||
|
||||
function states_to_call (prev, curr, call_stack) {
|
||||
// console.log(prev)
|
||||
// console.log(curr)
|
||||
let calls = []
|
||||
if (prev.pendown && !eq_vect(prev.position, curr.position)) {
|
||||
// console.log("headings", prev.heading, curr.heading)
|
||||
// console.log("forward?", curr.forward)
|
||||
// console.log("#calls", call_stack.length)
|
||||
if (prev.heading == curr.heading && curr.forward && call_stack.length > 0) {
|
||||
// console.log("extending line!")
|
||||
const [x, y] = sub(curr.position, prev.position)
|
||||
const last_call = call_stack.pop();
|
||||
const new_call = ["line", last_call[1], last_call[2], x + last_call[3], y + last_call[4]]
|
||||
calls.push(new_call)
|
||||
} else {
|
||||
calls.push(["line", prev.position[0], prev.position[1], curr.position[0], curr.position[1]])
|
||||
}
|
||||
}
|
||||
if (!eq_color(curr.pencolor, prev.pencolor)) {
|
||||
calls.push(["stroke", ...resolve_color(curr.pencolor)])
|
||||
}
|
||||
if (curr.penwidth !== prev.penwidth) {
|
||||
calls.push(["strokeWeight", curr.penwidth])
|
||||
}
|
||||
if (curr.clear) {
|
||||
calls.push("clear")
|
||||
}
|
||||
return calls
|
||||
}
|
||||
|
||||
function p5_call_root () {
|
||||
return [
|
||||
["background", ...resolve_color(background_color)],
|
||||
["push"],
|
||||
["rotate", Math.PI],
|
||||
["scale", -1, 1],
|
||||
["stroke", ...resolve_color(turtle_init.pencolor)],
|
||||
]
|
||||
}
|
||||
|
||||
function p5_render_turtle (state, calls) {
|
||||
if (!state.visible) return
|
||||
calls.push(["push"])
|
||||
const [r, g, b, a] = get_turtle_color()
|
||||
calls.push(["fill", r, g, b, a])
|
||||
const {heading, pencolor, position: [x, y], pendown, penwidth} = state
|
||||
const origin = [0, turtle_radius]
|
||||
const [x1, y1] = origin
|
||||
const [x2, y2] = rotate(origin, turtle_angle)
|
||||
const [x3, y3] = rotate(origin, -turtle_angle)
|
||||
calls.push(["translate", x, y])
|
||||
// need negative turtle rotation with the other p5 translations
|
||||
calls.push(["rotate", -turn_to_rad(heading)])
|
||||
calls.push(["noStroke"])
|
||||
calls.push(["beginShape"])
|
||||
calls.push(["vertex", x1, y1])
|
||||
calls.push(["vertex", x2, y2])
|
||||
calls.push(["vertex", x3, y3])
|
||||
calls.push(["endShape"])
|
||||
calls.push(["strokeWeight", penwidth])
|
||||
calls.push(["stroke", ...resolve_color(pencolor)])
|
||||
if (pendown) calls.push(["line", 0, 0, x1, y1])
|
||||
calls.push(["pop"])
|
||||
return calls
|
||||
}
|
||||
|
||||
export function p5 (commands) {
|
||||
const all_states = {}
|
||||
for (const command of commands) {
|
||||
const [turtle_id, _, this_command] = command
|
||||
let stack = all_states[turtle_id]
|
||||
if (!stack) {
|
||||
const new_stack = [turtle_init]
|
||||
all_states[turtle_id] = new_stack
|
||||
stack = new_stack
|
||||
}
|
||||
let prev_state = last(all_states[turtle_id])
|
||||
const new_state = command_to_state(prev_state, this_command)
|
||||
all_states[turtle_id].push(new_state)
|
||||
|
||||
}
|
||||
// console.log(all_states)
|
||||
const [r, g, b, _] = resolve_color(background_color)
|
||||
if ((r + g + b)/3 > 128) set_turtle_color([0, 0, 0, 150])
|
||||
const p5_calls = [...p5_call_root()]
|
||||
for (const states of Object.values(all_states)) {
|
||||
// console.log(states)
|
||||
let my_calls = []
|
||||
for (let i = 1; i < states.length; ++i) {
|
||||
const prev = states[i - 1]
|
||||
const curr = states[i]
|
||||
const calls = states_to_call(prev, curr, my_calls)
|
||||
for (const call of calls) {
|
||||
// console.log(call)
|
||||
if (call === "clear") {
|
||||
my_calls = []
|
||||
} else {
|
||||
my_calls.push(call)
|
||||
}
|
||||
}
|
||||
}
|
||||
p5_calls.push(["strokeWeight", 1])
|
||||
p5_calls.push(["stroke", 255])
|
||||
for (const call of my_calls) {
|
||||
p5_calls.push(call)
|
||||
}
|
||||
p5_render_turtle(last(states), p5_calls)
|
||||
}
|
||||
p5_calls[0] = ["background", ...resolve_color(background_color)]
|
||||
p5_calls.push(["pop"])
|
||||
return p5_calls
|
||||
}
|
||||
|
||||
|
15
pkg/package.json
Normal file
15
pkg/package.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "rudus",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"files": [
|
||||
"rudus_bg.wasm",
|
||||
"rudus.js",
|
||||
"rudus.d.ts"
|
||||
],
|
||||
"main": "rudus.js",
|
||||
"types": "rudus.d.ts",
|
||||
"sideEffects": [
|
||||
"./snippets/*"
|
||||
]
|
||||
}
|
13
pkg/rudus.d.ts
vendored
13
pkg/rudus.d.ts
vendored
|
@ -1,21 +1,16 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export function ludus(src: string): Promise<void>;
|
||||
export function ludus(src: string): string;
|
||||
|
||||
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||
|
||||
export interface InitOutput {
|
||||
readonly memory: WebAssembly.Memory;
|
||||
readonly ludus: (a: number, b: number) => any;
|
||||
readonly __wbindgen_exn_store: (a: number) => void;
|
||||
readonly __externref_table_alloc: () => number;
|
||||
readonly __wbindgen_export_2: WebAssembly.Table;
|
||||
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
|
||||
readonly ludus: (a: number, b: number) => [number, number];
|
||||
readonly __wbindgen_export_0: WebAssembly.Table;
|
||||
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
||||
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
||||
readonly __wbindgen_export_6: WebAssembly.Table;
|
||||
readonly closure354_externref_shim: (a: number, b: number, c: any) => void;
|
||||
readonly closure367_externref_shim: (a: number, b: number, c: any, d: any) => void;
|
||||
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
|
||||
readonly __wbindgen_start: () => void;
|
||||
}
|
||||
|
||||
|
|
244
pkg/rudus.js
244
pkg/rudus.js
|
@ -1,25 +1,6 @@
|
|||
import { io } from "./worker.js"
|
||||
|
||||
let wasm;
|
||||
|
||||
function addToExternrefTable0(obj) {
|
||||
const idx = wasm.__externref_table_alloc();
|
||||
wasm.__wbindgen_export_2.set(idx, obj);
|
||||
return idx;
|
||||
}
|
||||
|
||||
function handleError(f, args) {
|
||||
try {
|
||||
return f.apply(this, args);
|
||||
} catch (e) {
|
||||
const idx = addToExternrefTable0(e);
|
||||
wasm.__wbindgen_exn_store(idx);
|
||||
}
|
||||
}
|
||||
|
||||
const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
|
||||
|
||||
if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
|
||||
let WASM_VECTOR_LEN = 0;
|
||||
|
||||
let cachedUint8ArrayMemory0 = null;
|
||||
|
||||
|
@ -30,13 +11,6 @@ function getUint8ArrayMemory0() {
|
|||
return cachedUint8ArrayMemory0;
|
||||
}
|
||||
|
||||
function getStringFromWasm0(ptr, len) {
|
||||
ptr = ptr >>> 0;
|
||||
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
|
||||
}
|
||||
|
||||
let WASM_VECTOR_LEN = 0;
|
||||
|
||||
const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } );
|
||||
|
||||
const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
|
||||
|
@ -91,66 +65,31 @@ function passStringToWasm0(arg, malloc, realloc) {
|
|||
return ptr;
|
||||
}
|
||||
|
||||
let cachedDataViewMemory0 = null;
|
||||
const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
|
||||
|
||||
function getDataViewMemory0() {
|
||||
if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
|
||||
cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
|
||||
}
|
||||
return cachedDataViewMemory0;
|
||||
}
|
||||
if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
|
||||
|
||||
function isLikeNone(x) {
|
||||
return x === undefined || x === null;
|
||||
}
|
||||
|
||||
const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined')
|
||||
? { register: () => {}, unregister: () => {} }
|
||||
: new FinalizationRegistry(state => {
|
||||
wasm.__wbindgen_export_6.get(state.dtor)(state.a, state.b)
|
||||
});
|
||||
|
||||
function makeMutClosure(arg0, arg1, dtor, f) {
|
||||
const state = { a: arg0, b: arg1, cnt: 1, dtor };
|
||||
const real = (...args) => {
|
||||
// First up with a closure we increment the internal reference
|
||||
// count. This ensures that the Rust closure environment won't
|
||||
// be deallocated while we're invoking it.
|
||||
state.cnt++;
|
||||
const a = state.a;
|
||||
state.a = 0;
|
||||
try {
|
||||
return f(a, state.b, ...args);
|
||||
} finally {
|
||||
if (--state.cnt === 0) {
|
||||
wasm.__wbindgen_export_6.get(state.dtor)(a, state.b);
|
||||
CLOSURE_DTORS.unregister(state);
|
||||
} else {
|
||||
state.a = a;
|
||||
}
|
||||
}
|
||||
};
|
||||
real.original = state;
|
||||
CLOSURE_DTORS.register(real, state, state);
|
||||
return real;
|
||||
function getStringFromWasm0(ptr, len) {
|
||||
ptr = ptr >>> 0;
|
||||
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
|
||||
}
|
||||
/**
|
||||
* @param {string} src
|
||||
* @returns {Promise<void>}
|
||||
* @returns {string}
|
||||
*/
|
||||
export function ludus(src) {
|
||||
const ptr0 = passStringToWasm0(src, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.ludus(ptr0, len0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
function __wbg_adapter_18(arg0, arg1, arg2) {
|
||||
wasm.closure354_externref_shim(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
function __wbg_adapter_44(arg0, arg1, arg2, arg3) {
|
||||
wasm.closure367_externref_shim(arg0, arg1, arg2, arg3);
|
||||
let deferred2_0;
|
||||
let deferred2_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(src, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.ludus(ptr0, len0);
|
||||
deferred2_0 = ret[0];
|
||||
deferred2_1 = ret[1];
|
||||
return getStringFromWasm0(ret[0], ret[1]);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
async function __wbg_load(module, imports) {
|
||||
|
@ -187,131 +126,8 @@ async function __wbg_load(module, imports) {
|
|||
function __wbg_get_imports() {
|
||||
const imports = {};
|
||||
imports.wbg = {};
|
||||
imports.wbg.__wbg_call_672a4d21634d4a24 = function() { return handleError(function (arg0, arg1) {
|
||||
const ret = arg0.call(arg1);
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_call_7cccdd69e0791ae2 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
const ret = arg0.call(arg1, arg2);
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) {
|
||||
let deferred0_0;
|
||||
let deferred0_1;
|
||||
try {
|
||||
deferred0_0 = arg0;
|
||||
deferred0_1 = arg1;
|
||||
console.error(getStringFromWasm0(arg0, arg1));
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
|
||||
}
|
||||
};
|
||||
imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) {
|
||||
let deferred0_0;
|
||||
let deferred0_1;
|
||||
try {
|
||||
deferred0_0 = arg0;
|
||||
deferred0_1 = arg1;
|
||||
const ret = io(getStringFromWasm0(arg0, arg1));
|
||||
return ret;
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
|
||||
}
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) {
|
||||
console.log(getStringFromWasm0(arg0, arg1));
|
||||
};
|
||||
imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) {
|
||||
try {
|
||||
var state0 = {a: arg0, b: arg1};
|
||||
var cb0 = (arg0, arg1) => {
|
||||
const a = state0.a;
|
||||
state0.a = 0;
|
||||
try {
|
||||
return __wbg_adapter_44(a, state0.b, arg0, arg1);
|
||||
} finally {
|
||||
state0.a = a;
|
||||
}
|
||||
};
|
||||
const ret = new Promise(cb0);
|
||||
return ret;
|
||||
} finally {
|
||||
state0.a = state0.b = 0;
|
||||
}
|
||||
};
|
||||
imports.wbg.__wbg_new_8a6f238a6ece86ea = function() {
|
||||
const ret = new Error();
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) {
|
||||
const ret = new Function(getStringFromWasm0(arg0, arg1));
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_now_8dddb61fa4928554 = function() {
|
||||
const ret = Date.now();
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) {
|
||||
queueMicrotask(arg0);
|
||||
};
|
||||
imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) {
|
||||
const ret = arg0.queueMicrotask;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_random_57c118f142535bb6 = function() {
|
||||
const ret = Math.random();
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) {
|
||||
const ret = Promise.resolve(arg0);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) {
|
||||
const ret = arg1.stack;
|
||||
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||
};
|
||||
imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() {
|
||||
const ret = typeof global === 'undefined' ? null : global;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() {
|
||||
const ret = typeof globalThis === 'undefined' ? null : globalThis;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() {
|
||||
const ret = typeof self === 'undefined' ? null : self;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() {
|
||||
const ret = typeof window === 'undefined' ? null : window;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) {
|
||||
const ret = arg0.then(arg1);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) {
|
||||
const ret = arg0.then(arg1, arg2);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_cb_drop = function(arg0) {
|
||||
const obj = arg0.original;
|
||||
if (obj.cnt-- == 1) {
|
||||
obj.a = 0;
|
||||
return true;
|
||||
}
|
||||
const ret = false;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper1077 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 355, __wbg_adapter_18);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_init_externref_table = function() {
|
||||
const table = wasm.__wbindgen_export_2;
|
||||
const table = wasm.__wbindgen_export_0;
|
||||
const offset = table.grow(4);
|
||||
table.set(0, undefined);
|
||||
table.set(offset + 0, undefined);
|
||||
|
@ -320,25 +136,6 @@ function __wbg_get_imports() {
|
|||
table.set(offset + 3, false);
|
||||
;
|
||||
};
|
||||
imports.wbg.__wbindgen_is_function = function(arg0) {
|
||||
const ret = typeof(arg0) === 'function';
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_is_undefined = function(arg0) {
|
||||
const ret = arg0 === undefined;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
|
||||
const obj = arg1;
|
||||
const ret = typeof(obj) === 'string' ? obj : undefined;
|
||||
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
var len1 = WASM_VECTOR_LEN;
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||
};
|
||||
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
|
||||
throw new Error(getStringFromWasm0(arg0, arg1));
|
||||
};
|
||||
|
||||
return imports;
|
||||
}
|
||||
|
@ -350,7 +147,6 @@ function __wbg_init_memory(imports, memory) {
|
|||
function __wbg_finalize_init(instance, module) {
|
||||
wasm = instance.exports;
|
||||
__wbg_init.__wbindgen_wasm_module = module;
|
||||
cachedDataViewMemory0 = null;
|
||||
cachedUint8ArrayMemory0 = null;
|
||||
|
||||
|
||||
|
|
Binary file not shown.
11
pkg/rudus_bg.wasm.d.ts
vendored
11
pkg/rudus_bg.wasm.d.ts
vendored
|
@ -1,14 +1,9 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const memory: WebAssembly.Memory;
|
||||
export const ludus: (a: number, b: number) => any;
|
||||
export const __wbindgen_exn_store: (a: number) => void;
|
||||
export const __externref_table_alloc: () => number;
|
||||
export const __wbindgen_export_2: WebAssembly.Table;
|
||||
export const __wbindgen_free: (a: number, b: number, c: number) => void;
|
||||
export const ludus: (a: number, b: number) => [number, number];
|
||||
export const __wbindgen_export_0: WebAssembly.Table;
|
||||
export const __wbindgen_malloc: (a: number, b: number) => number;
|
||||
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
||||
export const __wbindgen_export_6: WebAssembly.Table;
|
||||
export const closure354_externref_shim: (a: number, b: number, c: any) => void;
|
||||
export const closure367_externref_shim: (a: number, b: number, c: any, d: any) => void;
|
||||
export const __wbindgen_free: (a: number, b: number, c: number) => void;
|
||||
export const __wbindgen_start: () => void;
|
||||
|
|
120
pkg/svg.js
120
pkg/svg.js
|
@ -1,120 +0,0 @@
|
|||
import {eq_vect, resolve_color, set_turtle_color, get_turtle_color, turtle_radius, rotate, turn_to_deg, command_to_state, turtle_init, background_color, turtle_angle, last} from "./turtle_geometry.js"
|
||||
|
||||
function hex (n) {
|
||||
return n.toString(16).padStart(2, "0")
|
||||
}
|
||||
|
||||
function svg_render_line (prev, curr) {
|
||||
if (!prev.pendown) return ""
|
||||
if (eq_vect(prev.position, curr.position)) return ""
|
||||
const {position: [x1, y1], pencolor, penwidth} = prev
|
||||
const {position: [x2, y2]} = curr
|
||||
const [r, g, b, a] = resolve_color(pencolor)
|
||||
return `
|
||||
<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="#${hex(r)}${hex(g)}${hex(b)}" stroke-linecap="round" stroke-opacity="${a/255}" stroke-width="${penwidth}"/>
|
||||
`
|
||||
}
|
||||
|
||||
function escape_svg (svg) {
|
||||
return svg
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'")
|
||||
}
|
||||
|
||||
export function extract_ludus (svg) {
|
||||
const code = svg.split("<ludus>")[1]?.split("</ludus>")[0] ?? ""
|
||||
return code
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, `"`)
|
||||
.replace(/'/g, `'`)
|
||||
}
|
||||
|
||||
function svg_render_path (states) {
|
||||
const path = []
|
||||
for (let i = 1; i < states.length; ++i) {
|
||||
const prev = states[i - 1]
|
||||
const curr = states[i]
|
||||
path.push(svg_render_line(prev, curr))
|
||||
}
|
||||
return path.join("")
|
||||
}
|
||||
|
||||
function svg_render_turtle (state) {
|
||||
if (!state.visible) return ""
|
||||
const [fr, fg, fb, fa] = get_turtle_color()
|
||||
const fill_alpha = fa/255
|
||||
const {heading, pencolor, position: [x, y], pendown, penwidth} = state
|
||||
const origin = [0, turtle_radius]
|
||||
const [x1, y1] = origin
|
||||
const [x2, y2] = rotate(origin, turtle_angle)
|
||||
const [x3, y3] = rotate(origin, -turtle_angle)
|
||||
const [pr, pg, pb, pa] = resolve_color(pencolor)
|
||||
const pen_alpha = pa/255
|
||||
const ink = pendown ? `<line x1="${x1}" y1="${y1}" x2="0" y2="0" stroke="#${hex(pr)}${hex(pg)}${hex(pb)}" stroke-linecap="round" stroke-opacity="${pen_alpha}" stroke-width="${penwidth}" />` : ""
|
||||
return `
|
||||
<g transform="translate(${x}, ${y})rotate(${-turn_to_deg(heading)})">
|
||||
<polygon points="${x1} ${y1} ${x2} ${y2} ${x3} ${y3}" stroke="none" fill="#${hex(fr)}${hex(fg)}${hex(fb)})" fill-opacity="${fill_alpha}"/>
|
||||
${ink}
|
||||
</g>
|
||||
`
|
||||
}
|
||||
|
||||
// TODO: update this to match the new `p5` function
|
||||
export function svg (commands, code) {
|
||||
const all_states = {}
|
||||
for (const command of commands) {
|
||||
const [turtle_id, _, this_command] = command
|
||||
let stack = all_states[turtle_id]
|
||||
if (!stack) {
|
||||
const new_stack = [turtle_init]
|
||||
all_states[turtle_id] = new_stack
|
||||
stack = new_stack
|
||||
}
|
||||
const prev_state = last(all_states[turtle_id])
|
||||
const new_state = command_to_state(prev_state, this_command)
|
||||
all_states[turtle_id].push(new_state)
|
||||
}
|
||||
let maxX = 0, maxY = 0, minX = 0, minY = 0
|
||||
for (const states of Object.values(all_states)) {
|
||||
for (const {position: [x, y]} of states) {
|
||||
maxX = Math.max(maxX, x)
|
||||
maxY = Math.max(maxY, y)
|
||||
minX = Math.min(minX, x)
|
||||
minY = Math.min(minY, y)
|
||||
}
|
||||
}
|
||||
const [r, g, b] = resolve_color(background_color)
|
||||
if ((r+g+b)/3 > 128) set_turtle_color([0, 0, 0, 150])
|
||||
const view_width = Math.max((maxX - minX) * 1.2, 200)
|
||||
const view_height = Math.max((maxY - minY) * 1.2, 200)
|
||||
const margin = Math.max(view_width, view_height) * 0.1
|
||||
const x_origin = minX - margin
|
||||
const y_origin = -maxY - margin
|
||||
let path = ""
|
||||
let turtle = ""
|
||||
for (const states of Object.values(all_states)) {
|
||||
path = path + svg_render_path(states)
|
||||
turtle = svg_render_turtle(last(states))
|
||||
}
|
||||
return `<?xml version="1.0" standalone="no"?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="${x_origin} ${y_origin} ${view_width} ${view_height}" width="10in" height="8in">
|
||||
|
||||
<rect x="${x_origin - 5}" y="${y_origin - 5}" width="${view_width + 10}" height="${view_height + 10}" fill="#${hex(r)}${hex(g)}${hex(b)}" stroke-width="0" paint-order="fill" />
|
||||
|
||||
<g transform="scale(-1, 1) rotate(180)">
|
||||
${path}
|
||||
${turtle}
|
||||
</g>
|
||||
|
||||
<ludus>
|
||||
${escape_svg(code)}
|
||||
</ludus>
|
||||
</svg>
|
||||
`
|
||||
}
|
||||
|
5
pkg/test.js
Normal file
5
pkg/test.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import * as mod from "./ludus.js";
|
||||
|
||||
console.log(mod.run(`
|
||||
:foobar
|
||||
`));
|
|
@ -1,186 +0,0 @@
|
|||
export const turtle_init = {
|
||||
position: [0, 0],
|
||||
heading: 0,
|
||||
pendown: true,
|
||||
pencolor: "white",
|
||||
penwidth: 1,
|
||||
visible: true
|
||||
}
|
||||
|
||||
export const turtle_radius = 20
|
||||
|
||||
export const turtle_angle = 0.385
|
||||
|
||||
let turtle_color = [255, 255, 255, 150]
|
||||
|
||||
export function get_turtle_color () {
|
||||
return turtle_color
|
||||
}
|
||||
|
||||
export function set_turtle_color (new_color) {
|
||||
turtle_color = new_color
|
||||
}
|
||||
|
||||
export const colors = {
|
||||
black: [0, 0, 0, 255],
|
||||
silver: [192, 192, 192, 255],
|
||||
gray: [128, 128, 128, 255],
|
||||
grey: [128, 128, 128, 255],
|
||||
white: [255, 255, 255, 255],
|
||||
maroon: [128, 0, 0, 255],
|
||||
red: [255, 0, 0, 255],
|
||||
purple: [128, 0, 128, 255],
|
||||
fuchsia: [255, 0, 255, 255],
|
||||
green: [0, 128, 0, 255],
|
||||
lime: [0, 255, 0, 255],
|
||||
olive: [128, 128, 0, 255],
|
||||
yellow: [255, 255, 0, 255],
|
||||
navy: [0, 0, 128, 255],
|
||||
blue: [0, 0, 255, 255],
|
||||
teal: [0, 128, 128, 255],
|
||||
aqua: [0, 255, 255, 255],
|
||||
}
|
||||
|
||||
export function resolve_color (color) {
|
||||
if (typeof color === 'string') return colors[color]
|
||||
if (typeof color === 'number') return [color, color, color, 255]
|
||||
if (Array.isArray(color)) return color
|
||||
return [0, 0, 0, 255] // default to black?
|
||||
}
|
||||
|
||||
export let background_color = "black"
|
||||
|
||||
export function add (v1, v2) {
|
||||
const [x1, y1] = v1
|
||||
const [x2, y2] = v2
|
||||
return [x1 + x2, y1 + y2]
|
||||
}
|
||||
|
||||
export function sub (v1, v2) {
|
||||
const [x1, y1] = v1
|
||||
const [x2, y2] = v2
|
||||
return [x1 - x2, y1 - y2]
|
||||
}
|
||||
|
||||
export function mult (vector, scalar) {
|
||||
const [x, y] = vector
|
||||
return [x * scalar, y * scalar]
|
||||
}
|
||||
|
||||
export function unit_of (heading) {
|
||||
const turns = -heading + 0.25
|
||||
const radians = turn_to_rad(turns)
|
||||
return [Math.cos(radians), Math.sin(radians)]
|
||||
}
|
||||
|
||||
export function command_to_state (prev_state, curr_command) {
|
||||
const [verb] = curr_command
|
||||
switch (verb) {
|
||||
case "goto": {
|
||||
const [_, [x, y]] = curr_command
|
||||
return {...prev_state, position: [x, y], clear: false}
|
||||
}
|
||||
case "home": {
|
||||
return {...prev_state, position: [0, 0], heading: 0, clear: false}
|
||||
}
|
||||
case "right": {
|
||||
const [_, angle] = curr_command
|
||||
const {heading} = prev_state
|
||||
return {...prev_state, heading: heading + angle, clear: false}
|
||||
}
|
||||
case "left": {
|
||||
const [_, angle] = curr_command
|
||||
const {heading} = prev_state
|
||||
return {...prev_state, heading: heading - angle, clear: false}
|
||||
}
|
||||
case "forward": {
|
||||
const [_, steps] = curr_command
|
||||
const {heading, position} = prev_state
|
||||
const unit = unit_of(heading)
|
||||
const move = mult(unit, steps)
|
||||
return {...prev_state, position: add(position, move), clear: false, forward: true}
|
||||
}
|
||||
case "back": {
|
||||
const [_, steps] = curr_command
|
||||
const {heading, position} = prev_state
|
||||
const unit = unit_of(heading)
|
||||
const move = mult(unit, -steps)
|
||||
return {...prev_state, position: add(position, move), clear: false, forward: false}
|
||||
}
|
||||
case "penup": {
|
||||
return {...prev_state, pendown: false, clear: false}
|
||||
}
|
||||
case "pendown": {
|
||||
return {...prev_state, pendown: true, clear: false}
|
||||
}
|
||||
case "penwidth": {
|
||||
const [_, width] = curr_command
|
||||
return {...prev_state, penwidth: width, clear: false}
|
||||
}
|
||||
case "pencolor": {
|
||||
const [_, color] = curr_command
|
||||
return {...prev_state, pencolor: color, clear: false}
|
||||
}
|
||||
case "setheading": {
|
||||
const [_, heading] = curr_command
|
||||
return {...prev_state, heading: heading, clear: false}
|
||||
}
|
||||
case "loadstate": {
|
||||
// console.log("LOADSTATE: ", curr_command)
|
||||
const [_, [x, y], heading, visible, pendown, penwidth, pencolor] = curr_command
|
||||
return {position: [x, y], heading, visible, pendown, penwidth, pencolor}
|
||||
}
|
||||
case "show": {
|
||||
return {...prev_state, visible: true, clear: false}
|
||||
}
|
||||
case "hide": {
|
||||
return {...prev_state, visible: false, clear: false}
|
||||
}
|
||||
case "background": {
|
||||
background_color = curr_command[1]
|
||||
return prev_state
|
||||
}
|
||||
case "clear": {
|
||||
return {...prev_state, clear: true}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function eq_vect (v1, v2) {
|
||||
const [x1, y1] = v1
|
||||
const [x2, y2] = v2
|
||||
return (x1 === x2) && (y1 === y2)
|
||||
}
|
||||
|
||||
export function eq_color (c1, c2) {
|
||||
if (c1 === c2) return true
|
||||
const res1 = resolve_color(c1)
|
||||
const res2 = resolve_color(c2)
|
||||
for (let i = 0; i < res1.length; ++i) {
|
||||
if (res1[i] !== res2[i]) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export function rotate (vector, heading) {
|
||||
const radians = turn_to_rad(heading)
|
||||
const [x, y] = vector
|
||||
return [
|
||||
(x * Math.cos (radians)) - (y * Math.sin (radians)),
|
||||
(x * Math.sin (radians)) + (y * Math.cos (radians))
|
||||
]
|
||||
}
|
||||
|
||||
export function turn_to_rad (heading) {
|
||||
return (heading % 1) * 2 * Math.PI
|
||||
}
|
||||
|
||||
export function turn_to_deg (heading) {
|
||||
return (heading % 1) * 360
|
||||
}
|
||||
|
||||
export function last (arr) {
|
||||
return arr[arr.length - 1]
|
||||
}
|
||||
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
import init, {ludus} from "./rudus.js";
|
||||
|
||||
let initialized_wasm = false
|
||||
onmessage = run
|
||||
|
||||
// exposed in rust as:
|
||||
// async fn io (out: String) -> Result<JsValue, JsValue>
|
||||
// rust calls this to perform io
|
||||
export function io (out) {
|
||||
// only send messages if we have some
|
||||
if (out.length > 0) postMessage(out)
|
||||
// make an event handler that captures and delivers messages from the main thread
|
||||
// because our promise resolution isn't about calculating a value but setting a global variable, we can't asyncify it
|
||||
// explicitly return a promise
|
||||
return new Promise((resolve, reject) => {
|
||||
// deliver the response to ludus when we get a response from the main thread
|
||||
onmessage = (e) => {
|
||||
resolve(JSON.stringify(e.data))
|
||||
}
|
||||
// cancel the response if it takes too long
|
||||
setTimeout(() => reject(`io took too long to respond to ${out}`), 1000)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// set as default event handler from main thread
|
||||
async function run(e) {
|
||||
// we must NEVER run `await init()` twice
|
||||
if (!initialized_wasm) {
|
||||
// this must come before the init call
|
||||
initialized_wasm = true
|
||||
await init()
|
||||
console.log("Worker: Ludus has been initialized.")
|
||||
}
|
||||
// the data is always an array; we only really expect one member tho
|
||||
let msgs = e.data
|
||||
for (const msg of msgs) {
|
||||
// evaluate source if we get some
|
||||
if (msg.verb === "Run" && typeof msg.data === 'string') {
|
||||
// temporarily stash an empty function so we don't keep calling this one if we receive additional messages
|
||||
onmessage = () => {}
|
||||
// actually run the ludus--which will call `io`--and replace `run` as the event handler for ipc
|
||||
await ludus(msg.data)
|
||||
// once we've returned from `ludus`, make this the event handler again
|
||||
onmessage = run
|
||||
} else {
|
||||
// report and swallow any malformed startup messages
|
||||
console.log("Worker: Did not get valid startup message. Instead got:")
|
||||
console.log(e.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
1
sandbox.ld
Normal file
1
sandbox.ld
Normal file
|
@ -0,0 +1 @@
|
|||
print! ("foobar")
|
917
sandbox_run.txt
Normal file
917
sandbox_run.txt
Normal file
|
@ -0,0 +1,917 @@
|
|||
{
|
||||
|
||||
box foos = []
|
||||
fn foo! {
|
||||
() -> update! (foos, append (_, :foo) )
|
||||
}
|
||||
fn foos! {
|
||||
() -> repeat 4 {
|
||||
{
|
||||
|
||||
foo! ()
|
||||
}
|
||||
}
|
||||
}
|
||||
foos! ()
|
||||
unbox (foos)
|
||||
}
|
||||
closing over in type at 1: #{:list fn list/base...
|
||||
closing over in eq? at 1: #{:list fn list/base...
|
||||
closing over in eq? at 2: fn eq?
|
||||
closing over in first at 1: #{:list fn list/base...
|
||||
closing over in rest at 1: #{:list fn list/base...
|
||||
closing over in inc at 1: #{:list fn list/base...
|
||||
closing over in dec at 1: #{:list fn list/base...
|
||||
closing over in count at 1: #{:list fn list/base...
|
||||
closing over in any? at 1: fn empty?
|
||||
closing over in any? at 2: fn not
|
||||
closing over in list at 1: #{:list fn list/base...
|
||||
closing over in append at 1: #{:list fn list/base...
|
||||
closing over in fold at 1: fn fold
|
||||
closing over in fold at 2: fn first
|
||||
closing over in fold at 3: fn rest
|
||||
closing over in foldr at 1: fn foldr
|
||||
closing over in foldr at 2: fn first
|
||||
closing over in foldr at 3: fn rest
|
||||
closing over in map at 1: fn map
|
||||
closing over in map at 2: fn append
|
||||
closing over in map at 3: fn fold
|
||||
closing over in filter at 1: fn filter
|
||||
closing over in filter at 2: fn append
|
||||
closing over in filter at 3: fn fold
|
||||
closing over in keep at 1: fn some?
|
||||
closing over in keep at 2: fn filter
|
||||
closing over in concat at 1: #{:list fn list/base...
|
||||
closing over in concat at 2: fn concat
|
||||
closing over in concat at 3: fn fold
|
||||
closing over in contains? at 1: fn first
|
||||
closing over in contains? at 2: fn eq?
|
||||
closing over in contains? at 3: fn rest
|
||||
closing over in print! at 1: #{:list fn list/base...
|
||||
closing over in show at 1: #{:list fn list/base...
|
||||
closing over in report! at 1: fn print!
|
||||
closing over in report! at 2: fn show
|
||||
closing over in report! at 3: fn concat
|
||||
closing over in doc! at 1: #{:list fn list/base...
|
||||
closing over in doc! at 2: fn print!
|
||||
closing over in string at 1: fn show
|
||||
closing over in string at 2: fn string
|
||||
closing over in string at 3: fn concat
|
||||
closing over in join at 1: fn join
|
||||
closing over in join at 2: fn concat
|
||||
closing over in join at 3: fn fold
|
||||
closing over in split at 1: #{:list fn list/base...
|
||||
closing over in trim at 1: #{:list fn list/base...
|
||||
closing over in upcase at 1: #{:list fn list/base...
|
||||
closing over in downcase at 1: #{:list fn list/base...
|
||||
closing over in chars at 1: #{:list fn list/base...
|
||||
closing over in chars/safe at 1: #{:list fn list/base...
|
||||
closing over in strip at 1: fn strip
|
||||
closing over in words at 1: fn strip
|
||||
closing over in words at 2: fn split
|
||||
closing over in words at 3: fn empty?
|
||||
closing over in words at 4: fn append
|
||||
closing over in words at 5: fn fold
|
||||
closing over in sentence at 1: fn join
|
||||
closing over in to_number at 1: #{:list fn list/base...
|
||||
closing over in unbox at 1: #{:list fn list/base...
|
||||
closing over in store! at 1: #{:list fn list/base...
|
||||
closing over in update! at 1: fn unbox
|
||||
closing over in update! at 2: fn store!
|
||||
closing over in add at 1: #{:list fn list/base...
|
||||
closing over in add at 2: fn add
|
||||
closing over in add at 3: fn fold
|
||||
closing over in sub at 1: #{:list fn list/base...
|
||||
closing over in sub at 2: fn sub
|
||||
closing over in sub at 3: fn fold
|
||||
closing over in mult at 1: #{:list fn list/base...
|
||||
closing over in mult at 2: fn mult
|
||||
closing over in mult at 3: fn fold
|
||||
closing over in div at 1: #{:list fn list/base...
|
||||
closing over in div at 2: fn mult
|
||||
closing over in div at 3: fn fold
|
||||
closing over in div at 4: fn div
|
||||
closing over in div/0 at 1: #{:list fn list/base...
|
||||
closing over in div/0 at 2: fn mult
|
||||
closing over in div/0 at 3: fn fold
|
||||
closing over in div/0 at 4: fn div/0
|
||||
closing over in div/safe at 1: fn div
|
||||
closing over in div/safe at 2: fn mult
|
||||
closing over in div/safe at 3: fn fold
|
||||
closing over in div/safe at 4: fn div/safe
|
||||
closing over in inv at 1: fn div
|
||||
closing over in inv/0 at 1: fn div/0
|
||||
closing over in inv/safe at 1: fn div/safe
|
||||
closing over in neg at 1: fn mult
|
||||
closing over in gt? at 1: #{:list fn list/base...
|
||||
closing over in gte? at 1: #{:list fn list/base...
|
||||
closing over in lt? at 1: #{:list fn list/base...
|
||||
closing over in lte? at 1: #{:list fn list/base...
|
||||
closing over in between? at 1: fn gte?
|
||||
closing over in between? at 2: fn lt?
|
||||
closing over in neg? at 1: fn lt?
|
||||
closing over in pos? at 1: fn gt?
|
||||
closing over in abs at 1: fn neg?
|
||||
closing over in abs at 2: fn mult
|
||||
closing over in turn/deg at 1: fn mult
|
||||
closing over in deg/turn at 1: fn div
|
||||
closing over in turn/rad at 1: 6.283185307179586
|
||||
closing over in turn/rad at 2: fn mult
|
||||
closing over in rad/turn at 1: 6.283185307179586
|
||||
closing over in rad/turn at 2: fn div
|
||||
closing over in deg/rad at 1: 6.283185307179586
|
||||
closing over in deg/rad at 2: fn div
|
||||
closing over in deg/rad at 3: fn mult
|
||||
closing over in rad/deg at 1: 6.283185307179586
|
||||
closing over in rad/deg at 2: fn div
|
||||
closing over in rad/deg at 3: fn mult
|
||||
closing over in sin at 1: fn turn/rad
|
||||
closing over in sin at 2: #{:list fn list/base...
|
||||
closing over in sin at 3: fn deg/rad
|
||||
closing over in cos at 1: fn turn/rad
|
||||
closing over in cos at 2: #{:list fn list/base...
|
||||
closing over in cos at 3: fn deg/rad
|
||||
closing over in tan at 1: fn turn/rad
|
||||
closing over in tan at 2: #{:list fn list/base...
|
||||
closing over in tan at 3: fn deg/rad
|
||||
closing over in rotate at 1: fn rotate
|
||||
closing over in rotate at 2: fn cos
|
||||
closing over in rotate at 3: fn mult
|
||||
closing over in rotate at 4: fn sin
|
||||
closing over in rotate at 5: fn sub
|
||||
closing over in rotate at 6: fn add
|
||||
closing over in atan/2 at 1: #{:list fn list/base...
|
||||
closing over in atan/2 at 2: fn rad/turn
|
||||
closing over in atan/2 at 3: fn atan/2
|
||||
closing over in atan/2 at 4: fn rad/deg
|
||||
closing over in angle at 1: fn atan/2
|
||||
closing over in angle at 2: fn sub
|
||||
closing over in mod at 1: #{:list fn list/base...
|
||||
closing over in mod/0 at 1: #{:list fn list/base...
|
||||
closing over in mod/safe at 1: #{:list fn list/base...
|
||||
closing over in even? at 1: fn mod
|
||||
closing over in even? at 2: fn eq?
|
||||
closing over in odd? at 1: fn mod
|
||||
closing over in odd? at 2: fn eq?
|
||||
closing over in square at 1: fn mult
|
||||
closing over in sqrt at 1: fn neg?
|
||||
closing over in sqrt at 2: fn not
|
||||
closing over in sqrt at 3: #{:list fn list/base...
|
||||
closing over in sqrt/safe at 1: fn neg?
|
||||
closing over in sqrt/safe at 2: fn not
|
||||
closing over in sqrt/safe at 3: #{:list fn list/base...
|
||||
closing over in sum_of_squares at 1: fn square
|
||||
closing over in sum_of_squares at 2: fn add
|
||||
closing over in sum_of_squares at 3: fn sum_of_squares
|
||||
closing over in sum_of_squares at 4: fn fold
|
||||
closing over in dist at 1: fn sum_of_squares
|
||||
closing over in dist at 2: fn sqrt
|
||||
closing over in dist at 3: fn dist
|
||||
closing over in heading/vector at 1: fn neg
|
||||
closing over in heading/vector at 2: fn add
|
||||
closing over in heading/vector at 3: fn cos
|
||||
closing over in heading/vector at 4: fn sin
|
||||
closing over in floor at 1: #{:list fn list/base...
|
||||
closing over in ceil at 1: #{:list fn list/base...
|
||||
closing over in round at 1: #{:list fn list/base...
|
||||
closing over in range at 1: #{:list fn list/base...
|
||||
closing over in at at 1: #{:list fn list/base...
|
||||
closing over in second at 1: fn ordered?
|
||||
closing over in second at 2: fn at
|
||||
closing over in last at 1: fn ordered?
|
||||
closing over in last at 2: fn count
|
||||
closing over in last at 3: fn dec
|
||||
closing over in last at 4: fn at
|
||||
closing over in slice at 1: fn slice
|
||||
closing over in slice at 2: fn gte?
|
||||
closing over in slice at 3: fn count
|
||||
closing over in slice at 4: fn gt?
|
||||
closing over in slice at 5: fn neg?
|
||||
closing over in slice at 6: #{:list fn list/base...
|
||||
closing over in butlast at 1: fn count
|
||||
closing over in butlast at 2: fn dec
|
||||
closing over in butlast at 3: fn slice
|
||||
closing over in assoc at 1: #{:list fn list/base...
|
||||
closing over in dissoc at 1: #{:list fn list/base...
|
||||
closing over in get at 1: fn get
|
||||
closing over in get at 2: #{:list fn list/base...
|
||||
closing over in update at 1: fn get
|
||||
closing over in update at 2: fn assoc
|
||||
closing over in keys at 1: fn list
|
||||
closing over in keys at 2: fn first
|
||||
closing over in keys at 3: fn map
|
||||
closing over in values at 1: fn list
|
||||
closing over in values at 2: fn second
|
||||
closing over in values at 3: fn map
|
||||
closing over in has? at 1: fn has?
|
||||
closing over in has? at 2: fn get
|
||||
closing over in has? at 3: fn some?
|
||||
closing over in dict at 1: fn assoc
|
||||
closing over in dict at 2: fn fold
|
||||
closing over in dict at 3: fn list
|
||||
closing over in dict at 4: fn dict
|
||||
closing over in each! at 1: fn each!
|
||||
closing over in random at 1: #{:list fn list/base...
|
||||
closing over in random at 2: fn random
|
||||
closing over in random at 3: fn mult
|
||||
closing over in random at 4: fn sub
|
||||
closing over in random at 5: fn add
|
||||
closing over in random at 6: fn count
|
||||
closing over in random at 7: fn floor
|
||||
closing over in random at 8: fn at
|
||||
closing over in random at 9: fn keys
|
||||
closing over in random at 10: fn get
|
||||
closing over in random_int at 1: fn random
|
||||
closing over in random_int at 2: fn floor
|
||||
closing over in add_command! at 1: box { [] }
|
||||
closing over in add_command! at 2: fn append
|
||||
closing over in add_command! at 3: fn update!
|
||||
closing over in add_command! at 4: box { #{:position (0...
|
||||
closing over in add_command! at 5: fn unbox
|
||||
closing over in add_command! at 6: fn apply_command
|
||||
closing over in add_command! at 7: fn store!
|
||||
closing over in forward! at 1: fn add_command!
|
||||
closing over in back! at 1: fn add_command!
|
||||
closing over in left! at 1: fn add_command!
|
||||
closing over in right! at 1: fn add_command!
|
||||
closing over in penup! at 1: fn add_command!
|
||||
closing over in pendown! at 1: fn add_command!
|
||||
closing over in pencolor! at 1: fn add_command!
|
||||
closing over in penwidth! at 1: fn add_command!
|
||||
closing over in background! at 1: fn add_command!
|
||||
closing over in home! at 1: fn add_command!
|
||||
closing over in clear! at 1: fn add_command!
|
||||
closing over in goto! at 1: fn add_command!
|
||||
closing over in goto! at 2: fn goto!
|
||||
closing over in setheading! at 1: fn add_command!
|
||||
closing over in showturtle! at 1: fn add_command!
|
||||
closing over in hideturtle! at 1: fn add_command!
|
||||
closing over in loadstate! at 1: fn add_command!
|
||||
closing over in apply_command at 1: fn assoc
|
||||
closing over in apply_command at 2: fn add
|
||||
closing over in apply_command at 3: fn update
|
||||
closing over in apply_command at 4: fn sub
|
||||
closing over in apply_command at 5: fn heading/vector
|
||||
closing over in apply_command at 6: fn mult
|
||||
closing over in position at 1: box { #{:position (0...
|
||||
closing over in position at 2: fn unbox
|
||||
closing over in heading at 1: box { #{:position (0...
|
||||
closing over in heading at 2: fn unbox
|
||||
closing over in pendown? at 1: box { #{:position (0...
|
||||
closing over in pendown? at 2: fn unbox
|
||||
closing over in pencolor at 1: box { #{:position (0...
|
||||
closing over in pencolor at 2: fn unbox
|
||||
closing over in penwidth at 1: box { #{:position (0...
|
||||
closing over in penwidth at 2: fn unbox
|
||||
binding `foos` in sandbox
|
||||
stack depth: 1; match depth: 0
|
||||
at stack index: 0
|
||||
new locals: foos@0//0
|
||||
binding `foo!` in sandbox
|
||||
stack depth: 2; match depth: 0
|
||||
at stack index: 1
|
||||
new locals: foos@0//0|foo!@1//0
|
||||
***function clause matching: : ()
|
||||
***calling function update! stack depth: 0
|
||||
resolving binding `foos` in foo!
|
||||
locals:
|
||||
as enclosing upvalue 0
|
||||
***calling function append stack depth: 1
|
||||
resolving binding `append` in foo!
|
||||
locals:
|
||||
as enclosing upvalue 1
|
||||
resolving binding `update!` in foo!
|
||||
locals:
|
||||
as enclosing upvalue 2
|
||||
***after 2 args stack depth: 3
|
||||
=== function chuncktion: foo!/0 ===
|
||||
IDX | CODE | INFO
|
||||
0000: reset_match
|
||||
0001: ***function clause matching: : ()
|
||||
0003: match
|
||||
0004: jump 00000
|
||||
0007: jump_if_no_match 00034
|
||||
0010: ***calling function update! stack depth: 0
|
||||
0012: resolving binding `foos` in foo!
|
||||
locals:
|
||||
0014: as enclosing upvalue 0
|
||||
0016: get_upvalue 000
|
||||
0018: ***calling function append stack depth: 1
|
||||
0020: nothing
|
||||
0021: constant 00000: :foo
|
||||
0024: resolving binding `append` in foo!
|
||||
locals:
|
||||
0026: as enclosing upvalue 1
|
||||
0028: get_upvalue 001
|
||||
0030: partial 002
|
||||
0032: resolving binding `update!` in foo!
|
||||
locals:
|
||||
0034: as enclosing upvalue 2
|
||||
0036: get_upvalue 002
|
||||
0038: ***after 2 args stack depth: 3
|
||||
0040: tail_call 002
|
||||
0042: store
|
||||
0043: return
|
||||
0044: panic_no_match
|
||||
resolving binding `foos` in sandbox
|
||||
locals: foos@0//0|foo!@1//0
|
||||
at locals position 0
|
||||
resolving binding `append` in sandbox
|
||||
locals: foos@0//0|foo!@1//0
|
||||
as global
|
||||
resolving binding `update!` in sandbox
|
||||
locals: foos@0//0|foo!@1//0
|
||||
as global
|
||||
binding `foos!` in sandbox
|
||||
stack depth: 3; match depth: 0
|
||||
at stack index: 2
|
||||
new locals: foos@0//0|foo!@1//0|foos!@2//0
|
||||
***function clause matching: : ()
|
||||
***calling function foo! stack depth: 1
|
||||
resolving binding `foo!` in foos!
|
||||
locals:
|
||||
as enclosing upvalue 0
|
||||
***after 0 args stack depth: 2
|
||||
leaving scope 1
|
||||
***leaving block before pop stack depth: 1
|
||||
popping back from 1 to 1
|
||||
=== function chuncktion: foos!/0 ===
|
||||
IDX | CODE | INFO
|
||||
0000: reset_match
|
||||
0001: ***function clause matching: : ()
|
||||
0003: match
|
||||
0004: jump 00000
|
||||
0007: jump_if_no_match 00042
|
||||
0010: constant 00000: 4
|
||||
0013: truncate
|
||||
0014: jump 00001
|
||||
0017: decrement
|
||||
0018: duplicate
|
||||
0019: jump_if_zero 00024
|
||||
0022: ***calling function foo! stack depth: 1
|
||||
0024: resolving binding `foo!` in foos!
|
||||
locals:
|
||||
0026: as enclosing upvalue 0
|
||||
0028: get_upvalue 000
|
||||
0030: ***after 0 args stack depth: 2
|
||||
0032: tail_call 000
|
||||
0034: store
|
||||
0035: leaving scope 1
|
||||
0037: ***leaving block before pop stack depth: 1
|
||||
0039: popping back from 1 to 1
|
||||
0041: load
|
||||
0042: pop
|
||||
0043: jump_back 00026
|
||||
0046: pop
|
||||
0047: constant 00001: nil
|
||||
0050: store
|
||||
0051: return
|
||||
0052: panic_no_match
|
||||
resolving binding `foo!` in sandbox
|
||||
locals: foos@0//0|foo!@1//0|foos!@2//0
|
||||
at locals position 1
|
||||
***calling function foos! stack depth: 3
|
||||
resolving binding `foos!` in sandbox
|
||||
locals: foos@0//0|foo!@1//0|foos!@2//0
|
||||
at locals position 2
|
||||
***after 0 args stack depth: 4
|
||||
***calling function unbox stack depth: 3
|
||||
resolving binding `foos` in sandbox
|
||||
locals: foos@0//0|foo!@1//0|foos!@2//0
|
||||
at locals position 0
|
||||
resolving binding `unbox` in sandbox
|
||||
locals: foos@0//0|foo!@1//0|foos!@2//0
|
||||
as global
|
||||
***after 1 args stack depth: 5
|
||||
leaving scope 0
|
||||
releasing binding foos!@2//0
|
||||
releasing binding foo!@1//0
|
||||
releasing binding foos@0//0
|
||||
***leaving block before pop stack depth: 3
|
||||
popping back from 3 to 0
|
||||
=== source code ===
|
||||
box foos = []
|
||||
|
||||
fn foo! () -> update! (foos, append (_, :foo))
|
||||
|
||||
fn foos! () -> repeat 4 {
|
||||
foo! ()
|
||||
}
|
||||
|
||||
foos! ()
|
||||
|
||||
unbox (foos)
|
||||
|
||||
=== chunk: sandbox ===
|
||||
IDX | CODE | INFO
|
||||
0000: push_list
|
||||
0001: push_box 082
|
||||
0003: noop
|
||||
0004: stack depth: 1; match depth: 0
|
||||
0006: at stack index: 0
|
||||
0008: new locals: foos@0//0
|
||||
0010: constant 00000: fn foo!
|
||||
0013: binding `foo!` in sandbox
|
||||
0015: stack depth: 2; match depth: 0
|
||||
0017: at stack index: 1
|
||||
0019: new locals: foos@0//0|foo!@1//0
|
||||
0021: resolving binding `foos` in sandbox
|
||||
locals: foos@0//0|foo!@1//0
|
||||
0023: at locals position 0
|
||||
0025: push_binding 000
|
||||
0027: set_upvalue
|
||||
0028: resolving binding `append` in sandbox
|
||||
locals: foos@0//0|foo!@1//0
|
||||
0030: as global
|
||||
0032: constant 00001: :append
|
||||
0035: push_global
|
||||
0036: set_upvalue
|
||||
0037: resolving binding `update!` in sandbox
|
||||
locals: foos@0//0|foo!@1//0
|
||||
0039: as global
|
||||
0041: constant 00002: :update!
|
||||
0044: push_global
|
||||
0045: set_upvalue
|
||||
0046: constant 00003: fn foos!
|
||||
0049: binding `foos!` in sandbox
|
||||
0051: stack depth: 3; match depth: 0
|
||||
0053: at stack index: 2
|
||||
0055: new locals: foos@0//0|foo!@1//0|foos!@2//0
|
||||
0057: resolving binding `foo!` in sandbox
|
||||
locals: foos@0//0|foo!@1//0|foos!@2//0
|
||||
0059: at locals position 1
|
||||
0061: push_binding 001
|
||||
0063: set_upvalue
|
||||
0064: ***calling function foos! stack depth: 3
|
||||
0066: resolving binding `foos!` in sandbox
|
||||
locals: foos@0//0|foo!@1//0|foos!@2//0
|
||||
0068: at locals position 2
|
||||
0070: push_binding 002
|
||||
0072: ***after 0 args stack depth: 4
|
||||
0074: call 000
|
||||
0076: pop
|
||||
0077: ***calling function unbox stack depth: 3
|
||||
0079: resolving binding `foos` in sandbox
|
||||
locals: foos@0//0|foo!@1//0|foos!@2//0
|
||||
0081: at locals position 0
|
||||
0083: push_binding 000
|
||||
0085: resolving binding `unbox` in sandbox
|
||||
locals: foos@0//0|foo!@1//0|foos!@2//0
|
||||
0087: as global
|
||||
0089: constant 00004: :unbox
|
||||
0092: push_global
|
||||
0093: ***after 1 args stack depth: 5
|
||||
0095: call 001
|
||||
0097: store
|
||||
0098: leaving scope 0
|
||||
0100: releasing binding foos!@2//0
|
||||
0102: releasing binding foo!@1//0
|
||||
0104: releasing binding foos@0//0
|
||||
0106: ***leaving block before pop stack depth: 3
|
||||
0108: popping back from 3 to 0
|
||||
0110: pop_n 003
|
||||
0112: load
|
||||
|
||||
|
||||
|
||||
=== vm run ===
|
||||
0000: [] (_,_,_,_,_,_,_,_)
|
||||
0000: push_list
|
||||
0001: [->[]<-] (_,_,_,_,_,_,_,_)
|
||||
0001: push_box 082
|
||||
0002: [->box { [] }<-] (_,_,_,_,_,_,_,_)
|
||||
0002: binding `foos` in sandbox
|
||||
0004: [->box { [] }<-] (_,_,_,_,_,_,_,_)
|
||||
0004: stack depth: 1; match depth: 0
|
||||
0006: [->box { [] }<-] (_,_,_,_,_,_,_,_)
|
||||
0006: at stack index: 0
|
||||
0008: [->box { [] }<-] (_,_,_,_,_,_,_,_)
|
||||
0008: new locals: foos@0//0
|
||||
0010: [->box { [] }<-] (_,_,_,_,_,_,_,_)
|
||||
0010: constant 00000: fn foo!
|
||||
0013: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
|
||||
0013: binding `foo!` in sandbox
|
||||
0015: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
|
||||
0015: stack depth: 2; match depth: 0
|
||||
0017: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
|
||||
0017: at stack index: 1
|
||||
0019: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
|
||||
0019: new locals: foos@0//0|foo!@1//0
|
||||
0021: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
|
||||
0021: resolving binding `foos` in sandbox
|
||||
locals: foos@0//0|foo!@1//0
|
||||
0023: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
|
||||
0023: at locals position 0
|
||||
0025: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
|
||||
0025: push_binding 000
|
||||
0027: [->box { [] }<-|fn foo!|box { [] }] (_,_,_,_,_,_,_,_)
|
||||
0027: set_upvalue
|
||||
closing over in foo! at 1: box { [] }
|
||||
0028: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
|
||||
0028: resolving binding `append` in sandbox
|
||||
locals: foos@0//0|foo!@1//0
|
||||
0030: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
|
||||
0030: as global
|
||||
0032: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
|
||||
0032: constant 00001: :append
|
||||
0035: [->box { [] }<-|fn foo!|:append] (_,_,_,_,_,_,_,_)
|
||||
0035: push_global
|
||||
0036: [->box { [] }<-|fn foo!|fn append] (_,_,_,_,_,_,_,_)
|
||||
0036: set_upvalue
|
||||
closing over in foo! at 2: fn append
|
||||
0037: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
|
||||
0037: resolving binding `update!` in sandbox
|
||||
locals: foos@0//0|foo!@1//0
|
||||
0039: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
|
||||
0039: as global
|
||||
0041: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
|
||||
0041: constant 00002: :update!
|
||||
0044: [->box { [] }<-|fn foo!|:update!] (_,_,_,_,_,_,_,_)
|
||||
0044: push_global
|
||||
0045: [->box { [] }<-|fn foo!|fn update!] (_,_,_,_,_,_,_,_)
|
||||
0045: set_upvalue
|
||||
closing over in foo! at 3: fn update!
|
||||
0046: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
|
||||
0046: constant 00003: fn foos!
|
||||
0049: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0049: binding `foos!` in sandbox
|
||||
0051: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0051: stack depth: 3; match depth: 0
|
||||
0053: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0053: at stack index: 2
|
||||
0055: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0055: new locals: foos@0//0|foo!@1//0|foos!@2//0
|
||||
0057: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0057: resolving binding `foo!` in sandbox
|
||||
locals: foos@0//0|foo!@1//0|foos!@2//0
|
||||
0059: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0059: at locals position 1
|
||||
0061: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0061: push_binding 001
|
||||
0063: [->box { [] }<-|fn foo!|fn foos!|fn foo!] (_,_,_,_,_,_,_,_)
|
||||
0063: set_upvalue
|
||||
closing over in foos! at 1: fn foo!
|
||||
0064: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0064: ***calling function foos! stack depth: 3
|
||||
0066: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0066: resolving binding `foos!` in sandbox
|
||||
locals: foos@0//0|foo!@1//0|foos!@2//0
|
||||
0068: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0068: at locals position 2
|
||||
0070: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0070: push_binding 002
|
||||
0072: [->box { [] }<-|fn foo!|fn foos!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0072: ***after 0 args stack depth: 4
|
||||
0074: [->box { [] }<-|fn foo!|fn foos!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0074: call 000
|
||||
=== calling into fn foos!/0 ===
|
||||
0000: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0000: reset_match
|
||||
0001: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0001: ***function clause matching: : ()
|
||||
0003: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0003: match
|
||||
0004: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0004: jump 00000
|
||||
0007: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0007: jump_if_no_match 00042
|
||||
0010: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0010: constant 00000: 4
|
||||
0013: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_)
|
||||
0013: truncate
|
||||
0014: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_)
|
||||
0014: jump 00001
|
||||
0018: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_)
|
||||
0018: duplicate
|
||||
0019: [box { [] }|fn foo!|fn foos!|->4<-|4] (_,_,_,_,_,_,_,_)
|
||||
0019: jump_if_zero 00024
|
||||
0022: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_)
|
||||
0022: ***calling function foo! stack depth: 1
|
||||
0024: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_)
|
||||
0024: resolving binding `foo!` in foos!
|
||||
locals:
|
||||
0026: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_)
|
||||
0026: as enclosing upvalue 0
|
||||
0028: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_)
|
||||
0028: get_upvalue 000
|
||||
0030: [box { [] }|fn foo!|fn foos!|->4<-|fn foo!] (_,_,_,_,_,_,_,_)
|
||||
0030: ***after 0 args stack depth: 2
|
||||
0032: [box { [] }|fn foo!|fn foos!|->4<-|fn foo!] (_,_,_,_,_,_,_,_)
|
||||
0032: tail_call 000
|
||||
=== tail call into fn foo!/0 from foos! ===
|
||||
0000: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0000: reset_match
|
||||
0001: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0001: ***function clause matching: : ()
|
||||
0003: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0003: match
|
||||
0004: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0004: jump 00000
|
||||
0007: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0007: jump_if_no_match 00034
|
||||
0010: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0010: ***calling function update! stack depth: 0
|
||||
0012: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0012: resolving binding `foos` in foo!
|
||||
locals:
|
||||
0014: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0014: as enclosing upvalue 0
|
||||
0016: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0016: get_upvalue 000
|
||||
0018: [box { [] }|fn foo!|fn foos!|->box { [] }<-] (_,_,_,_,_,_,_,_)
|
||||
0018: ***calling function append stack depth: 1
|
||||
0020: [box { [] }|fn foo!|fn foos!|->box { [] }<-] (_,_,_,_,_,_,_,_)
|
||||
0020: nothing
|
||||
0021: [box { [] }|fn foo!|fn foos!|->box { [] }<-|_] (_,_,_,_,_,_,_,_)
|
||||
0021: constant 00000: :foo
|
||||
0024: [box { [] }|fn foo!|fn foos!|->box { [] }<-|_|:foo] (_,_,_,_,_,_,_,_)
|
||||
0024: resolving binding `append` in foo!
|
||||
locals:
|
||||
0026: [box { [] }|fn foo!|fn foos!|->box { [] }<-|_|:foo] (_,_,_,_,_,_,_,_)
|
||||
0026: as enclosing upvalue 1
|
||||
0028: [box { [] }|fn foo!|fn foos!|->box { [] }<-|_|:foo] (_,_,_,_,_,_,_,_)
|
||||
0028: get_upvalue 001
|
||||
0030: [box { [] }|fn foo!|fn foos!|->box { [] }<-|_|:foo|fn append] (_,_,_,_,_,_,_,_)
|
||||
0030: partial 002
|
||||
0032: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
|
||||
0032: resolving binding `update!` in foo!
|
||||
locals:
|
||||
0034: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
|
||||
0034: as enclosing upvalue 2
|
||||
0036: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
|
||||
0036: get_upvalue 002
|
||||
0038: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|fn update!] (_,_,_,_,_,_,_,_)
|
||||
0038: ***after 2 args stack depth: 3
|
||||
0040: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|fn update!] (_,_,_,_,_,_,_,_)
|
||||
0040: tail_call 002
|
||||
=== tail call into fn update!/2 from foo! ===
|
||||
0000: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
|
||||
0000: reset_match
|
||||
0001: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
|
||||
0001: match_depth 001
|
||||
0003: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
|
||||
0003: constant 00000: :box
|
||||
0006: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|:box] (_,_,_,_,_,_,_,_)
|
||||
0006: match_type
|
||||
0007: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
|
||||
0007: jump_if_no_match 00012
|
||||
0010: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
|
||||
0010: match_depth 000
|
||||
0012: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
|
||||
0012: constant 00001: :fn
|
||||
0015: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|:fn] (_,_,_,_,_,_,_,_)
|
||||
0015: match_type
|
||||
0016: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
|
||||
0016: jump_if_no_match 00003
|
||||
0019: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
|
||||
0019: jump 00000
|
||||
0022: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
|
||||
0022: jump_if_no_match 00034
|
||||
0025: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
|
||||
0025: push_binding 000
|
||||
0027: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|box { [] }] (_,_,_,_,_,_,_,_)
|
||||
0027: get_upvalue 000
|
||||
0029: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|box { [] }|fn unbox] (_,_,_,_,_,_,_,_)
|
||||
0029: call 001
|
||||
=== calling into fn unbox/1 ===
|
||||
0000: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_)
|
||||
0000: reset_match
|
||||
0001: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_)
|
||||
0001: match_depth 000
|
||||
0003: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_)
|
||||
0003: constant 00000: :box
|
||||
0006: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|:box] (_,_,_,_,_,_,_,_)
|
||||
0006: match_type
|
||||
0007: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_)
|
||||
0007: jump_if_no_match 00003
|
||||
0010: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_)
|
||||
0010: jump 00000
|
||||
0013: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_)
|
||||
0013: jump_if_no_match 00015
|
||||
0016: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_)
|
||||
0016: get_upvalue 000
|
||||
0018: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|#{:list fn list/base...] (_,_,_,_,_,_,_,_)
|
||||
0018: constant 00001: :unbox
|
||||
0021: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|#{:list fn list/base...|:unbox] (_,_,_,_,_,_,_,_)
|
||||
0021: get_key
|
||||
0022: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|fn unbox/base] (_,_,_,_,_,_,_,_)
|
||||
0022: store
|
||||
0023: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (fn unbox/base,_,_,_,_,_,_,_)
|
||||
0023: push_binding 000
|
||||
0025: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|box { [] }] (fn unbox/base,_,_,_,_,_,_,_)
|
||||
0025: load
|
||||
0026: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|box { [] }|fn unbox/base] (_,_,_,_,_,_,_,_)
|
||||
0026: tail_call 001
|
||||
=== tail call into fn unbox/base/1 from unbox ===
|
||||
0028: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|[]] (_,_,_,_,_,_,_,_)
|
||||
0028: store
|
||||
0029: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] ([],_,_,_,_,_,_,_)
|
||||
0029: pop
|
||||
0030: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial] ([],_,_,_,_,_,_,_)
|
||||
0030: return
|
||||
== returning from fn unbox ==
|
||||
0031: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]] (_,_,_,_,_,_,_,_)
|
||||
0031: reset_match
|
||||
0032: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]] (_,_,_,_,_,_,_,_)
|
||||
0032: match
|
||||
0033: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]] (_,_,_,_,_,_,_,_)
|
||||
0033: panic_if_no_match
|
||||
0034: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]] (_,_,_,_,_,_,_,_)
|
||||
0034: push_binding 002
|
||||
0036: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[]] (_,_,_,_,_,_,_,_)
|
||||
0036: push_binding 001
|
||||
0038: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[]|fn append/partial] (_,_,_,_,_,_,_,_)
|
||||
0038: call 001
|
||||
=== calling into fn append/partial/1 ===
|
||||
0000: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_)
|
||||
0000: reset_match
|
||||
0001: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_)
|
||||
0001: match_depth 001
|
||||
0003: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_)
|
||||
0003: constant 00000: :list
|
||||
0006: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|:list] (_,_,_,_,_,_,_,_)
|
||||
0006: match_type
|
||||
0007: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_)
|
||||
0007: jump_if_no_match 00009
|
||||
0010: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_)
|
||||
0010: match_depth 000
|
||||
0012: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_)
|
||||
0012: match
|
||||
0013: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_)
|
||||
0013: jump_if_no_match 00003
|
||||
0016: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_)
|
||||
0016: jump 00000
|
||||
0019: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_)
|
||||
0019: jump_if_no_match 00018
|
||||
0022: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_)
|
||||
0022: get_upvalue 000
|
||||
0024: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|#{:list fn list/base...] (_,_,_,_,_,_,_,_)
|
||||
0024: constant 00001: :append
|
||||
0027: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|#{:list fn list/base...|:append] (_,_,_,_,_,_,_,_)
|
||||
0027: get_key
|
||||
0028: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|fn append/base] (_,_,_,_,_,_,_,_)
|
||||
0028: store
|
||||
0029: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (fn append/base,_,_,_,_,_,_,_)
|
||||
0029: push_binding 000
|
||||
0031: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|[]] (fn append/base,_,_,_,_,_,_,_)
|
||||
0031: push_binding 001
|
||||
0033: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|[]|:foo] (fn append/base,_,_,_,_,_,_,_)
|
||||
0033: load
|
||||
0034: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|[]|:foo|fn append/base] (_,_,_,_,_,_,_,_)
|
||||
0034: tail_call 002
|
||||
=== tail call into fn append/base/2 from append ===
|
||||
0036: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0036: store
|
||||
0037: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] ([:foo],_,_,_,_,_,_,_)
|
||||
0037: pop_n 002
|
||||
0039: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]] ([:foo],_,_,_,_,_,_,_)
|
||||
0039: return
|
||||
== returning from fn append ==
|
||||
0040: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0040: reset_match
|
||||
0041: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0041: match
|
||||
0042: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0042: panic_if_no_match
|
||||
0043: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0043: push_binding 000
|
||||
0045: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]|box { [] }] (_,_,_,_,_,_,_,_)
|
||||
0045: push_binding 003
|
||||
0047: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]|box { [] }|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0047: get_upvalue 001
|
||||
0049: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]|box { [] }|[:foo]|fn store!] (_,_,_,_,_,_,_,_)
|
||||
0049: tail_call 002
|
||||
=== tail call into fn store!/2 from update! ===
|
||||
0000: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0000: reset_match
|
||||
0001: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0001: match_depth 001
|
||||
0003: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0003: constant 00000: :box
|
||||
0006: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|:box] (_,_,_,_,_,_,_,_)
|
||||
0006: match_type
|
||||
0007: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0007: jump_if_no_match 00009
|
||||
0010: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0010: match_depth 000
|
||||
0012: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0012: match
|
||||
0013: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0013: jump_if_no_match 00003
|
||||
0016: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0016: jump 00000
|
||||
0019: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0019: jump_if_no_match 00023
|
||||
0022: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0022: get_upvalue 000
|
||||
0024: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|#{:list fn list/base...] (_,_,_,_,_,_,_,_)
|
||||
0024: constant 00001: :store!
|
||||
0027: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|#{:list fn list/base...|:store!] (_,_,_,_,_,_,_,_)
|
||||
0027: get_key
|
||||
0028: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|fn store!/base] (_,_,_,_,_,_,_,_)
|
||||
0028: store
|
||||
0029: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (fn store!/base,_,_,_,_,_,_,_)
|
||||
0029: push_binding 000
|
||||
0031: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|box { [] }] (fn store!/base,_,_,_,_,_,_,_)
|
||||
0031: push_binding 001
|
||||
0033: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|box { [] }|[:foo]] (fn store!/base,_,_,_,_,_,_,_)
|
||||
0033: load
|
||||
0034: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|box { [] }|[:foo]|fn store!/base] (_,_,_,_,_,_,_,_)
|
||||
0034: call 002
|
||||
=== calling into fn store!/base/2 ===
|
||||
0036: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0036: pop
|
||||
0037: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0037: push_binding 001
|
||||
0039: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0039: store
|
||||
0040: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]] ([:foo],_,_,_,_,_,_,_)
|
||||
0040: load
|
||||
0041: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0041: store
|
||||
0042: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]] ([:foo],_,_,_,_,_,_,_)
|
||||
0042: pop_n 002
|
||||
0044: [box { [:foo] }|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_)
|
||||
0044: return
|
||||
== returning from fn store! ==
|
||||
0076: [->box { [:foo] }<-|fn foo!|fn foos!|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0076: pop
|
||||
0077: [->box { [:foo] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0077: ***calling function unbox stack depth: 3
|
||||
0079: [->box { [:foo] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0079: resolving binding `foos` in sandbox
|
||||
locals: foos@0//0|foo!@1//0|foos!@2//0
|
||||
0081: [->box { [:foo] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0081: at locals position 0
|
||||
0083: [->box { [:foo] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
|
||||
0083: push_binding 000
|
||||
0085: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }] (_,_,_,_,_,_,_,_)
|
||||
0085: resolving binding `unbox` in sandbox
|
||||
locals: foos@0//0|foo!@1//0|foos!@2//0
|
||||
0087: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }] (_,_,_,_,_,_,_,_)
|
||||
0087: as global
|
||||
0089: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }] (_,_,_,_,_,_,_,_)
|
||||
0089: constant 00004: :unbox
|
||||
0092: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }|:unbox] (_,_,_,_,_,_,_,_)
|
||||
0092: push_global
|
||||
0093: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }|fn unbox] (_,_,_,_,_,_,_,_)
|
||||
0093: ***after 1 args stack depth: 5
|
||||
0095: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }|fn unbox] (_,_,_,_,_,_,_,_)
|
||||
0095: call 001
|
||||
=== calling into fn unbox/1 ===
|
||||
0000: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_)
|
||||
0000: reset_match
|
||||
0001: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_)
|
||||
0001: match_depth 000
|
||||
0003: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_)
|
||||
0003: constant 00000: :box
|
||||
0006: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|:box] (_,_,_,_,_,_,_,_)
|
||||
0006: match_type
|
||||
0007: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_)
|
||||
0007: jump_if_no_match 00003
|
||||
0010: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_)
|
||||
0010: jump 00000
|
||||
0013: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_)
|
||||
0013: jump_if_no_match 00015
|
||||
0016: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_)
|
||||
0016: get_upvalue 000
|
||||
0018: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|#{:list fn list/base...] (_,_,_,_,_,_,_,_)
|
||||
0018: constant 00001: :unbox
|
||||
0021: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|#{:list fn list/base...|:unbox] (_,_,_,_,_,_,_,_)
|
||||
0021: get_key
|
||||
0022: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|fn unbox/base] (_,_,_,_,_,_,_,_)
|
||||
0022: store
|
||||
0023: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (fn unbox/base,_,_,_,_,_,_,_)
|
||||
0023: push_binding 000
|
||||
0025: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|box { [:foo] }] (fn unbox/base,_,_,_,_,_,_,_)
|
||||
0025: load
|
||||
0026: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|box { [:foo] }|fn unbox/base] (_,_,_,_,_,_,_,_)
|
||||
0026: tail_call 001
|
||||
=== tail call into fn unbox/base/1 from unbox ===
|
||||
0028: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0028: store
|
||||
0029: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] ([:foo],_,_,_,_,_,_,_)
|
||||
0029: pop
|
||||
0030: [box { [:foo] }|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_)
|
||||
0030: return
|
||||
== returning from fn unbox ==
|
||||
0097: [->box { [:foo] }<-|fn foo!|fn foos!|[:foo]] (_,_,_,_,_,_,_,_)
|
||||
0097: store
|
||||
0098: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_)
|
||||
0098: leaving scope 0
|
||||
0100: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_)
|
||||
0100: releasing binding foos!@2//0
|
||||
0102: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_)
|
||||
0102: releasing binding foo!@1//0
|
||||
0104: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_)
|
||||
0104: releasing binding foos@0//0
|
||||
0106: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_)
|
||||
0106: ***leaving block before pop stack depth: 3
|
||||
0108: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_)
|
||||
0108: popping back from 3 to 0
|
||||
0110: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_)
|
||||
0110: pop_n 003
|
||||
0112: [] ([:foo],_,_,_,_,_,_,_)
|
||||
0112: load
|
||||
0112: [] (_,_,_,_,_,_,_,_)
|
||||
[:foo]
|
249
scratch/first.txt
Normal file
249
scratch/first.txt
Normal file
|
@ -0,0 +1,249 @@
|
|||
=== vm run: test ===
|
||||
0000: [] (_,_,_,_,_,_,_,_)
|
||||
0000: reset_match
|
||||
0001: [] (_,_,_,_,_,_,_,_)
|
||||
0001: constant 00000: 2
|
||||
0004: [->2<-] (_,_,_,_,_,_,_,_)
|
||||
0004: match
|
||||
0005: [->2<-] (_,_,_,_,_,_,_,_)
|
||||
0005: panic_if_no_match
|
||||
0006: [->2<-] (_,_,_,_,_,_,_,_)
|
||||
0006: push_list
|
||||
0007: [->2<-|[]] (_,_,_,_,_,_,_,_)
|
||||
0007: constant 00001: 1
|
||||
0010: [->2<-|[]|1] (_,_,_,_,_,_,_,_)
|
||||
0010: append_list
|
||||
0011: [->2<-|[1]] (_,_,_,_,_,_,_,_)
|
||||
0011: constant 00000: 2
|
||||
0014: [->2<-|[1]|2] (_,_,_,_,_,_,_,_)
|
||||
0014: append_list
|
||||
0015: [->2<-|[1, 2]] (_,_,_,_,_,_,_,_)
|
||||
0015: constant 00002: 3
|
||||
0018: [->2<-|[1, 2]|3] (_,_,_,_,_,_,_,_)
|
||||
0018: append_list
|
||||
0019: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0019: ***entering loop with stack depth of 2
|
||||
0021: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0021: store_n 001
|
||||
0023: [->2<-] ([1, 2, 3],_,_,_,_,_,_,_)
|
||||
0023: ***after store, stack depth is now 2
|
||||
0025: [->2<-] ([1, 2, 3],_,_,_,_,_,_,_)
|
||||
0025: load
|
||||
0026: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0026: ***after load, stack depth is now 2
|
||||
0028: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0028: reset_match
|
||||
0029: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0029: match_depth 000
|
||||
0031: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0031: match_list 000
|
||||
0033: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0033: jump_if_no_match 00006
|
||||
0042: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0042: jump_if_no_match 00010
|
||||
0055: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0055: reset_match
|
||||
0056: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0056: match_depth 000
|
||||
0058: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0058: match_list 001
|
||||
0060: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0060: jump_if_no_match 00012
|
||||
0075: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0075: jump_if_no_match 00030
|
||||
0108: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0108: reset_match
|
||||
0109: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0109: match_depth 000
|
||||
0111: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0111: match_splatted_list 002
|
||||
0113: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0113: jump_if_no_match 00019
|
||||
0116: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0116: load_splatted_list 002
|
||||
0118: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0118: match_depth 001
|
||||
0120: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0120: match
|
||||
0121: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0121: jump_if_no_match 00010
|
||||
0124: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0124: match_depth 000
|
||||
0126: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0126: match
|
||||
0127: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0127: jump_if_no_match 00004
|
||||
0130: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0130: jump 00002
|
||||
0135: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0135: jump_if_no_match 00068
|
||||
0138: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0138: ***before visiting body, the stack depth is 4
|
||||
0140: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0140: ***calling function eq? stack depth: 4
|
||||
0142: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0142: ***calling function first stack depth: 4
|
||||
0144: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0144: resolving binding `xs` in test
|
||||
0146: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0146: push_binding 003
|
||||
0148: [->2<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0148: resolving binding `first` in test
|
||||
0150: [->2<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0150: constant 00004: :first
|
||||
0153: [->2<-|[1, 2, 3]|1|[2, 3]|[2, 3]|:first] (_,_,_,_,_,_,_,_)
|
||||
0153: push_global
|
||||
0154: [->2<-|[1, 2, 3]|1|[2, 3]|[2, 3]|fn first] (_,_,_,_,_,_,_,_)
|
||||
0154: ***after 1 args stack depth: 6
|
||||
0156: [->2<-|[1, 2, 3]|1|[2, 3]|[2, 3]|fn first] (_,_,_,_,_,_,_,_)
|
||||
0156: call 001
|
||||
=== calling into fn first/1 ===
|
||||
0000: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0000: reset_match
|
||||
0001: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0001: match_depth 000
|
||||
0003: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0003: match_list 000
|
||||
0005: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0005: jump_if_no_match 00006
|
||||
0014: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0014: jump_if_no_match 00003
|
||||
0020: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0020: jump_if_no_match 00005
|
||||
0028: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0028: match_depth 000
|
||||
0030: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0030: constant 00000: :list
|
||||
0033: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|:list] (_,_,_,_,_,_,_,_)
|
||||
0033: match_type
|
||||
0034: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0034: jump_if_no_match 00003
|
||||
0037: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0037: jump 00000
|
||||
0040: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0040: jump_if_no_match 00024
|
||||
0043: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0043: ***accessing keyword: base :first stack depth: 1
|
||||
0045: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0045: resolving binding `base` in first
|
||||
0047: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0047: get_upvalue 000
|
||||
0049: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|#{:rest fn rest/base...] (_,_,_,_,_,_,_,_)
|
||||
0049: constant 00001: :first
|
||||
0052: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|#{:rest fn rest/base...|:first] (_,_,_,_,_,_,_,_)
|
||||
0052: get_key
|
||||
0053: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (_,_,_,_,_,_,_,_)
|
||||
0053: ***after keyword access stack depth: 2
|
||||
0055: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (_,_,_,_,_,_,_,_)
|
||||
0055: stash
|
||||
0056: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (fn first/base,_,_,_,_,_,_,_)
|
||||
0056: pop
|
||||
0057: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (fn first/base,_,_,_,_,_,_,_)
|
||||
0057: resolving binding `xs` in first
|
||||
0059: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (fn first/base,_,_,_,_,_,_,_)
|
||||
0059: push_binding 000
|
||||
0061: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|[2, 3]] (fn first/base,_,_,_,_,_,_,_)
|
||||
0061: load
|
||||
0062: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|[2, 3]|fn first/base] (_,_,_,_,_,_,_,_)
|
||||
0062: tail_call 001
|
||||
=== tail call into fn first/base/1 from first ===
|
||||
0158: [->2<-|[1, 2, 3]|1|[2, 3]|2] (_,_,_,_,_,_,_,_)
|
||||
0158: resolving binding `test` in test
|
||||
0160: [->2<-|[1, 2, 3]|1|[2, 3]|2] (_,_,_,_,_,_,_,_)
|
||||
0160: push_binding 000
|
||||
0162: [->2<-|[1, 2, 3]|1|[2, 3]|2|2] (_,_,_,_,_,_,_,_)
|
||||
0162: resolving binding `eq?` in test
|
||||
0164: [->2<-|[1, 2, 3]|1|[2, 3]|2|2] (_,_,_,_,_,_,_,_)
|
||||
0164: constant 00003: :eq?
|
||||
0167: [->2<-|[1, 2, 3]|1|[2, 3]|2|2|:eq?] (_,_,_,_,_,_,_,_)
|
||||
0167: push_global
|
||||
0168: [->2<-|[1, 2, 3]|1|[2, 3]|2|2|fn eq?] (_,_,_,_,_,_,_,_)
|
||||
0168: ***after 2 args stack depth: 7
|
||||
0170: [->2<-|[1, 2, 3]|1|[2, 3]|2|2|fn eq?] (_,_,_,_,_,_,_,_)
|
||||
0170: call 002
|
||||
=== calling into fn eq?/2 ===
|
||||
0000: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
|
||||
0000: reset_match
|
||||
0001: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
|
||||
0001: match_depth 001
|
||||
0003: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
|
||||
0003: match
|
||||
0004: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
|
||||
0004: jump_if_no_match 00009
|
||||
0007: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
|
||||
0007: match_depth 000
|
||||
0009: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
|
||||
0009: match
|
||||
0010: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
|
||||
0010: jump_if_no_match 00003
|
||||
0013: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
|
||||
0013: jump 00000
|
||||
0016: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
|
||||
0016: jump_if_no_match 00029
|
||||
0019: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
|
||||
0019: ***accessing keyword: base :eq? stack depth: 2
|
||||
0021: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
|
||||
0021: resolving binding `base` in eq?
|
||||
0023: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
|
||||
0023: get_upvalue 000
|
||||
0025: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|#{:rest fn rest/base...] (_,_,_,_,_,_,_,_)
|
||||
0025: constant 00000: :eq?
|
||||
0028: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|#{:rest fn rest/base...|:eq?] (_,_,_,_,_,_,_,_)
|
||||
0028: get_key
|
||||
0029: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|fn eq?/base] (_,_,_,_,_,_,_,_)
|
||||
0029: ***after keyword access stack depth: 3
|
||||
0031: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|fn eq?/base] (_,_,_,_,_,_,_,_)
|
||||
0031: stash
|
||||
0032: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|fn eq?/base] (fn eq?/base,_,_,_,_,_,_,_)
|
||||
0032: pop
|
||||
0033: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (fn eq?/base,_,_,_,_,_,_,_)
|
||||
0033: resolving binding `x` in eq?
|
||||
0035: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (fn eq?/base,_,_,_,_,_,_,_)
|
||||
0035: push_binding 000
|
||||
0037: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|2] (fn eq?/base,_,_,_,_,_,_,_)
|
||||
0037: resolving binding `y` in eq?
|
||||
0039: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|2] (fn eq?/base,_,_,_,_,_,_,_)
|
||||
0039: push_binding 001
|
||||
0041: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|2|2] (fn eq?/base,_,_,_,_,_,_,_)
|
||||
0041: load
|
||||
0042: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|2|2|fn eq?/base] (_,_,_,_,_,_,_,_)
|
||||
0042: tail_call 002
|
||||
=== tail call into fn eq?/base/2 from eq? ===
|
||||
0172: [->2<-|[1, 2, 3]|1|[2, 3]|true] (_,_,_,_,_,_,_,_)
|
||||
0172: jump_if_false 00004
|
||||
0175: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0175: true
|
||||
0176: [->2<-|[1, 2, 3]|1|[2, 3]|true] (_,_,_,_,_,_,_,_)
|
||||
0176: jump 00018
|
||||
0197: [->2<-|[1, 2, 3]|1|[2, 3]|true] (_,_,_,_,_,_,_,_)
|
||||
0197: ***after visiting loop body, the stack depth is 5
|
||||
0199: [->2<-|[1, 2, 3]|1|[2, 3]|true] (_,_,_,_,_,_,_,_)
|
||||
0199: store
|
||||
0200: [->2<-|[1, 2, 3]|1|[2, 3]|_] (true,_,_,_,_,_,_,_)
|
||||
0200: pop
|
||||
0201: [->2<-|[1, 2, 3]|1|[2, 3]] (true,_,_,_,_,_,_,_)
|
||||
0201: pop
|
||||
0202: [->2<-|[1, 2, 3]|1] (true,_,_,_,_,_,_,_)
|
||||
0202: pop
|
||||
0203: [->2<-|[1, 2, 3]] (true,_,_,_,_,_,_,_)
|
||||
0203: jump 00001
|
||||
0207: [->2<-|[1, 2, 3]] (true,_,_,_,_,_,_,_)
|
||||
0207: load
|
||||
0208: [->2<-|[1, 2, 3]|true] (_,_,_,_,_,_,_,_)
|
||||
0208: store
|
||||
0209: [->2<-|[1, 2, 3]|_] (true,_,_,_,_,_,_,_)
|
||||
0209: pop_n 002
|
||||
0211: [->2<-] (true,_,_,_,_,_,_,_)
|
||||
0211: load
|
||||
0212: [->2<-] (_,_,_,_,_,_,_,_)
|
||||
true
|
||||
|
||||
**********
|
||||
**********
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
291
scratch/second.txt
Normal file
291
scratch/second.txt
Normal file
|
@ -0,0 +1,291 @@
|
|||
=== vm run: test ===
|
||||
0000: [] (_,_,_,_,_,_,_,_)
|
||||
0000: reset_match
|
||||
0001: [] (_,_,_,_,_,_,_,_)
|
||||
0001: constant 00000: 4
|
||||
0004: [->4<-] (_,_,_,_,_,_,_,_)
|
||||
0004: match
|
||||
0005: [->4<-] (_,_,_,_,_,_,_,_)
|
||||
0005: panic_if_no_match
|
||||
0006: [->4<-] (_,_,_,_,_,_,_,_)
|
||||
0006: push_list
|
||||
0007: [->4<-|[]] (_,_,_,_,_,_,_,_)
|
||||
0007: constant 00001: 1
|
||||
0010: [->4<-|[]|1] (_,_,_,_,_,_,_,_)
|
||||
0010: append_list
|
||||
0011: [->4<-|[1]] (_,_,_,_,_,_,_,_)
|
||||
0011: constant 00002: 2
|
||||
0014: [->4<-|[1]|2] (_,_,_,_,_,_,_,_)
|
||||
0014: append_list
|
||||
0015: [->4<-|[1, 2]] (_,_,_,_,_,_,_,_)
|
||||
0015: constant 00003: 3
|
||||
0018: [->4<-|[1, 2]|3] (_,_,_,_,_,_,_,_)
|
||||
0018: append_list
|
||||
0019: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0019: ***entering loop with stack depth of 2
|
||||
0021: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0021: store_n 001
|
||||
0023: [->4<-] ([1, 2, 3],_,_,_,_,_,_,_)
|
||||
0023: ***after store, stack depth is now 2
|
||||
0025: [->4<-] ([1, 2, 3],_,_,_,_,_,_,_)
|
||||
0025: load
|
||||
0026: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0026: ***after load, stack depth is now 2
|
||||
0028: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0028: reset_match
|
||||
0029: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0029: match_depth 000
|
||||
0031: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0031: match_list 000
|
||||
0033: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0033: jump_if_no_match 00006
|
||||
0042: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0042: jump_if_no_match 00010
|
||||
0055: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0055: reset_match
|
||||
0056: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0056: match_depth 000
|
||||
0058: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0058: match_list 001
|
||||
0060: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0060: jump_if_no_match 00012
|
||||
0075: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0075: jump_if_no_match 00030
|
||||
0108: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0108: reset_match
|
||||
0109: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0109: match_depth 000
|
||||
0111: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0111: match_splatted_list 002
|
||||
0113: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0113: jump_if_no_match 00019
|
||||
0116: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0116: load_splatted_list 002
|
||||
0118: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0118: match_depth 001
|
||||
0120: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0120: match
|
||||
0121: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0121: jump_if_no_match 00010
|
||||
0124: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0124: match_depth 000
|
||||
0126: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0126: match
|
||||
0127: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0127: jump_if_no_match 00004
|
||||
0130: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0130: jump 00002
|
||||
0135: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0135: jump_if_no_match 00068
|
||||
0138: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0138: ***before visiting body, the stack depth is 4
|
||||
0140: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0140: ***calling function eq? stack depth: 4
|
||||
0142: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0142: ***calling function first stack depth: 4
|
||||
0144: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0144: resolving binding `xs` in test
|
||||
0146: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0146: push_binding 003
|
||||
0148: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0148: resolving binding `first` in test
|
||||
0150: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0150: constant 00005: :first
|
||||
0153: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]|:first] (_,_,_,_,_,_,_,_)
|
||||
0153: push_global
|
||||
0154: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]|fn first] (_,_,_,_,_,_,_,_)
|
||||
0154: ***after 1 args stack depth: 6
|
||||
0156: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]|fn first] (_,_,_,_,_,_,_,_)
|
||||
0156: call 001
|
||||
=== calling into fn first/1 ===
|
||||
0000: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0000: reset_match
|
||||
0001: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0001: match_depth 000
|
||||
0003: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0003: match_list 000
|
||||
0005: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0005: jump_if_no_match 00006
|
||||
0014: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0014: jump_if_no_match 00003
|
||||
0020: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0020: jump_if_no_match 00005
|
||||
0028: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0028: match_depth 000
|
||||
0030: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0030: constant 00000: :list
|
||||
0033: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|:list] (_,_,_,_,_,_,_,_)
|
||||
0033: match_type
|
||||
0034: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0034: jump_if_no_match 00003
|
||||
0037: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0037: jump 00000
|
||||
0040: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0040: jump_if_no_match 00024
|
||||
0043: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0043: ***accessing keyword: base :first stack depth: 1
|
||||
0045: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0045: resolving binding `base` in first
|
||||
0047: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0047: get_upvalue 000
|
||||
0049: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|#{:append fn append/...] (_,_,_,_,_,_,_,_)
|
||||
0049: constant 00001: :first
|
||||
0052: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|#{:append fn append/...|:first] (_,_,_,_,_,_,_,_)
|
||||
0052: get_key?
|
||||
0053: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (_,_,_,_,_,_,_,_)
|
||||
0053: ***after keyword access stack depth: 2
|
||||
0055: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (_,_,_,_,_,_,_,_)
|
||||
0055: stash
|
||||
0056: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (fn first/base,_,_,_,_,_,_,_)
|
||||
0056: pop
|
||||
0057: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (fn first/base,_,_,_,_,_,_,_)
|
||||
0057: resolving binding `xs` in first
|
||||
0059: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (fn first/base,_,_,_,_,_,_,_)
|
||||
0059: push_binding 000
|
||||
0061: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|[2, 3]] (fn first/base,_,_,_,_,_,_,_)
|
||||
0061: load
|
||||
0062: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|[2, 3]|fn first/base] (_,_,_,_,_,_,_,_)
|
||||
0062: tail_call 001
|
||||
=== tail call into fn first/base/1 from first ===
|
||||
0158: [->4<-|[1, 2, 3]|1|[2, 3]|2] (_,_,_,_,_,_,_,_)
|
||||
0158: resolving binding `test` in test
|
||||
0160: [->4<-|[1, 2, 3]|1|[2, 3]|2] (_,_,_,_,_,_,_,_)
|
||||
0160: push_binding 000
|
||||
0162: [->4<-|[1, 2, 3]|1|[2, 3]|2|4] (_,_,_,_,_,_,_,_)
|
||||
0162: resolving binding `eq?` in test
|
||||
0164: [->4<-|[1, 2, 3]|1|[2, 3]|2|4] (_,_,_,_,_,_,_,_)
|
||||
0164: constant 00004: :eq?
|
||||
0167: [->4<-|[1, 2, 3]|1|[2, 3]|2|4|:eq?] (_,_,_,_,_,_,_,_)
|
||||
0167: push_global
|
||||
0168: [->4<-|[1, 2, 3]|1|[2, 3]|2|4|fn eq?] (_,_,_,_,_,_,_,_)
|
||||
0168: ***after 2 args stack depth: 7
|
||||
0170: [->4<-|[1, 2, 3]|1|[2, 3]|2|4|fn eq?] (_,_,_,_,_,_,_,_)
|
||||
0170: call 002
|
||||
=== calling into fn eq?/2 ===
|
||||
0000: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
|
||||
0000: reset_match
|
||||
0001: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
|
||||
0001: match_depth 001
|
||||
0003: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
|
||||
0003: match
|
||||
0004: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
|
||||
0004: jump_if_no_match 00009
|
||||
0007: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
|
||||
0007: match_depth 000
|
||||
0009: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
|
||||
0009: match
|
||||
0010: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
|
||||
0010: jump_if_no_match 00003
|
||||
0013: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
|
||||
0013: jump 00000
|
||||
0016: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
|
||||
0016: jump_if_no_match 00029
|
||||
0019: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
|
||||
0019: ***accessing keyword: base :eq? stack depth: 2
|
||||
0021: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
|
||||
0021: resolving binding `base` in eq?
|
||||
0023: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
|
||||
0023: get_upvalue 000
|
||||
0025: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|#{:append fn append/...] (_,_,_,_,_,_,_,_)
|
||||
0025: constant 00000: :eq?
|
||||
0028: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|#{:append fn append/...|:eq?] (_,_,_,_,_,_,_,_)
|
||||
0028: get_key
|
||||
0029: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|fn eq?/base] (_,_,_,_,_,_,_,_)
|
||||
0029: ***after keyword access stack depth: 3
|
||||
0031: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|fn eq?/base] (_,_,_,_,_,_,_,_)
|
||||
0031: stash
|
||||
0032: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|fn eq?/base] (fn eq?/base,_,_,_,_,_,_,_)
|
||||
0032: pop
|
||||
0033: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (fn eq?/base,_,_,_,_,_,_,_)
|
||||
0033: resolving binding `x` in eq?
|
||||
0035: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (fn eq?/base,_,_,_,_,_,_,_)
|
||||
0035: push_binding 000
|
||||
0037: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|2] (fn eq?/base,_,_,_,_,_,_,_)
|
||||
0037: resolving binding `y` in eq?
|
||||
0039: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|2] (fn eq?/base,_,_,_,_,_,_,_)
|
||||
0039: push_binding 001
|
||||
0041: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|2|4] (fn eq?/base,_,_,_,_,_,_,_)
|
||||
0041: load
|
||||
0042: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|2|4|fn eq?/base] (_,_,_,_,_,_,_,_)
|
||||
0042: tail_call 002
|
||||
=== tail call into fn eq?/base/2 from eq? ===
|
||||
0172: [->4<-|[1, 2, 3]|1|[2, 3]|false] (_,_,_,_,_,_,_,_)
|
||||
0172: jump_if_false 00004
|
||||
0179: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0179: before visiting recur args the compiler thinks the stack depth is 5
|
||||
0181: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0181: recur arg: 0
|
||||
0183: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0183: resolving binding `xs` in test
|
||||
0185: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0185: push_binding 003
|
||||
0187: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0187: after visiting recur args the compiler thinks the stack depth is 6
|
||||
0189: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_)
|
||||
0189: store_n 001
|
||||
0191: [->4<-|[1, 2, 3]|1|[2, 3]] ([2, 3],_,_,_,_,_,_,_)
|
||||
0191: pop_n 004
|
||||
0193: [] ([2, 3],_,_,_,_,_,_,_)
|
||||
0193: load
|
||||
0194: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0194: jump_back 00168
|
||||
0026: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0026: ***after load, stack depth is now 2
|
||||
0028: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0028: reset_match
|
||||
0029: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0029: match_depth 000
|
||||
0031: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0031: match_list 000
|
||||
0033: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0033: jump_if_no_match 00006
|
||||
0042: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0042: jump_if_no_match 00010
|
||||
0055: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0055: reset_match
|
||||
0056: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0056: match_depth 000
|
||||
0058: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0058: match_list 001
|
||||
0060: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0060: jump_if_no_match 00012
|
||||
0075: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0075: jump_if_no_match 00030
|
||||
0108: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0108: reset_match
|
||||
0109: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0109: match_depth 000
|
||||
0111: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0111: match_splatted_list 002
|
||||
0113: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0113: jump_if_no_match 00019
|
||||
0116: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
|
||||
0116: load_splatted_list 002
|
||||
0118: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
|
||||
0118: match_depth 001
|
||||
0120: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
|
||||
0120: match
|
||||
0121: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
|
||||
0121: jump_if_no_match 00010
|
||||
0124: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
|
||||
0124: match_depth 000
|
||||
0126: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
|
||||
0126: match
|
||||
0127: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
|
||||
0127: jump_if_no_match 00004
|
||||
0130: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
|
||||
0130: jump 00002
|
||||
0135: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
|
||||
0135: jump_if_no_match 00068
|
||||
0138: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
|
||||
0138: ***before visiting body, the stack depth is 4
|
||||
0140: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
|
||||
0140: ***calling function eq? stack depth: 4
|
||||
0142: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
|
||||
0142: ***calling function first stack depth: 4
|
||||
0144: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
|
||||
0144: resolving binding `xs` in test
|
||||
0146: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
|
||||
0146: push_binding 003
|
||||
thread 'main' panicked at src/vm.rs:313:51:
|
||||
index out of bounds: the len is 3 but the index is 3
|
473
src/ast.rs
473
src/ast.rs
|
@ -1,473 +0,0 @@
|
|||
use crate::spans::*;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum StringPart {
|
||||
Data(String),
|
||||
Word(&'static str),
|
||||
Inline(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for StringPart {
|
||||
fn fmt(self: &StringPart, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let rep = match self {
|
||||
StringPart::Word(s) => format!("{{{s}}}"),
|
||||
StringPart::Data(s) => s.to_string(),
|
||||
StringPart::Inline(s) => s.to_string(),
|
||||
};
|
||||
write!(f, "{}", rep)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Ast {
|
||||
// a special Error node
|
||||
// may come in handy?
|
||||
Error,
|
||||
|
||||
And,
|
||||
Or,
|
||||
|
||||
// expression nodes
|
||||
Placeholder,
|
||||
Nil,
|
||||
Boolean(bool),
|
||||
Number(f64),
|
||||
Keyword(&'static str),
|
||||
Method(&'static str, Box<Spanned<Self>>),
|
||||
Word(&'static str),
|
||||
String(&'static str),
|
||||
Interpolated(Vec<Spanned<StringPart>>),
|
||||
Block(Vec<Spanned<Self>>),
|
||||
If(Box<Spanned<Self>>, Box<Spanned<Self>>, Box<Spanned<Self>>),
|
||||
Tuple(Vec<Spanned<Self>>),
|
||||
Arguments(Vec<Spanned<Self>>),
|
||||
List(Vec<Spanned<Self>>),
|
||||
Dict(Vec<Spanned<Self>>),
|
||||
Let(Box<Spanned<Self>>, Box<Spanned<Self>>),
|
||||
LBox(&'static str, Box<Spanned<Self>>),
|
||||
Synthetic(Box<Spanned<Self>>, Box<Spanned<Self>>, Vec<Spanned<Self>>),
|
||||
When(Vec<Spanned<Self>>),
|
||||
WhenClause(Box<Spanned<Self>>, Box<Spanned<Self>>),
|
||||
Match(Box<Spanned<Self>>, Vec<Spanned<Self>>),
|
||||
Receive(Vec<Spanned<Self>>),
|
||||
Spawn(Box<Spanned<Self>>),
|
||||
MatchClause(
|
||||
Box<Spanned<Self>>,
|
||||
Box<Option<Spanned<Self>>>,
|
||||
Box<Spanned<Self>>,
|
||||
),
|
||||
Fn(&'static str, Box<Spanned<Ast>>, Option<&'static str>),
|
||||
FnBody(Vec<Spanned<Ast>>),
|
||||
FnDeclaration(&'static str),
|
||||
Panic(Box<Spanned<Self>>),
|
||||
Do(Vec<Spanned<Self>>),
|
||||
Repeat(Box<Spanned<Self>>, Box<Spanned<Self>>),
|
||||
Splat(&'static str),
|
||||
StringPair(&'static str, Box<Spanned<Self>>),
|
||||
KeywordPair(&'static str, Box<Spanned<Self>>),
|
||||
Loop(Box<Spanned<Self>>, Vec<Spanned<Self>>),
|
||||
Recur(Vec<Spanned<Self>>),
|
||||
|
||||
// pattern nodes
|
||||
NilPattern,
|
||||
BooleanPattern(bool),
|
||||
NumberPattern(f64),
|
||||
StringPattern(&'static str),
|
||||
InterpolatedPattern(Vec<Spanned<StringPart>>),
|
||||
KeywordPattern(&'static str),
|
||||
WordPattern(&'static str),
|
||||
AsPattern(&'static str, &'static str),
|
||||
Splattern(Box<Spanned<Self>>),
|
||||
PlaceholderPattern,
|
||||
TuplePattern(Vec<Spanned<Self>>),
|
||||
ListPattern(Vec<Spanned<Self>>),
|
||||
StrPairPattern(&'static str, Box<Spanned<Self>>),
|
||||
KeyPairPattern(&'static str, Box<Spanned<Self>>),
|
||||
DictPattern(Vec<Spanned<Self>>),
|
||||
}
|
||||
|
||||
impl Ast {
|
||||
pub fn show(&self) -> String {
|
||||
use Ast::*;
|
||||
match self {
|
||||
And => "and".to_string(),
|
||||
Or => "or".to_string(),
|
||||
Error => unreachable!(),
|
||||
Nil | NilPattern => "nil".to_string(),
|
||||
String(s) | StringPattern(s) => format!("\"{s}\""),
|
||||
Interpolated(strs) | InterpolatedPattern(strs) => {
|
||||
let mut out = "".to_string();
|
||||
out = format!("\"{out}");
|
||||
for (part, _) in strs {
|
||||
out = format!("{out}{part}");
|
||||
}
|
||||
format!("{out}\"")
|
||||
}
|
||||
Boolean(b) | BooleanPattern(b) => b.to_string(),
|
||||
Number(n) | NumberPattern(n) => n.to_string(),
|
||||
Keyword(k) | KeywordPattern(k) => format!(":{k}"),
|
||||
Method(m, args) => format!("::{m} {}", args.0),
|
||||
Word(w) | WordPattern(w) => w.to_string(),
|
||||
Block(lines) => {
|
||||
let mut out = "{\n".to_string();
|
||||
for (line, _) in lines {
|
||||
out = format!("{out}\n {}", line.show());
|
||||
}
|
||||
format!("{out}\n}}")
|
||||
}
|
||||
If(cond, then, r#else) => format!(
|
||||
"if {}\n then {}\n else {}",
|
||||
cond.0.show(),
|
||||
then.0.show(),
|
||||
r#else.0.show()
|
||||
),
|
||||
Let(pattern, expression) => {
|
||||
format!("let {} = {}", pattern.0.show(), expression.0.show())
|
||||
}
|
||||
Dict(entries) | DictPattern(entries) => {
|
||||
format!(
|
||||
"#{{{}}}",
|
||||
entries
|
||||
.iter()
|
||||
.map(|(pair, _)| pair.show())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
List(members) | ListPattern(members) => format!(
|
||||
"[{}]",
|
||||
members
|
||||
.iter()
|
||||
.map(|(member, _)| member.show())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
Arguments(members) => format!(
|
||||
"({})",
|
||||
members
|
||||
.iter()
|
||||
.map(|(member, _)| member.show())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
Tuple(members) | TuplePattern(members) => format!(
|
||||
"({})",
|
||||
members
|
||||
.iter()
|
||||
.map(|(member, _)| member.show())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
Synthetic(root, first, rest) => format!(
|
||||
"{} {} {}",
|
||||
root.0.show(),
|
||||
first.0.show(),
|
||||
rest.iter()
|
||||
.map(|(term, _)| term.show())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
),
|
||||
When(clauses) | Receive(clauses) => format!(
|
||||
"when {{\n {}\n}}",
|
||||
clauses
|
||||
.iter()
|
||||
.map(|(clause, _)| clause.show())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n ")
|
||||
),
|
||||
Placeholder | PlaceholderPattern => "_".to_string(),
|
||||
LBox(name, rhs) => format!("box {name} = {}", rhs.0.show()),
|
||||
Match(scrutinee, clauses) => format!(
|
||||
"match {} with {{\n {}\n}}",
|
||||
scrutinee.0.show(),
|
||||
clauses
|
||||
.iter()
|
||||
.map(|(clause, _)| clause.show())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n ")
|
||||
),
|
||||
FnBody(clauses) => clauses
|
||||
.iter()
|
||||
.map(|(clause, _)| clause.show())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n "),
|
||||
Fn(name, body, doc) => {
|
||||
let mut out = format!("fn {name} {{\n");
|
||||
if let Some(doc) = doc {
|
||||
out = format!("{out} {doc}\n");
|
||||
}
|
||||
format!("{out} {}\n}}", body.0.show())
|
||||
}
|
||||
FnDeclaration(name) => format!("fn {name}"),
|
||||
Panic(expr) => format!("panic! {}", expr.0.show()),
|
||||
Do(terms) => {
|
||||
format!(
|
||||
"do {}",
|
||||
terms
|
||||
.iter()
|
||||
.map(|(term, _)| term.show())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" > ")
|
||||
)
|
||||
}
|
||||
Spawn(body) => format!("spawn! {}", body.0.show()),
|
||||
Repeat(times, body) => format!("repeat {} {{\n{}\n}}", times.0.show(), body.0.show()),
|
||||
Splat(word) => format!("...{}", word),
|
||||
Splattern(pattern) => format!("...{}", pattern.0.show()),
|
||||
AsPattern(word, type_keyword) => format!("{word} as :{type_keyword}"),
|
||||
KeywordPair(key, value) | KeyPairPattern(key, value) => {
|
||||
format!(":{key} {}", value.0.show())
|
||||
}
|
||||
StringPair(key, value) | StrPairPattern(key, value) => {
|
||||
format!("\"{key}\" {}", value.0.show())
|
||||
}
|
||||
Loop(init, body) => format!(
|
||||
"loop {} with {{\n {}\n}}",
|
||||
init.0.show(),
|
||||
body.iter()
|
||||
.map(|(clause, _)| clause.show())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n ")
|
||||
),
|
||||
Recur(args) => format!(
|
||||
"recur ({})",
|
||||
args.iter()
|
||||
.map(|(arg, _)| arg.show())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
MatchClause(pattern, guard, body) => {
|
||||
let mut out = pattern.0.show();
|
||||
if let Some(guard) = guard.as_ref() {
|
||||
out = format!("{out} if {}", guard.0.show());
|
||||
}
|
||||
format!("{out} -> {}", body.0.show())
|
||||
}
|
||||
WhenClause(cond, body) => format!("{} -> {}", cond.0.show(), body.0.show()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Ast {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use Ast::*;
|
||||
match self {
|
||||
And => write!(f, "And"),
|
||||
Or => write!(f, "Or"),
|
||||
Error => write!(f, "Error"),
|
||||
Nil => write!(f, "nil"),
|
||||
String(s) => write!(f, "String: \"{}\"", s),
|
||||
Interpolated(strs) => {
|
||||
write!(
|
||||
f,
|
||||
"Interpolated: \"{}\"",
|
||||
strs.iter()
|
||||
.map(|(s, _)| s.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
)
|
||||
}
|
||||
Boolean(b) => write!(f, "Boolean: {}", b),
|
||||
Number(n) => write!(f, "Number: {}", n),
|
||||
Keyword(k) => write!(f, "Keyword: :{}", k),
|
||||
Method(m, args) => write!(f, "Method: ::{m} ({})", args.0),
|
||||
Word(w) => write!(f, "Word: {}", w),
|
||||
Block(b) => write!(
|
||||
f,
|
||||
"Block: <{}>",
|
||||
b.iter()
|
||||
.map(|(line, _)| line.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
If(cond, then_branch, else_branch) => write!(
|
||||
f,
|
||||
"If: {} Then: {} Else: {}",
|
||||
cond.0, then_branch.0, else_branch.0
|
||||
),
|
||||
Let(pattern, expression) => {
|
||||
write!(f, "Let: {} = {}", pattern.0, expression.0)
|
||||
}
|
||||
Dict(entries) => write!(
|
||||
f,
|
||||
"#{{{}}}",
|
||||
entries
|
||||
.iter()
|
||||
.map(|pair| pair.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
List(l) => write!(
|
||||
f,
|
||||
"List: [{}]",
|
||||
l.iter()
|
||||
.map(|(line, _)| line.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
Arguments(a) => write!(
|
||||
f,
|
||||
"Arguments: ({})",
|
||||
a.iter()
|
||||
.map(|(line, _)| line.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
Tuple(t) => write!(
|
||||
f,
|
||||
"Tuple: ({})",
|
||||
t.iter()
|
||||
.map(|(line, _)| line.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
Synthetic(root, first, rest) => write!(
|
||||
f,
|
||||
"Synth: [{}, {}, {}]",
|
||||
root.0,
|
||||
first.0,
|
||||
rest.iter()
|
||||
.map(|(term, _)| term.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
When(clauses) | Receive(clauses) => write!(
|
||||
f,
|
||||
"When: [{}]",
|
||||
clauses
|
||||
.iter()
|
||||
.map(|clause| clause.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
Placeholder => write!(f, "Placeholder"),
|
||||
LBox(_name, _rhs) => todo!(),
|
||||
Match(value, clauses) => {
|
||||
write!(
|
||||
f,
|
||||
"match: {} with {}",
|
||||
&value.0.to_string(),
|
||||
clauses
|
||||
.iter()
|
||||
.map(|clause| clause.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
)
|
||||
}
|
||||
FnBody(clauses) => {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
clauses
|
||||
.iter()
|
||||
.map(|clause| clause.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
)
|
||||
}
|
||||
Fn(name, body, ..) => {
|
||||
write!(f, "fn: {name}\n{}", body.0)
|
||||
}
|
||||
FnDeclaration(_name) => todo!(),
|
||||
Panic(_expr) => todo!(),
|
||||
Do(terms) => {
|
||||
write!(
|
||||
f,
|
||||
"do: {}",
|
||||
terms
|
||||
.iter()
|
||||
.map(|(term, _)| term.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" > ")
|
||||
)
|
||||
}
|
||||
Spawn(body) => write!(f, "spawn: {}", body.0),
|
||||
Repeat(_times, _body) => todo!(),
|
||||
Splat(word) => {
|
||||
write!(f, "splat: {}", word)
|
||||
}
|
||||
KeywordPair(k, v) | KeyPairPattern(k, v) => {
|
||||
write!(f, "key_pair: {} {}", k, v.0)
|
||||
}
|
||||
StringPair(k, v) | StrPairPattern(k, v) => {
|
||||
write!(f, "str_pair: {k} {}", v.0)
|
||||
}
|
||||
Loop(init, body) => {
|
||||
write!(
|
||||
f,
|
||||
"loop: {} with {}",
|
||||
init.0,
|
||||
body.iter()
|
||||
.map(|clause| clause.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
)
|
||||
}
|
||||
Recur(args) => {
|
||||
write!(
|
||||
f,
|
||||
"recur: {}",
|
||||
args.iter()
|
||||
.map(|(arg, _)| arg.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
MatchClause(pattern, guard, body) => {
|
||||
write!(
|
||||
f,
|
||||
"match clause: {} if {:?} -> {}",
|
||||
pattern.0, guard, body.0
|
||||
)
|
||||
}
|
||||
WhenClause(cond, body) => {
|
||||
write!(f, "when clause: {} -> {}", cond.0, body.0)
|
||||
}
|
||||
|
||||
NilPattern => write!(f, "nil"),
|
||||
BooleanPattern(b) => write!(f, "{}", b),
|
||||
NumberPattern(n) => write!(f, "{}", n),
|
||||
StringPattern(s) => write!(f, "{}", s),
|
||||
KeywordPattern(k) => write!(f, ":{}", k),
|
||||
WordPattern(w) => write!(f, "{}", w),
|
||||
AsPattern(w, t) => write!(f, "{} as :{}", w, t),
|
||||
Splattern(p) => write!(f, "...{}", p.0),
|
||||
PlaceholderPattern => write!(f, "_"),
|
||||
TuplePattern(t) => write!(
|
||||
f,
|
||||
"({})",
|
||||
t.iter()
|
||||
.map(|x| x.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
ListPattern(l) => write!(
|
||||
f,
|
||||
"({})",
|
||||
l.iter()
|
||||
.map(|x| x.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
DictPattern(entries) => write!(
|
||||
f,
|
||||
"#{{{}}}",
|
||||
entries
|
||||
.iter()
|
||||
.map(|(pair, _)| pair.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
InterpolatedPattern(strprts) => write!(
|
||||
f,
|
||||
"interpolated: \"{}\"",
|
||||
strprts
|
||||
.iter()
|
||||
.map(|part| part.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
442
src/base.rs
442
src/base.rs
|
@ -1,9 +1,9 @@
|
|||
use crate::js::*;
|
||||
use crate::value::*;
|
||||
use imbl::*;
|
||||
use ran::ran_f64;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Clone, Debug, Hash)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum BaseFn {
|
||||
Nullary(&'static str, fn() -> Value),
|
||||
Unary(&'static str, fn(&Value) -> Value),
|
||||
|
@ -52,28 +52,15 @@ pub fn store(b: &Value, val: &Value) -> Value {
|
|||
// TODO: do better than returning just the docstr
|
||||
// name, patterns, AND docstring
|
||||
pub fn doc(f: &Value) -> Value {
|
||||
let f = f.as_fn();
|
||||
let name = f.name();
|
||||
let patterns = f.patterns();
|
||||
let docstr = f.doc();
|
||||
Value::from_string(format!(
|
||||
"{name}\n{}\n{}",
|
||||
patterns.stringify(),
|
||||
docstr.stringify()
|
||||
))
|
||||
match f {
|
||||
Value::Fn(f) => f.as_ref().doc(),
|
||||
_ => Value::Interned("no documentation found"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assoc(dict: &Value, key: &Value, value: &Value) -> Value {
|
||||
match (dict, key) {
|
||||
(Value::Dict(d), Value::Keyword(k)) => {
|
||||
Value::Dict(Box::new(d.update(Key::Keyword(k), value.clone())))
|
||||
}
|
||||
(Value::Dict(d), Value::Interned(k)) => {
|
||||
Value::Dict(Box::new(d.update(Key::Interned(k), value.clone())))
|
||||
}
|
||||
(Value::Dict(d), Value::String(s)) => {
|
||||
Value::Dict(Box::new(d.update(Key::String(s.clone()), value.clone())))
|
||||
}
|
||||
(Value::Dict(d), Value::Keyword(k)) => Value::Dict(Box::new(d.update(k, value.clone()))),
|
||||
_ => unreachable!("internal Ludus error calling assoc with ({dict}, {key}, {value})"),
|
||||
}
|
||||
}
|
||||
|
@ -88,9 +75,18 @@ pub fn r#bool(x: &Value) -> Value {
|
|||
pub fn chars(x: &Value) -> Value {
|
||||
match x {
|
||||
Value::Interned(s) => {
|
||||
let chars = s.chars();
|
||||
|
||||
let mut charlist = vector![];
|
||||
for char in s.chars() {
|
||||
charlist.push_back(Value::from_string(char.to_string()))
|
||||
for char in chars {
|
||||
if char.is_ascii() {
|
||||
charlist.push_back(Value::String(Rc::new(char.to_string())))
|
||||
} else {
|
||||
return Value::Tuple(Rc::new(vec![
|
||||
Value::Keyword("err"),
|
||||
Value::String(Rc::new(format!("{char} is not an ascii character"))),
|
||||
]));
|
||||
}
|
||||
}
|
||||
Value::Tuple(Rc::new(vec![
|
||||
Value::Keyword("ok"),
|
||||
|
@ -98,9 +94,18 @@ pub fn chars(x: &Value) -> Value {
|
|||
]))
|
||||
}
|
||||
Value::String(s) => {
|
||||
let chars = s.chars();
|
||||
|
||||
let mut charlist = vector![];
|
||||
for char in s.chars() {
|
||||
charlist.push_back(Value::from_string(char.to_string()))
|
||||
for char in chars {
|
||||
if char.is_ascii() {
|
||||
charlist.push_back(Value::String(Rc::new(char.to_string())))
|
||||
} else {
|
||||
return Value::Tuple(Rc::new(vec![
|
||||
Value::Keyword("err"),
|
||||
Value::String(Rc::new(format!("{char} is not an ascii character"))),
|
||||
]));
|
||||
}
|
||||
}
|
||||
Value::Tuple(Rc::new(vec![
|
||||
Value::Keyword("ok"),
|
||||
|
@ -111,44 +116,6 @@ pub fn chars(x: &Value) -> Value {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn words(str: &Value) -> Value {
|
||||
// console_log!("wordsing words");
|
||||
let str = Value::as_string(str);
|
||||
let mut words = Vector::new();
|
||||
let mut word = String::new();
|
||||
for char in str.chars() {
|
||||
if char.is_alphanumeric() || char == '\'' {
|
||||
word.push(char)
|
||||
} else if !word.is_empty() {
|
||||
words.push_back(Value::from_string(word));
|
||||
word = String::new()
|
||||
}
|
||||
}
|
||||
// console_log!("words gathered into vector; returning to ludus");
|
||||
Value::list(words)
|
||||
}
|
||||
|
||||
pub fn index_of(scrutinee: &Value, target: &Value) -> Value {
|
||||
let scrutinee = scrutinee.as_list();
|
||||
for (i, value) in scrutinee.iter().enumerate() {
|
||||
if value == target {
|
||||
return Value::from_usize(i);
|
||||
}
|
||||
}
|
||||
Value::Nil
|
||||
}
|
||||
|
||||
pub fn indices_of(scrutinee: &Value, target: &Value) -> Value {
|
||||
let scrutinee = scrutinee.as_list();
|
||||
let mut indices = Vector::new();
|
||||
for (i, value) in scrutinee.iter().enumerate() {
|
||||
if value == target {
|
||||
indices.push_back(Value::from_usize(i))
|
||||
}
|
||||
}
|
||||
Value::list(indices)
|
||||
}
|
||||
|
||||
// TODO: figure out how to get to opportunistic mutation here
|
||||
pub fn concat(x: &Value, y: &Value) -> Value {
|
||||
match (x, y) {
|
||||
|
@ -177,11 +144,17 @@ pub fn append(x: &Value, y: &Value) -> Value {
|
|||
}
|
||||
|
||||
pub fn dec(x: &Value) -> Value {
|
||||
Value::from_f64(x.as_f64() - 1.0)
|
||||
match x {
|
||||
Value::Number(n) => Value::Number(n - 1.0),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inc(x: &Value) -> Value {
|
||||
Value::from_f64(x.as_f64() + 1.0)
|
||||
match x {
|
||||
Value::Number(n) => Value::Number(n + 1.0),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn div(x: &Value, y: &Value) -> Value {
|
||||
|
@ -198,27 +171,11 @@ pub fn mult(x: &Value, y: &Value) -> Value {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn pow(x: &Value, y: &Value) -> Value {
|
||||
let x = x.as_f64();
|
||||
let y = y.as_f64();
|
||||
Value::from_f64(x.powf(y))
|
||||
}
|
||||
|
||||
pub fn dissoc(dict: &Value, key: &Value) -> Value {
|
||||
match (dict, key) {
|
||||
(Value::Dict(dict), Value::Keyword(key)) => {
|
||||
let mut new = dict.clone();
|
||||
new.remove(&Key::Keyword(key));
|
||||
Value::Dict(new)
|
||||
}
|
||||
(Value::Dict(dict), Value::Interned(key)) => {
|
||||
let mut new = dict.clone();
|
||||
new.remove(&Key::Interned(key));
|
||||
Value::Dict(new)
|
||||
}
|
||||
(Value::Dict(dict), Value::String(key)) => {
|
||||
let mut new = dict.clone();
|
||||
new.remove(&Key::String(key.clone()));
|
||||
new.remove(key);
|
||||
Value::Dict(new)
|
||||
}
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
|
@ -235,14 +192,6 @@ pub fn first(ordered: &Value) -> Value {
|
|||
Some(n) => n.clone(),
|
||||
None => Value::Nil,
|
||||
},
|
||||
Value::String(s) => match s.chars().next() {
|
||||
Some(char) => Value::from_string(char.to_string()),
|
||||
None => Value::Nil,
|
||||
},
|
||||
Value::Interned(s) => match s.chars().next() {
|
||||
Some(char) => Value::from_string(char.to_string()),
|
||||
None => Value::Nil,
|
||||
},
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
@ -252,48 +201,26 @@ pub fn first(ordered: &Value) -> Value {
|
|||
pub fn at(ordered: &Value, i: &Value) -> Value {
|
||||
match (ordered, i) {
|
||||
(Value::List(list), Value::Number(n)) => {
|
||||
let i = f64::from(*n) as usize;
|
||||
let i = *n as usize;
|
||||
match list.get(i) {
|
||||
Some(n) => n.clone(),
|
||||
None => Value::Nil,
|
||||
}
|
||||
}
|
||||
(Value::Tuple(tuple), Value::Number(n)) => {
|
||||
let i = f64::from(*n) as usize;
|
||||
let i = *n as usize;
|
||||
match tuple.get(i) {
|
||||
Some(n) => n.clone(),
|
||||
None => Value::Nil,
|
||||
}
|
||||
}
|
||||
(Value::String(s), Value::Number(n)) => {
|
||||
let i = f64::from(*n) as usize;
|
||||
match s.chars().nth(i) {
|
||||
Some(n) => Value::from_string(n.to_string()),
|
||||
None => Value::Nil,
|
||||
}
|
||||
}
|
||||
(Value::Interned(s), Value::Number(n)) => {
|
||||
let i = f64::from(*n) as usize;
|
||||
match s.chars().nth(i) {
|
||||
Some(n) => Value::from_string(n.to_string()),
|
||||
None => Value::Nil,
|
||||
}
|
||||
}
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(dict: &Value, key: &Value) -> Value {
|
||||
match (dict, key) {
|
||||
(Value::Dict(dict), Value::Keyword(key)) => match dict.get(&Key::Keyword(key)) {
|
||||
Some(x) => x.clone(),
|
||||
None => Value::Nil,
|
||||
},
|
||||
(Value::Dict(dict), Value::Interned(key)) => match dict.get(&Key::Interned(key)) {
|
||||
Some(x) => x.clone(),
|
||||
None => Value::Nil,
|
||||
},
|
||||
(Value::Dict(dict), Value::String(key)) => match dict.get(&Key::String(key.clone())) {
|
||||
(Value::Dict(dict), Value::Keyword(key)) => match dict.get(key) {
|
||||
Some(x) => x.clone(),
|
||||
None => Value::Nil,
|
||||
},
|
||||
|
@ -311,18 +238,11 @@ pub fn last(ordered: &Value) -> Value {
|
|||
Some(x) => x.clone(),
|
||||
None => Value::Nil,
|
||||
},
|
||||
Value::String(s) => match s.chars().last() {
|
||||
Some(char) => Value::from_string(char.to_string()),
|
||||
None => Value::Nil,
|
||||
},
|
||||
Value::Interned(s) => match s.chars().last() {
|
||||
Some(char) => Value::from_string(char.to_string()),
|
||||
None => Value::Nil,
|
||||
},
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: fix this: x is a list of all the args passed to Ludus's print!
|
||||
pub fn print(x: &Value) -> Value {
|
||||
let Value::List(args) = x else {
|
||||
unreachable!("internal Ludus error")
|
||||
|
@ -332,7 +252,7 @@ pub fn print(x: &Value) -> Value {
|
|||
.map(|val| format!("{val}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
console_log!("{out}");
|
||||
println!("{out}");
|
||||
Value::Keyword("ok")
|
||||
}
|
||||
|
||||
|
@ -356,11 +276,11 @@ pub fn rest(ordered: &Value) -> Value {
|
|||
|
||||
pub fn count(coll: &Value) -> Value {
|
||||
match coll {
|
||||
Value::Dict(d) => Value::from_usize(d.len()),
|
||||
Value::List(l) => Value::from_usize(l.len()),
|
||||
Value::Tuple(t) => Value::from_usize(t.len()),
|
||||
Value::String(s) => Value::from_usize(s.len()),
|
||||
Value::Interned(s) => Value::from_usize(s.len()),
|
||||
Value::Dict(d) => Value::Number(d.len() as f64),
|
||||
Value::List(l) => Value::Number(l.len() as f64),
|
||||
Value::Tuple(t) => Value::Number(t.len() as f64),
|
||||
Value::String(s) => Value::Number(s.len() as f64),
|
||||
Value::Interned(s) => Value::Number(s.len() as f64),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
@ -368,11 +288,11 @@ pub fn count(coll: &Value) -> Value {
|
|||
pub fn range(start: &Value, end: &Value) -> Value {
|
||||
match (start, end) {
|
||||
(Value::Number(start), Value::Number(end)) => {
|
||||
let start = f64::from(*start) as isize;
|
||||
let end = f64::from(*end) as isize;
|
||||
let start = *start as isize;
|
||||
let end = *end as isize;
|
||||
let mut range = Vector::new();
|
||||
for n in start..end {
|
||||
range.push_back(Value::from_usize(n as usize))
|
||||
range.push_back(Value::Number(n as f64))
|
||||
}
|
||||
Value::List(Box::new(range))
|
||||
}
|
||||
|
@ -384,14 +304,14 @@ pub fn slice(ordered: &Value, start: &Value, end: &Value) -> Value {
|
|||
match (ordered, start, end) {
|
||||
(Value::List(list), Value::Number(start), Value::Number(end)) => {
|
||||
let mut newlist = list.clone();
|
||||
let start = std::cmp::max(f64::from(*start) as usize, 0);
|
||||
let end = std::cmp::min(f64::from(*end) as usize, list.len());
|
||||
let start = std::cmp::max(*start as usize, 0);
|
||||
let end = std::cmp::min(*end as usize, list.len());
|
||||
Value::List(Box::new(newlist.slice(start..end)))
|
||||
}
|
||||
// TODO: figure out something better to do than return an empty string on a bad slice
|
||||
(Value::String(string), Value::Number(start), Value::Number(end)) => {
|
||||
let start = std::cmp::max(f64::from(*start) as usize, 0);
|
||||
let end = std::cmp::min(f64::from(*end) as usize, string.len());
|
||||
let start = std::cmp::max(*start as usize, 0);
|
||||
let end = std::cmp::min(*end as usize, string.len());
|
||||
Value::String(Rc::new(
|
||||
string
|
||||
.clone()
|
||||
|
@ -402,8 +322,8 @@ pub fn slice(ordered: &Value, start: &Value, end: &Value) -> Value {
|
|||
))
|
||||
}
|
||||
(Value::Interned(string), Value::Number(start), Value::Number(end)) => {
|
||||
let start = std::cmp::max(f64::from(*start) as usize, 0);
|
||||
let end = std::cmp::min(f64::from(*end) as usize, string.len());
|
||||
let start = std::cmp::max(*start as usize, 0);
|
||||
let end = std::cmp::min(*end as usize, string.len());
|
||||
Value::String(Rc::new(string.get(start..end).unwrap_or("").to_string()))
|
||||
}
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
|
@ -418,7 +338,7 @@ pub fn list(x: &Value) -> Value {
|
|||
let kvs = d.iter();
|
||||
let mut list = vector![];
|
||||
for (key, value) in kvs {
|
||||
let kv = Value::Tuple(Rc::new(vec![key.to_value(), value.clone()]));
|
||||
let kv = Value::Tuple(Rc::new(vec![Value::Keyword(key), value.clone()]));
|
||||
list.push_back(kv);
|
||||
}
|
||||
Value::List(Box::new(list))
|
||||
|
@ -430,14 +350,14 @@ pub fn list(x: &Value) -> Value {
|
|||
pub fn number(x: &Value) -> Value {
|
||||
match x {
|
||||
Value::Interned(string) => match string.parse::<f64>() {
|
||||
Ok(n) => Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::from_f64(n)])),
|
||||
Ok(n) => Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::Number(n)])),
|
||||
Err(_) => Value::Tuple(Rc::new(vec![
|
||||
Value::Keyword("err"),
|
||||
Value::String(Rc::new(format!("could not parse `{string}` as a number"))),
|
||||
])),
|
||||
},
|
||||
Value::String(string) => match string.parse::<f64>() {
|
||||
Ok(n) => Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::from_f64(n)])),
|
||||
Ok(n) => Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::Number(n)])),
|
||||
Err(_) => Value::Tuple(Rc::new(vec![
|
||||
Value::Keyword("err"),
|
||||
Value::String(Rc::new(format!("could not parse `{string}` as a number"))),
|
||||
|
@ -462,7 +382,6 @@ pub fn r#type(x: &Value) -> Value {
|
|||
Value::Box(_) => Value::Keyword("box"),
|
||||
Value::BaseFn(_) => Value::Keyword("fn"),
|
||||
Value::Partial(_) => Value::Keyword("fn"),
|
||||
Value::Process => Value::Keyword("process"),
|
||||
Value::Nothing => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
@ -470,7 +389,8 @@ pub fn r#type(x: &Value) -> Value {
|
|||
pub fn split(source: &Value, splitter: &Value) -> Value {
|
||||
match (source, splitter) {
|
||||
(Value::String(source), Value::String(splitter)) => {
|
||||
let parts = source.split(splitter.as_str());
|
||||
println!("splitting {source} with {splitter}");
|
||||
let parts = source.split_terminator(splitter.as_str());
|
||||
let mut list = vector![];
|
||||
for part in parts {
|
||||
list.push_back(Value::String(Rc::new(part.to_string())));
|
||||
|
@ -533,41 +453,63 @@ pub fn trimr(string: &Value) -> Value {
|
|||
}
|
||||
|
||||
pub fn atan_2(x: &Value, y: &Value) -> Value {
|
||||
let x = x.as_f64();
|
||||
let y = y.as_f64();
|
||||
Value::from_f64(x.atan2(y))
|
||||
match (x, y) {
|
||||
(Value::Number(x), Value::Number(y)) => Value::Number(x.atan2(*y)),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ceil(x: &Value) -> Value {
|
||||
Value::from_f64(x.as_f64().ceil())
|
||||
match x {
|
||||
Value::Number(x) => Value::Number(x.ceil()),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cos(x: &Value) -> Value {
|
||||
Value::from_f64(x.as_f64().cos())
|
||||
match x {
|
||||
Value::Number(x) => Value::Number(x.cos()),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn floor(x: &Value) -> Value {
|
||||
Value::from_f64(x.as_f64().floor())
|
||||
match x {
|
||||
Value::Number(x) => Value::Number(x.floor()),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn base_random() -> Value {
|
||||
Value::from_f64(random())
|
||||
pub fn random() -> Value {
|
||||
Value::Number(ran_f64())
|
||||
}
|
||||
|
||||
pub fn round(x: &Value) -> Value {
|
||||
Value::from_f64(x.as_f64().round())
|
||||
match x {
|
||||
Value::Number(x) => Value::Number(x.round()),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sin(x: &Value) -> Value {
|
||||
Value::from_f64(x.as_f64().sin())
|
||||
match x {
|
||||
Value::Number(x) => Value::Number(x.sin()),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sqrt(x: &Value) -> Value {
|
||||
Value::from_f64(x.as_f64().sqrt())
|
||||
match x {
|
||||
Value::Number(x) => Value::Number(x.sqrt()),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tan(x: &Value) -> Value {
|
||||
Value::from_f64(x.as_f64().tan())
|
||||
match x {
|
||||
Value::Number(x) => Value::Number(x.tan()),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gt(x: &Value, y: &Value) -> Value {
|
||||
|
@ -631,150 +573,60 @@ pub fn r#mod(x: &Value, y: &Value) -> Value {
|
|||
|
||||
pub fn make_base() -> Value {
|
||||
let members = vec![
|
||||
("add", Value::BaseFn(Box::new(BaseFn::Binary("add", add)))),
|
||||
(
|
||||
"append",
|
||||
Value::BaseFn(Box::new(BaseFn::Binary("append", append))),
|
||||
),
|
||||
(
|
||||
"assoc",
|
||||
Value::BaseFn(Box::new(BaseFn::Ternary("assoc", assoc))),
|
||||
),
|
||||
("at", Value::BaseFn(Box::new(BaseFn::Binary("at", at)))),
|
||||
(
|
||||
"atan_2",
|
||||
Value::BaseFn(Box::new(BaseFn::Binary("atan_2", atan_2))),
|
||||
),
|
||||
(
|
||||
"bool",
|
||||
Value::BaseFn(Box::new(BaseFn::Unary("bool", r#bool))),
|
||||
),
|
||||
("ceil", Value::BaseFn(Box::new(BaseFn::Unary("ceil", ceil)))),
|
||||
(
|
||||
"chars",
|
||||
Value::BaseFn(Box::new(BaseFn::Unary("chars", chars))),
|
||||
),
|
||||
(
|
||||
"concat",
|
||||
Value::BaseFn(Box::new(BaseFn::Binary("concat", concat))),
|
||||
),
|
||||
("cos", Value::BaseFn(Box::new(BaseFn::Unary("cos", cos)))),
|
||||
(
|
||||
"count",
|
||||
Value::BaseFn(Box::new(BaseFn::Unary("count", count))),
|
||||
),
|
||||
("dec", Value::BaseFn(Box::new(BaseFn::Unary("dec", dec)))),
|
||||
(
|
||||
"dissoc",
|
||||
Value::BaseFn(Box::new(BaseFn::Binary("dissoc", dissoc))),
|
||||
),
|
||||
("div", Value::BaseFn(Box::new(BaseFn::Binary("div", div)))),
|
||||
("doc", Value::BaseFn(Box::new(BaseFn::Unary("doc", doc)))),
|
||||
("add", Value::BaseFn(BaseFn::Binary("add", add))),
|
||||
("append", Value::BaseFn(BaseFn::Binary("append", append))),
|
||||
("assoc", Value::BaseFn(BaseFn::Ternary("assoc", assoc))),
|
||||
("at", Value::BaseFn(BaseFn::Binary("at", at))),
|
||||
("atan_2", Value::BaseFn(BaseFn::Binary("atan_2", atan_2))),
|
||||
("bool", Value::BaseFn(BaseFn::Unary("bool", r#bool))),
|
||||
("ceil", Value::BaseFn(BaseFn::Unary("ceil", ceil))),
|
||||
("chars", Value::BaseFn(BaseFn::Unary("chars", chars))),
|
||||
("concat", Value::BaseFn(BaseFn::Binary("concat", concat))),
|
||||
("cos", Value::BaseFn(BaseFn::Unary("cos", cos))),
|
||||
("count", Value::BaseFn(BaseFn::Unary("count", count))),
|
||||
("dec", Value::BaseFn(BaseFn::Unary("dec", dec))),
|
||||
("dissoc", Value::BaseFn(BaseFn::Binary("dissoc", dissoc))),
|
||||
("div", Value::BaseFn(BaseFn::Binary("div", div))),
|
||||
("doc!", Value::BaseFn(BaseFn::Unary("doc!", doc))),
|
||||
(
|
||||
"downcase",
|
||||
Value::BaseFn(Box::new(BaseFn::Unary("downcase", downcase))),
|
||||
),
|
||||
("eq?", Value::BaseFn(Box::new(BaseFn::Binary("eq?", eq)))),
|
||||
(
|
||||
"first",
|
||||
Value::BaseFn(Box::new(BaseFn::Unary("first", first))),
|
||||
),
|
||||
(
|
||||
"floor",
|
||||
Value::BaseFn(Box::new(BaseFn::Unary("floor", floor))),
|
||||
),
|
||||
("get", Value::BaseFn(Box::new(BaseFn::Binary("get", get)))),
|
||||
("gt?", Value::BaseFn(Box::new(BaseFn::Binary("gt?", gt)))),
|
||||
("gte?", Value::BaseFn(Box::new(BaseFn::Binary("gte?", gte)))),
|
||||
("inc", Value::BaseFn(Box::new(BaseFn::Unary("inc", inc)))),
|
||||
(
|
||||
"index_of",
|
||||
Value::BaseFn(Box::new(BaseFn::Binary("index_of", index_of))),
|
||||
),
|
||||
(
|
||||
"indices_of",
|
||||
Value::BaseFn(Box::new(BaseFn::Binary("indices_of", indices_of))),
|
||||
),
|
||||
("last", Value::BaseFn(Box::new(BaseFn::Unary("last", last)))),
|
||||
("list", Value::BaseFn(Box::new(BaseFn::Unary("list", list)))),
|
||||
("lt?", Value::BaseFn(Box::new(BaseFn::Binary("lt?", lt)))),
|
||||
("lte?", Value::BaseFn(Box::new(BaseFn::Binary("lte?", lte)))),
|
||||
("mod", Value::BaseFn(Box::new(BaseFn::Binary("mod", r#mod)))),
|
||||
(
|
||||
"mult",
|
||||
Value::BaseFn(Box::new(BaseFn::Binary("mult", mult))),
|
||||
),
|
||||
(
|
||||
"number",
|
||||
Value::BaseFn(Box::new(BaseFn::Unary("number", number))),
|
||||
),
|
||||
("pi", Value::from_f64(std::f64::consts::PI)),
|
||||
("pow", Value::BaseFn(Box::new(BaseFn::Binary("pow", pow)))),
|
||||
(
|
||||
"print!",
|
||||
Value::BaseFn(Box::new(BaseFn::Unary("print!", print))),
|
||||
),
|
||||
("process", Value::Process),
|
||||
(
|
||||
"random",
|
||||
Value::BaseFn(Box::new(BaseFn::Nullary("random", base_random))),
|
||||
),
|
||||
(
|
||||
"range",
|
||||
Value::BaseFn(Box::new(BaseFn::Binary("range", range))),
|
||||
),
|
||||
("rest", Value::BaseFn(Box::new(BaseFn::Unary("rest", rest)))),
|
||||
(
|
||||
"round",
|
||||
Value::BaseFn(Box::new(BaseFn::Unary("round", round))),
|
||||
),
|
||||
("show", Value::BaseFn(Box::new(BaseFn::Unary("show", show)))),
|
||||
("sin", Value::BaseFn(Box::new(BaseFn::Unary("sin", sin)))),
|
||||
(
|
||||
"slice",
|
||||
Value::BaseFn(Box::new(BaseFn::Ternary("slice", slice))),
|
||||
),
|
||||
(
|
||||
"split",
|
||||
Value::BaseFn(Box::new(BaseFn::Binary("split", split))),
|
||||
),
|
||||
("sqrt", Value::BaseFn(Box::new(BaseFn::Unary("sqrt", sqrt)))),
|
||||
("sqrt_2", Value::from_f64(std::f64::consts::SQRT_2)),
|
||||
(
|
||||
"store!",
|
||||
Value::BaseFn(Box::new(BaseFn::Binary("store!", store))),
|
||||
),
|
||||
("sub", Value::BaseFn(Box::new(BaseFn::Binary("sub", sub)))),
|
||||
("tan", Value::BaseFn(Box::new(BaseFn::Unary("tan", tan)))),
|
||||
("trim", Value::BaseFn(Box::new(BaseFn::Unary("trim", trim)))),
|
||||
(
|
||||
"triml",
|
||||
Value::BaseFn(Box::new(BaseFn::Unary("triml", triml))),
|
||||
),
|
||||
(
|
||||
"trimr",
|
||||
Value::BaseFn(Box::new(BaseFn::Unary("trimr", trimr))),
|
||||
),
|
||||
(
|
||||
"type",
|
||||
Value::BaseFn(Box::new(BaseFn::Unary("type", r#type))),
|
||||
),
|
||||
(
|
||||
"unbox",
|
||||
Value::BaseFn(Box::new(BaseFn::Unary("unbox", unbox))),
|
||||
),
|
||||
(
|
||||
"upcase",
|
||||
Value::BaseFn(Box::new(BaseFn::Unary("upcase", upcase))),
|
||||
),
|
||||
(
|
||||
"words",
|
||||
Value::BaseFn(Box::new(BaseFn::Unary("words", words))),
|
||||
Value::BaseFn(BaseFn::Unary("downcase", downcase)),
|
||||
),
|
||||
("eq?", Value::BaseFn(BaseFn::Binary("eq?", eq))),
|
||||
("first", Value::BaseFn(BaseFn::Unary("first", first))),
|
||||
("floor", Value::BaseFn(BaseFn::Unary("floor", floor))),
|
||||
("get", Value::BaseFn(BaseFn::Binary("get", get))),
|
||||
("gt?", Value::BaseFn(BaseFn::Binary("gt?", gt))),
|
||||
("gte?", Value::BaseFn(BaseFn::Binary("gte?", gte))),
|
||||
("inc", Value::BaseFn(BaseFn::Unary("inc", inc))),
|
||||
("last", Value::BaseFn(BaseFn::Unary("last", last))),
|
||||
("list", Value::BaseFn(BaseFn::Unary("list", list))),
|
||||
("lt?", Value::BaseFn(BaseFn::Binary("lt?", lt))),
|
||||
("lte?", Value::BaseFn(BaseFn::Binary("lte?", lte))),
|
||||
("mod", Value::BaseFn(BaseFn::Binary("mod", r#mod))),
|
||||
("mult", Value::BaseFn(BaseFn::Binary("mult", mult))),
|
||||
("number", Value::BaseFn(BaseFn::Unary("number", number))),
|
||||
("pi", Value::Number(std::f64::consts::PI)),
|
||||
("print!", Value::BaseFn(BaseFn::Unary("print!", print))),
|
||||
("random", Value::BaseFn(BaseFn::Nullary("random", random))),
|
||||
("range", Value::BaseFn(BaseFn::Binary("range", range))),
|
||||
("rest", Value::BaseFn(BaseFn::Unary("rest", rest))),
|
||||
("round", Value::BaseFn(BaseFn::Unary("round", round))),
|
||||
("show", Value::BaseFn(BaseFn::Unary("show", show))),
|
||||
("sin", Value::BaseFn(BaseFn::Unary("sin", sin))),
|
||||
("slice", Value::BaseFn(BaseFn::Ternary("slice", slice))),
|
||||
("split", Value::BaseFn(BaseFn::Binary("split", split))),
|
||||
("sqrt", Value::BaseFn(BaseFn::Unary("sqrt", sqrt))),
|
||||
("sqrt_2", Value::Number(std::f64::consts::SQRT_2)),
|
||||
("store!", Value::BaseFn(BaseFn::Binary("store!", store))),
|
||||
("sub", Value::BaseFn(BaseFn::Binary("sub", sub))),
|
||||
("tan", Value::BaseFn(BaseFn::Unary("tan", tan))),
|
||||
("trim", Value::BaseFn(BaseFn::Unary("trim", trim))),
|
||||
("triml", Value::BaseFn(BaseFn::Unary("triml", triml))),
|
||||
("trimr", Value::BaseFn(BaseFn::Unary("trimr", trimr))),
|
||||
("type", Value::BaseFn(BaseFn::Unary("type", r#type))),
|
||||
("unbox", Value::BaseFn(BaseFn::Unary("unbox", unbox))),
|
||||
("upcase", Value::BaseFn(BaseFn::Unary("upcase", upcase))),
|
||||
];
|
||||
let members = members
|
||||
.iter()
|
||||
.map(|(name, bfn)| (Key::Keyword(name), bfn.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
Value::Dict(Box::new(HashMap::from(members)))
|
||||
}
|
||||
|
|
62
src/chunk.rs
62
src/chunk.rs
|
@ -1,14 +1,12 @@
|
|||
use crate::js::*;
|
||||
use crate::op::Op;
|
||||
use crate::value::{Key, Value};
|
||||
use chumsky::prelude::SimpleSpan;
|
||||
use crate::value::Value;
|
||||
use imbl::HashMap;
|
||||
use num_traits::FromPrimitive;
|
||||
use regex::Regex;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StrPattern {
|
||||
pub words: Vec<&'static str>,
|
||||
pub words: Vec<String>,
|
||||
pub re: Regex,
|
||||
}
|
||||
|
||||
|
@ -18,17 +16,8 @@ pub struct Chunk {
|
|||
pub bytecode: Vec<u8>,
|
||||
pub keywords: Vec<&'static str>,
|
||||
pub string_patterns: Vec<StrPattern>,
|
||||
pub env: HashMap<Key, Value>,
|
||||
pub env: HashMap<&'static str, Value>,
|
||||
pub msgs: Vec<String>,
|
||||
pub spans: Vec<SimpleSpan>,
|
||||
pub src: &'static str,
|
||||
pub input: &'static str,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Chunk {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "Chunk.")
|
||||
}
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
|
@ -37,31 +26,26 @@ impl Chunk {
|
|||
use Op::*;
|
||||
match op {
|
||||
Pop | Store | Stash | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse
|
||||
| ResetMatch | GetKey | PanicWhenFallthrough | PanicNoMatch | PanicNoFnMatch
|
||||
| PanicNoLetMatch | TypeOf | Duplicate | Decrement | ToInt | Noop | LoadTuple
|
||||
| LoadList | Eq | Add | Sub | Mult | Div | Unbox | BoxStore | Assert | Get | At
|
||||
| Not | Panic | EmptyString | ConcatStrings | Stringify | MatchType | Return
|
||||
| UnconditionalMatch | Print | AppendList | ConcatList | PushList | PushDict
|
||||
| AppendDict | ConcatDict | Nothing | PushGlobal | SetUpvalue | LoadMessage
|
||||
| NextMessage | MatchMessage | ClearMessage | SendMethod | LoadScrutinee | Spawn => {
|
||||
console_log!("{} | {i:04}: {op}", self.spans[*i])
|
||||
| PanicIfNoMatch | ResetMatch | GetKey | PanicNoWhen | PanicNoMatch | TypeOf
|
||||
| Duplicate | Decrement | ToInt | Noop | LoadTuple | LoadList | Eq | Add | Sub
|
||||
| Mult | Div | Unbox | BoxStore | Assert | Get | At | Not | Panic | EmptyString
|
||||
| ConcatStrings | Stringify | MatchType | Return | UnconditionalMatch | Print
|
||||
| AppendList | ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing
|
||||
| PushGlobal | SetUpvalue => {
|
||||
println!("{i:04}: {op}")
|
||||
}
|
||||
Constant | MatchConstant => {
|
||||
let high = self.bytecode[*i + 1];
|
||||
let low = self.bytecode[*i + 2];
|
||||
let idx = ((high as usize) << 8) + low as usize;
|
||||
let value = &self.constants[idx].show();
|
||||
console_log!(
|
||||
"{} | {i:04}: {:16} {idx:05}: {value}",
|
||||
self.spans[*i],
|
||||
op.to_string()
|
||||
);
|
||||
println!("{i:04}: {:16} {idx:05}: {value}", op.to_string());
|
||||
*i += 2;
|
||||
}
|
||||
Msg => {
|
||||
let msg_idx = self.bytecode[*i + 1];
|
||||
let msg = &self.msgs[msg_idx as usize];
|
||||
console_log!("{} | {i:04}: {msg}", self.spans[*i]);
|
||||
println!("{i:04}: {msg}");
|
||||
*i += 1;
|
||||
}
|
||||
PushBinding | MatchTuple | MatchSplattedTuple | LoadSplattedTuple | MatchList
|
||||
|
@ -69,11 +53,7 @@ impl Chunk {
|
|||
| DropDictEntry | LoadDictValue | PushTuple | PushBox | MatchDepth | PopN | StoreN
|
||||
| Call | GetUpvalue | Partial | MatchString | PushStringMatches | TailCall | LoadN => {
|
||||
let next = self.bytecode[*i + 1];
|
||||
console_log!(
|
||||
"{} | {i:04}: {:16} {next:03}",
|
||||
self.spans[*i],
|
||||
op.to_string()
|
||||
);
|
||||
println!("{i:04}: {:16} {next:03}", op.to_string());
|
||||
*i += 1;
|
||||
}
|
||||
Jump | JumpIfFalse | JumpIfTrue | JumpIfNoMatch | JumpIfMatch | JumpBack
|
||||
|
@ -81,22 +61,26 @@ impl Chunk {
|
|||
let high = self.bytecode[*i + 1];
|
||||
let low = self.bytecode[*i + 2];
|
||||
let len = ((high as u16) << 8) + low as u16;
|
||||
console_log!(
|
||||
"{} | {i:04}: {:16} {len:05}",
|
||||
self.spans[*i],
|
||||
op.to_string()
|
||||
);
|
||||
println!("{i:04}: {:16} {len:05}", op.to_string());
|
||||
*i += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dissasemble(&self) {
|
||||
console_log!("IDX | CODE | INFO");
|
||||
println!("IDX | CODE | INFO");
|
||||
let mut i = 0;
|
||||
while i < self.bytecode.len() {
|
||||
self.dissasemble_instr(&mut i);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn kw_from(&self, kw: &str) -> Option<Value> {
|
||||
// self.kw_index_from(kw).map(Value::Keyword)
|
||||
// }
|
||||
|
||||
// pub fn kw_index_from(&self, kw: &str) -> Option<usize> {
|
||||
// self.keywords.iter().position(|s| *s == kw)
|
||||
// }
|
||||
}
|
||||
|
|
214
src/compiler.rs
214
src/compiler.rs
|
@ -1,10 +1,10 @@
|
|||
use crate::ast::{Ast, StringPart};
|
||||
use crate::chunk::{Chunk, StrPattern};
|
||||
use crate::op::Op;
|
||||
use crate::parser::Ast;
|
||||
use crate::parser::StringPart;
|
||||
use crate::spans::Spanned;
|
||||
use crate::value::*;
|
||||
use chumsky::prelude::SimpleSpan;
|
||||
use ordered_float::NotNan;
|
||||
use regex::Regex;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
|
@ -41,7 +41,7 @@ impl LoopInfo {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_builtin(_name: &str, _arity: usize) -> Option<Op> {
|
||||
fn get_builtin(name: &str, arity: usize) -> Option<Op> {
|
||||
// match (name, arity) {
|
||||
// ("type", 1) => Some(Op::TypeOf),
|
||||
// ("eq?", 2) => Some(Op::Eq),
|
||||
|
@ -69,12 +69,12 @@ pub struct Compiler {
|
|||
pub scope_depth: isize,
|
||||
pub match_depth: usize,
|
||||
pub stack_depth: usize,
|
||||
pub spans: Vec<SimpleSpan>,
|
||||
pub nodes: Vec<&'static Ast>,
|
||||
pub ast: &'static Ast,
|
||||
pub span: SimpleSpan,
|
||||
pub line: usize,
|
||||
pub lines: Vec<usize>,
|
||||
pub src: &'static str,
|
||||
pub input: &'static str,
|
||||
pub name: &'static str,
|
||||
pub depth: usize,
|
||||
pub upvalues: Vec<&'static str>,
|
||||
loop_info: Vec<LoopInfo>,
|
||||
|
@ -99,10 +99,10 @@ fn has_placeholder(args: &[Spanned<Ast>]) -> bool {
|
|||
impl Compiler {
|
||||
pub fn new(
|
||||
ast: &'static Spanned<Ast>,
|
||||
input: &'static str,
|
||||
name: &'static str,
|
||||
src: &'static str,
|
||||
depth: usize,
|
||||
env: imbl::HashMap<Key, Value>,
|
||||
env: imbl::HashMap<&'static str, Value>,
|
||||
debug: bool,
|
||||
) -> Compiler {
|
||||
let chunk = Chunk {
|
||||
|
@ -112,9 +112,6 @@ impl Compiler {
|
|||
string_patterns: vec![],
|
||||
env,
|
||||
msgs: vec![],
|
||||
src,
|
||||
input,
|
||||
spans: vec![],
|
||||
};
|
||||
Compiler {
|
||||
chunk,
|
||||
|
@ -123,14 +120,14 @@ impl Compiler {
|
|||
scope_depth: -1,
|
||||
match_depth: 0,
|
||||
stack_depth: 0,
|
||||
spans: vec![],
|
||||
nodes: vec![],
|
||||
ast: &ast.0,
|
||||
span: ast.1,
|
||||
line: 0,
|
||||
lines: vec![],
|
||||
loop_info: vec![],
|
||||
upvalues: vec![],
|
||||
src,
|
||||
input,
|
||||
name,
|
||||
tail_pos: false,
|
||||
debug,
|
||||
}
|
||||
|
@ -151,8 +148,8 @@ impl Compiler {
|
|||
let low = len as u8;
|
||||
let high = (len >> 8) as u8;
|
||||
self.emit_op(op);
|
||||
self.emit_byte(high as usize);
|
||||
self.emit_byte(low as usize);
|
||||
self.chunk.bytecode.push(high);
|
||||
self.chunk.bytecode.push(low);
|
||||
}
|
||||
|
||||
fn stub_jump(&mut self, op: Op) -> usize {
|
||||
|
@ -192,8 +189,8 @@ impl Compiler {
|
|||
self.emit_op(Op::Constant);
|
||||
let low = const_idx as u8;
|
||||
let high = (const_idx >> 8) as u8;
|
||||
self.emit_byte(high as usize);
|
||||
self.emit_byte(low as usize);
|
||||
self.chunk.bytecode.push(high);
|
||||
self.chunk.bytecode.push(low);
|
||||
self.stack_depth += 1;
|
||||
}
|
||||
|
||||
|
@ -219,18 +216,18 @@ impl Compiler {
|
|||
self.emit_op(Op::MatchConstant);
|
||||
let low = const_idx as u8;
|
||||
let high = (const_idx >> 8) as u8;
|
||||
self.emit_byte(high as usize);
|
||||
self.emit_byte(low as usize);
|
||||
self.chunk.bytecode.push(high);
|
||||
self.chunk.bytecode.push(low);
|
||||
}
|
||||
|
||||
fn emit_op(&mut self, op: Op) {
|
||||
self.chunk.bytecode.push(op as u8);
|
||||
self.chunk.spans.push(self.span);
|
||||
self.spans.push(self.span);
|
||||
}
|
||||
|
||||
fn emit_byte(&mut self, byte: usize) {
|
||||
self.chunk.bytecode.push(byte as u8);
|
||||
self.chunk.spans.push(self.span);
|
||||
self.spans.push(self.span);
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
|
@ -238,7 +235,7 @@ impl Compiler {
|
|||
}
|
||||
|
||||
pub fn bind(&mut self, name: &'static str) {
|
||||
self.msg(format!("binding `{name}` in {}", self.input));
|
||||
self.msg(format!("binding `{name}` in {}", self.name));
|
||||
self.msg(format!(
|
||||
"stack depth: {}; match depth: {}",
|
||||
self.stack_depth, self.match_depth
|
||||
|
@ -279,7 +276,7 @@ impl Compiler {
|
|||
fn resolve_binding(&mut self, name: &'static str) {
|
||||
self.msg(format!(
|
||||
"resolving binding `{name}` in {}\nlocals: {}",
|
||||
self.input,
|
||||
self.name,
|
||||
self.bindings
|
||||
.iter()
|
||||
.map(|binding| format!("{binding}"))
|
||||
|
@ -424,7 +421,7 @@ impl Compiler {
|
|||
self.emit_op(Op::Nil);
|
||||
self.stack_depth += 1;
|
||||
}
|
||||
Number(n) => self.emit_constant(Value::Number(NotNan::new(*n).unwrap())),
|
||||
Number(n) => self.emit_constant(Value::Number(*n)),
|
||||
Boolean(b) => {
|
||||
self.emit_op(if *b { Op::True } else { Op::False });
|
||||
self.stack_depth += 1;
|
||||
|
@ -457,13 +454,13 @@ impl Compiler {
|
|||
// return the evaluated rhs instead of whatever is last on the stack
|
||||
// we do this by pretending it's a binding
|
||||
(Let(patt, expr), _) => {
|
||||
// self.match_depth = 0;
|
||||
self.visit(expr);
|
||||
let expr_pos = self.stack_depth - 1;
|
||||
self.report_ast("let binding: matching".to_string(), patt);
|
||||
self.reset_match();
|
||||
self.emit_op(Op::LoadScrutinee);
|
||||
self.visit(patt);
|
||||
self.emit_op(Op::PanicNoLetMatch);
|
||||
self.emit_op(Op::PanicIfNoMatch);
|
||||
self.emit_op(Op::PushBinding);
|
||||
self.emit_byte(expr_pos);
|
||||
self.stack_depth += 1;
|
||||
|
@ -513,13 +510,14 @@ impl Compiler {
|
|||
}
|
||||
Let(patt, expr) => {
|
||||
self.report_depth("before let binding");
|
||||
// self.match_depth = 0;
|
||||
// self.emit_op(Op::ResetMatch);
|
||||
self.visit(expr);
|
||||
self.report_depth("after let expr");
|
||||
self.report_ast("let binding: matching".to_string(), patt);
|
||||
self.reset_match();
|
||||
self.emit_op(Op::LoadScrutinee);
|
||||
self.visit(patt);
|
||||
self.emit_op(Op::PanicNoLetMatch);
|
||||
self.emit_op(Op::PanicIfNoMatch);
|
||||
self.report_depth("after let binding");
|
||||
}
|
||||
WordPattern(name) => {
|
||||
|
@ -541,7 +539,7 @@ impl Compiler {
|
|||
}
|
||||
}
|
||||
NumberPattern(n) => {
|
||||
self.match_constant(Value::Number(NotNan::new(*n).unwrap()));
|
||||
self.match_constant(Value::Number(*n));
|
||||
}
|
||||
KeywordPattern(s) => {
|
||||
let existing_kw = self.chunk.keywords.iter().position(|kw| kw == s);
|
||||
|
@ -706,12 +704,10 @@ impl Compiler {
|
|||
let match_depth = self.match_depth;
|
||||
self.match_depth = 0;
|
||||
for pair in pairs.iter().take(pairs_len) {
|
||||
let (key, pattern) = match &pair.0 {
|
||||
KeyPairPattern(key, pattern) => (Value::Keyword(key), pattern),
|
||||
StrPairPattern(key, pattern) => (Value::Interned(key), pattern),
|
||||
_ => unreachable!("expected key to be keyword or string"),
|
||||
let (PairPattern(key, pattern), _) = pair else {
|
||||
unreachable!()
|
||||
};
|
||||
self.emit_constant(key);
|
||||
self.emit_constant(Value::Keyword(key));
|
||||
self.emit_op(Op::LoadDictValue);
|
||||
self.emit_byte(dict_stack_pos);
|
||||
self.visit(pattern);
|
||||
|
@ -726,7 +722,7 @@ impl Compiler {
|
|||
self.stack_depth += 1;
|
||||
|
||||
for pair in pairs.iter().take(pairs_len) {
|
||||
let (KeyPairPattern(key, _), _) = pair else {
|
||||
let (PairPattern(key, _), _) = pair else {
|
||||
unreachable!()
|
||||
};
|
||||
self.emit_constant(Value::Keyword(key));
|
||||
|
@ -755,7 +751,7 @@ impl Compiler {
|
|||
self.patch_jump(jump_idx, self.len() - jump_idx - 3);
|
||||
}
|
||||
Splattern(patt) => self.visit(patt),
|
||||
InterpolatedPattern(parts) => {
|
||||
InterpolatedPattern(parts, _) => {
|
||||
// println!("An interpolated pattern of {} parts", parts.len());
|
||||
let mut pattern = "".to_string();
|
||||
let mut words = vec![];
|
||||
|
@ -763,7 +759,7 @@ impl Compiler {
|
|||
match part {
|
||||
StringPart::Word(word) => {
|
||||
// println!("wordpart: {word}");
|
||||
words.push(*word);
|
||||
words.push(word.clone());
|
||||
pattern.push_str("(.*)");
|
||||
}
|
||||
StringPart::Data(data) => {
|
||||
|
@ -790,8 +786,9 @@ impl Compiler {
|
|||
self.emit_byte(pattern_idx);
|
||||
|
||||
for word in moar_words {
|
||||
let name: &'static str = std::string::String::leak(word);
|
||||
let binding = Binding {
|
||||
name: word,
|
||||
name,
|
||||
depth: self.scope_depth,
|
||||
stack_pos: self.stack_depth,
|
||||
};
|
||||
|
@ -801,7 +798,7 @@ impl Compiler {
|
|||
|
||||
self.patch_jump(jnm_idx, self.len() - jnm_idx - 3);
|
||||
}
|
||||
KeyPairPattern(..) | StrPairPattern(..) => unreachable!(),
|
||||
PairPattern(_, _) => unreachable!(),
|
||||
Tuple(members) => {
|
||||
self.tail_pos = false;
|
||||
for member in members {
|
||||
|
@ -846,12 +843,7 @@ impl Compiler {
|
|||
}
|
||||
}
|
||||
}
|
||||
StringPair(key, value) => {
|
||||
self.tail_pos = false;
|
||||
self.emit_constant(Value::Interned(key));
|
||||
self.visit(value);
|
||||
}
|
||||
KeywordPair(key, value) => {
|
||||
Pair(key, value) => {
|
||||
self.tail_pos = false;
|
||||
self.emit_constant(Value::Keyword(key));
|
||||
self.visit(value);
|
||||
|
@ -869,14 +861,6 @@ impl Compiler {
|
|||
self.stack_depth -= 1;
|
||||
self.report_depth("after keyword access");
|
||||
}
|
||||
(Keyword(_), Method(str, args)) | (Word(_), Method(str, args)) => {
|
||||
self.visit(first);
|
||||
self.emit_constant(Value::Keyword(str));
|
||||
self.visit(args);
|
||||
self.emit_op(Op::SendMethod);
|
||||
// target, method, args -> result
|
||||
self.stack_depth -= 2;
|
||||
}
|
||||
(Keyword(_), Arguments(args)) => {
|
||||
self.visit(&args[0]);
|
||||
self.visit(first);
|
||||
|
@ -971,16 +955,8 @@ impl Compiler {
|
|||
Keyword(str) => {
|
||||
self.emit_constant(Value::Keyword(str));
|
||||
self.emit_op(Op::GetKey);
|
||||
// target, keyword -> value
|
||||
self.stack_depth -= 1;
|
||||
}
|
||||
Method(str, args) => {
|
||||
self.emit_constant(Value::Keyword(str));
|
||||
self.visit(args);
|
||||
self.emit_op(Op::SendMethod);
|
||||
// target, method, args -> result
|
||||
self.stack_depth -= 2;
|
||||
}
|
||||
Arguments(args) => {
|
||||
self.store();
|
||||
let arity = args.len();
|
||||
|
@ -1015,7 +991,7 @@ impl Compiler {
|
|||
jump_idxes.push(self.stub_jump(Op::Jump));
|
||||
self.patch_jump(jif_jump_idx, self.len() - jif_jump_idx - 3);
|
||||
}
|
||||
self.emit_op(Op::PanicWhenFallthrough);
|
||||
self.emit_op(Op::PanicNoWhen);
|
||||
for idx in jump_idxes {
|
||||
self.patch_jump(idx, self.len() - idx - 3);
|
||||
}
|
||||
|
@ -1026,7 +1002,6 @@ impl Compiler {
|
|||
let tail_pos = self.tail_pos;
|
||||
self.tail_pos = false;
|
||||
self.visit(scrutinee.as_ref());
|
||||
self.emit_op(Op::LoadScrutinee);
|
||||
let stack_depth = self.stack_depth;
|
||||
let mut jump_idxes = vec![];
|
||||
let mut clauses = clauses.iter();
|
||||
|
@ -1064,108 +1039,10 @@ impl Compiler {
|
|||
self.emit_op(Op::Load);
|
||||
self.stack_depth += 1;
|
||||
}
|
||||
Spawn(body) => {
|
||||
// we compile this identically to a function
|
||||
// but it's a single, nullary fn
|
||||
// no match at the beginning, just go
|
||||
let name = "_spawned";
|
||||
let mut compiler = Compiler::new(
|
||||
body,
|
||||
self.input,
|
||||
self.src,
|
||||
self.depth + 1,
|
||||
self.chunk.env.clone(),
|
||||
self.debug,
|
||||
);
|
||||
compiler.tail_pos = true;
|
||||
compiler.visit(body);
|
||||
compiler.store();
|
||||
compiler.scope_depth -= 1;
|
||||
while let Some(binding) = compiler.bindings.last() {
|
||||
if binding.depth > compiler.scope_depth {
|
||||
compiler.bindings.pop();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
compiler.pop_n(compiler.stack_depth);
|
||||
compiler.stack_depth = 0;
|
||||
compiler.emit_op(Op::Return);
|
||||
let arities = vec![0];
|
||||
let chunks = vec![compiler.chunk];
|
||||
let lfn = crate::value::LFn::Defined {
|
||||
name,
|
||||
doc: None,
|
||||
arities,
|
||||
chunks,
|
||||
splat: 0,
|
||||
closed: RefCell::new(vec![]),
|
||||
patterns: vec!["()".to_string()],
|
||||
};
|
||||
|
||||
let the_fn = Value::Fn(Rc::new(lfn));
|
||||
|
||||
self.emit_constant(the_fn);
|
||||
for upvalue in compiler.upvalues {
|
||||
self.resolve_binding(upvalue);
|
||||
self.emit_op(Op::SetUpvalue);
|
||||
self.stack_depth -= 1;
|
||||
}
|
||||
self.emit_op(Op::Spawn);
|
||||
}
|
||||
Receive(clauses) => {
|
||||
self.msg("********starting receive".to_string());
|
||||
let tail_pos = self.tail_pos;
|
||||
self.emit_op(Op::ClearMessage);
|
||||
let receive_begin = self.len();
|
||||
self.emit_op(Op::LoadMessage);
|
||||
self.stack_depth += 1;
|
||||
let stack_depth = self.stack_depth;
|
||||
let mut jump_idxes = vec![];
|
||||
let mut clauses = clauses.iter();
|
||||
while let Some((MatchClause(pattern, guard, body), _)) = clauses.next() {
|
||||
self.tail_pos = false;
|
||||
let mut no_match_jumps = vec![];
|
||||
self.enter_scope();
|
||||
self.reset_match();
|
||||
self.visit(pattern);
|
||||
no_match_jumps.push(self.stub_jump(Op::JumpIfNoMatch));
|
||||
if guard.is_some() {
|
||||
let guard_expr: &'static Spanned<Ast> =
|
||||
Box::leak(Box::new(guard.clone().unwrap()));
|
||||
self.visit(guard_expr);
|
||||
no_match_jumps.push(self.stub_jump(Op::JumpIfFalse));
|
||||
}
|
||||
self.emit_op(Op::MatchMessage);
|
||||
self.tail_pos = tail_pos;
|
||||
self.visit(body);
|
||||
self.store();
|
||||
self.leave_scope();
|
||||
self.pop_n(self.stack_depth - stack_depth);
|
||||
jump_idxes.push(self.stub_jump(Op::Jump));
|
||||
for idx in no_match_jumps {
|
||||
self.patch_jump(idx, self.len() - idx - 3);
|
||||
}
|
||||
}
|
||||
// TODO: get the next message
|
||||
self.emit_op(Op::NextMessage);
|
||||
// TODO: jump back to the "get a message" instruction
|
||||
let jump_back = self.stub_jump(Op::JumpBack);
|
||||
self.patch_jump(jump_back, self.len() - receive_begin - 3);
|
||||
|
||||
for idx in jump_idxes {
|
||||
self.patch_jump(idx, self.len() - idx - 3);
|
||||
}
|
||||
self.pop_n(self.stack_depth - stack_depth);
|
||||
self.emit_op(Op::Load);
|
||||
self.stack_depth += 1;
|
||||
self.msg("********receive completed".to_string());
|
||||
}
|
||||
MatchClause(..) => unreachable!(),
|
||||
Fn(name, body, doc) => {
|
||||
let is_anon = name.is_empty();
|
||||
let mut name = name;
|
||||
let mut patterns = vec![];
|
||||
|
||||
if !is_anon {
|
||||
let declared = self.chunk.constants.iter().any(|val| match val {
|
||||
|
@ -1202,7 +1079,6 @@ impl Compiler {
|
|||
unreachable!()
|
||||
};
|
||||
let full_pattern = pattern;
|
||||
patterns.push(full_pattern.as_ref().0.show());
|
||||
let TuplePattern(pattern) = &pattern.0 else {
|
||||
unreachable!()
|
||||
};
|
||||
|
@ -1218,7 +1094,7 @@ impl Compiler {
|
|||
None => {
|
||||
let mut compiler = Compiler::new(
|
||||
clause,
|
||||
self.input,
|
||||
name,
|
||||
self.src,
|
||||
self.depth + 1,
|
||||
self.chunk.env.clone(),
|
||||
|
@ -1264,6 +1140,7 @@ impl Compiler {
|
|||
Box::leak(Box::new(guard.clone().unwrap()));
|
||||
compiler.visit(guard_expr);
|
||||
no_match_jumps.push(compiler.stub_jump(Op::JumpIfFalse));
|
||||
compiler.stack_depth -= 1;
|
||||
}
|
||||
compiler.tail_pos = true;
|
||||
compiler.visit(clause_body);
|
||||
|
@ -1294,7 +1171,7 @@ impl Compiler {
|
|||
let mut chunks = vec![];
|
||||
|
||||
for (arity, mut compiler) in compilers {
|
||||
compiler.emit_op(Op::PanicNoFnMatch);
|
||||
compiler.emit_op(Op::PanicNoMatch);
|
||||
let chunk = compiler.chunk;
|
||||
if self.debug {
|
||||
println!("=== function chuncktion: {name}/{arity} ===");
|
||||
|
@ -1318,7 +1195,6 @@ impl Compiler {
|
|||
chunks,
|
||||
splat,
|
||||
closed: RefCell::new(vec![]),
|
||||
patterns,
|
||||
};
|
||||
|
||||
let the_fn = Value::Fn(Rc::new(lfn));
|
||||
|
@ -1383,7 +1259,7 @@ impl Compiler {
|
|||
let jump_back = self.stub_jump(Op::JumpBack);
|
||||
// set jump points
|
||||
self.patch_jump(jump_back, self.len() - repeat_begin - 2);
|
||||
self.patch_jump(jiz_idx, self.len() - jiz_idx - 3);
|
||||
self.patch_jump(jiz_idx, self.len() - repeat_begin - 4);
|
||||
self.pop();
|
||||
self.emit_constant(Value::Nil);
|
||||
self.tail_pos = tail_pos;
|
||||
|
@ -1554,12 +1430,12 @@ impl Compiler {
|
|||
Placeholder => {
|
||||
self.emit_op(Op::Nothing);
|
||||
}
|
||||
And | Or | Arguments(..) | Method(..) => unreachable!(),
|
||||
And | Or | Arguments(..) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disassemble(&self) {
|
||||
println!("=== chunk: {} ===", self.input);
|
||||
println!("=== chunk: {} ===", self.name);
|
||||
self.chunk.dissasemble();
|
||||
}
|
||||
}
|
||||
|
|
220
src/errors.rs
220
src/errors.rs
|
@ -1,177 +1,55 @@
|
|||
use crate::js::*;
|
||||
use crate::lexer::Token;
|
||||
use crate::panic::{Panic, PanicMsg};
|
||||
// use crate::process::{LErr, Trace};
|
||||
use crate::validator::VErr;
|
||||
use crate::vm::CallFrame;
|
||||
use chumsky::error::RichPattern;
|
||||
use chumsky::prelude::*;
|
||||
// use ariadne::{sources, Color, Label, Report, ReportKind};
|
||||
|
||||
const SEPARATOR: &str = "\n\n";
|
||||
// pub fn report_panic(err: LErr) {
|
||||
// let mut srcs = HashSet::new();
|
||||
// let mut stack = vec![];
|
||||
// let mut order = 1;
|
||||
// for entry in err.trace.iter().rev() {
|
||||
// let Trace {
|
||||
// callee,
|
||||
// caller,
|
||||
// function,
|
||||
// arguments,
|
||||
// input,
|
||||
// src,
|
||||
// } = entry;
|
||||
// let (_, first_span) = callee;
|
||||
// let (_, second_span) = caller;
|
||||
// let Value::Fn(f) = function else {
|
||||
// unreachable!()
|
||||
// };
|
||||
// let fn_name = f.borrow().name.clone();
|
||||
// let i = first_span.start;
|
||||
// let j = second_span.end;
|
||||
// let label = Label::new((entry.input, i..j))
|
||||
// .with_color(Color::Yellow)
|
||||
// .with_message(format!("({order}) calling `{fn_name}` with `{arguments}`"));
|
||||
// order += 1;
|
||||
// stack.push(label);
|
||||
// srcs.insert((*input, *src));
|
||||
// }
|
||||
// Report::build(ReportKind::Error, (err.input, err.span.into_range()))
|
||||
// .with_message(format!("Ludus panicked! {}", err.msg))
|
||||
// .with_label(Label::new((err.input, err.span.into_range())).with_color(Color::Red))
|
||||
// .with_labels(stack)
|
||||
// .with_note(err.extra)
|
||||
// .finish()
|
||||
// .print(sources(srcs.iter().copied()))
|
||||
// .unwrap();
|
||||
// }
|
||||
|
||||
pub fn line_number(src: &'static str, span: SimpleSpan) -> usize {
|
||||
src.chars().take(span.start).filter(|c| *c == '\n').count()
|
||||
}
|
||||
|
||||
fn get_line(src: &'static str, line: usize) -> String {
|
||||
src.split("\n").nth(line).unwrap().to_string()
|
||||
}
|
||||
|
||||
pub fn lexing(errs: Vec<Rich<'static, char>>, src: &'static str, input: &'static str) -> String {
|
||||
let mut msgs = vec!["Ludus found some errors.".to_string()];
|
||||
pub fn report_invalidation(errs: Vec<VErr>) {
|
||||
// for err in errs {
|
||||
// Report::build(ReportKind::Error, (err.input, err.span.into_range()))
|
||||
// .with_message(err.msg.to_string())
|
||||
// .with_label(Label::new((err.input, err.span.into_range())).with_color(Color::Cyan))
|
||||
// .finish()
|
||||
// .print(sources(vec![(err.input, err.src)]))
|
||||
// .unwrap();
|
||||
// }
|
||||
for err in errs {
|
||||
let mut msg = vec![];
|
||||
let line_number = line_number(src, *err.span());
|
||||
let line = get_line(src, line_number);
|
||||
let char = src.chars().nth(err.span().start).unwrap();
|
||||
msg.push(format!("Syntax error: unexpected {char}"));
|
||||
msg.push(format!(" on line {} in {}", line_number + 1, input));
|
||||
msg.push(format!(" >>> {line}"));
|
||||
msgs.push(msg.join("\n"));
|
||||
}
|
||||
msgs.join(SEPARATOR)
|
||||
}
|
||||
|
||||
pub fn validation(errs: Vec<VErr>) -> String {
|
||||
let mut msgs = vec!["Ludus found some errors.".to_string()];
|
||||
for err in errs {
|
||||
let mut msg = vec![];
|
||||
let line_number = line_number(err.src, *err.span);
|
||||
let line = get_line(err.src, line_number);
|
||||
msg.push(format!("Validation error: {}", err.msg));
|
||||
msg.push(format!(" on line {} in {}", line_number + 1, err.input));
|
||||
msg.push(format!(" >>> {line}"));
|
||||
msgs.push(msg.join("\n"));
|
||||
}
|
||||
msgs.join(SEPARATOR)
|
||||
}
|
||||
|
||||
pub fn parsing(errs: Vec<Rich<'static, Token>>, src: &'static str, input: &'static str) -> String {
|
||||
let mut msgs = vec!["Ludus found some errors.".to_string()];
|
||||
for err in errs {
|
||||
let mut msg = vec![];
|
||||
let line_number = line_number(src, *err.span());
|
||||
let line = get_line(src, line_number);
|
||||
let details = parsing_message(err);
|
||||
msg.push(format!("Syntax error: {}", details));
|
||||
msg.push(format!(" on line {} in {}", line_number + 1, input));
|
||||
msg.push(format!(" >>> {line}"));
|
||||
msgs.push(msg.join("\n"))
|
||||
}
|
||||
msgs.join(SEPARATOR)
|
||||
}
|
||||
|
||||
fn parsing_message(err: Rich<'static, Token>) -> String {
|
||||
let found = match err.found() {
|
||||
Some(token) => token.show(),
|
||||
None => "end of input".to_string(),
|
||||
};
|
||||
let expected = err.expected();
|
||||
let mut expecteds = vec![];
|
||||
for pattern in expected {
|
||||
let shown = match pattern {
|
||||
RichPattern::Token(t) => t.show(),
|
||||
RichPattern::Label(s) => s.to_string(),
|
||||
RichPattern::Identifier(s) => s.clone(),
|
||||
RichPattern::Any => "any".to_string(),
|
||||
RichPattern::SomethingElse => "something else".to_string(),
|
||||
RichPattern::EndOfInput => "eof".to_string(),
|
||||
};
|
||||
expecteds.push(shown);
|
||||
}
|
||||
let expecteds = if expecteds.iter().any(|e| e == &"else".to_string()) {
|
||||
vec!["else".to_string()]
|
||||
} else {
|
||||
expecteds
|
||||
};
|
||||
let expecteds = if expecteds.iter().any(|e| e == &"then".to_string()) {
|
||||
vec!["then".to_string()]
|
||||
} else {
|
||||
expecteds
|
||||
};
|
||||
|
||||
let expecteds = expecteds.join(" | ");
|
||||
format!("Ludus did not expect to see: {found}\n expected: {expecteds}")
|
||||
}
|
||||
|
||||
pub fn panic(panic: Panic) -> String {
|
||||
// console_log!("Ludus panicked!: {panic}");
|
||||
// panic.call_stack.last().unwrap().chunk().dissasemble();
|
||||
// console_log!("{:?}", panic.call_stack.last().unwrap().chunk().spans);
|
||||
let Panic {
|
||||
msg,
|
||||
scrutinee,
|
||||
call_stack,
|
||||
} = &panic;
|
||||
match msg {
|
||||
PanicMsg::Generic(msg) => {
|
||||
let traceback = traceback(&panic);
|
||||
format!("{msg}\n{traceback}")
|
||||
}
|
||||
PanicMsg::NoFnMatch => {
|
||||
let name = call_stack.last().unwrap().function.as_fn().name();
|
||||
let scrutinee = scrutinee.as_ref().unwrap();
|
||||
let traceback = but_first_traceback(&panic);
|
||||
let patterns = call_stack
|
||||
.last()
|
||||
.unwrap()
|
||||
.function
|
||||
.as_fn()
|
||||
.patterns()
|
||||
.stringify();
|
||||
format!(
|
||||
"no match calling fn `{name}` with `{scrutinee}`\n expected match with one of:\n {patterns}\n{traceback}",
|
||||
)
|
||||
}
|
||||
PanicMsg::NoLetMatch => {
|
||||
let scrutinee = scrutinee.as_ref().unwrap();
|
||||
let traceback = traceback(&panic);
|
||||
format!("no match in `let` form against `{scrutinee}`\n{traceback}")
|
||||
}
|
||||
PanicMsg::NoMatch => {
|
||||
let scrutinee = scrutinee.as_ref().unwrap();
|
||||
let traceback = traceback(&panic);
|
||||
format!("no match in `match` form against `{scrutinee}`\n{traceback}")
|
||||
}
|
||||
println!("{}", err.msg)
|
||||
}
|
||||
}
|
||||
|
||||
fn but_first_traceback(panic: &Panic) -> String {
|
||||
let mut traceback = vec![];
|
||||
let mut stack = panic.call_stack.iter().rev();
|
||||
stack.next();
|
||||
for frame in stack {
|
||||
traceback.push(frame_info(frame));
|
||||
}
|
||||
traceback.join("\n")
|
||||
}
|
||||
|
||||
fn traceback(panic: &Panic) -> String {
|
||||
let mut traceback = vec![];
|
||||
for frame in panic.call_stack.iter().rev() {
|
||||
traceback.push(frame_info(frame));
|
||||
}
|
||||
traceback.join("\n")
|
||||
}
|
||||
|
||||
fn frame_info(frame: &CallFrame) -> String {
|
||||
let span = frame.chunk().spans[if frame.ip == 0 { 0 } else { frame.ip - 1 }];
|
||||
let line_number = line_number(frame.chunk().src, span);
|
||||
// console_log!("ip: {} | span: {span} | line: {line_number}", frame.ip);
|
||||
let line = get_line(frame.chunk().src, line_number);
|
||||
let line = line.trim_start();
|
||||
let name = frame.function.as_fn().name();
|
||||
let input = frame.chunk().input;
|
||||
format!(
|
||||
" in {name} on line {} in {input}\n >>> {line}",
|
||||
line_number + 1
|
||||
)
|
||||
}
|
||||
|
||||
/////// Some thoughts
|
||||
// We're putting the information we need on the function and the chunk.
|
||||
// In the compiler, on functions, build up a vec of strings that are the patterns the function can match against
|
||||
// The pattern asts have a `show` method.
|
||||
// And with the additional members on Chunk, we should have everything we need for a pretty fn no match message
|
||||
// Let no match is no problem, either. We should have no concerns pulling the line with the span start and string
|
||||
// We don't need to reproduce the pattern, since it will be right there in the code
|
||||
// As for match forms, we'll just use "no match" and print the value
|
||||
|
|
103
src/io.rs
103
src/io.rs
|
@ -1,103 +0,0 @@
|
|||
use wasm_bindgen::prelude::*;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use crate::value::Value;
|
||||
use crate::js::*;
|
||||
use imbl::Vector;
|
||||
use std::rc::Rc;
|
||||
|
||||
const OK: Value = Value::Keyword("ok");
|
||||
const ERR: Value = Value::Keyword("err");
|
||||
|
||||
#[wasm_bindgen(module = "/pkg/worker.js")]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(catch)]
|
||||
async fn io (output: String) -> Result<JsValue, JsValue>;
|
||||
}
|
||||
|
||||
type Url = Value; // expect a string
|
||||
type Commands = Value; // expect a list of command tuples
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(tag = "verb", content = "data")]
|
||||
pub enum MsgOut {
|
||||
Console(Value),
|
||||
Commands(Commands),
|
||||
Fetch(Url),
|
||||
Complete(Value),
|
||||
Error(String),
|
||||
Ready
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "verb", content = "data")]
|
||||
pub enum MsgIn {
|
||||
Input(String),
|
||||
Fetch(String, f64, String),
|
||||
Kill,
|
||||
Keys(Vec<String>),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for MsgIn {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
MsgIn::Input(str) => write!(f, "Input: {str}"),
|
||||
MsgIn::Kill => write!(f, "Kill"),
|
||||
MsgIn::Fetch(url, code, text) => write!(f, "Fetch: {url} :: {code} ::\n{text}"),
|
||||
MsgIn::Keys(keys) => write!(f, "Keys: {:?}", keys),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MsgIn {
|
||||
pub fn into_value(self) -> Value {
|
||||
match self {
|
||||
MsgIn::Input(str) => Value::from_string(str),
|
||||
MsgIn::Fetch(url, status_f64, string) => {
|
||||
let url = Value::from_string(url);
|
||||
let status = Value::from_f64(status_f64);
|
||||
let text = Value::from_string(string);
|
||||
let result_tuple = if status_f64 == 200.0 {
|
||||
Value::tuple(vec![OK, text])
|
||||
} else {
|
||||
Value::tuple(vec![ERR, status])
|
||||
};
|
||||
Value::tuple(vec![url, result_tuple])
|
||||
}
|
||||
MsgIn::Kill => Value::Nothing,
|
||||
MsgIn::Keys(downkeys) => {
|
||||
let mut vector = Vector::new();
|
||||
for key in downkeys {
|
||||
vector.push_back(Value::String(Rc::new(key)));
|
||||
}
|
||||
Value::list(vector)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_err_to_ludus_console(msg: String) {
|
||||
console_log!("{msg}");
|
||||
do_io(vec![MsgOut::Ready, MsgOut::Error(msg)]).await;
|
||||
}
|
||||
|
||||
pub async fn do_io (msgs: Vec<MsgOut>) -> Vec<MsgIn> {
|
||||
let json = serde_json::to_string(&msgs).unwrap();
|
||||
let inbox = io(json).await;
|
||||
let inbox = match inbox {
|
||||
Ok(msgs) => msgs,
|
||||
Err(errs) => {
|
||||
// console_log!("error receiving messages in io; {:?}", errs);
|
||||
return vec![];
|
||||
}
|
||||
};
|
||||
let inbox = inbox.as_string().expect("response should be a string");
|
||||
let inbox: Vec<MsgIn> = serde_json::from_str(inbox.as_str()).expect("response from js should be valid");
|
||||
if !inbox.is_empty() {
|
||||
// console_log!("ludus received messages");
|
||||
for msg in inbox.iter() {
|
||||
// console_log!("{}", msg);
|
||||
}
|
||||
}
|
||||
inbox
|
||||
}
|
||||
|
19
src/js.rs
19
src/js.rs
|
@ -1,19 +0,0 @@
|
|||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
pub fn log(a: &str);
|
||||
|
||||
#[wasm_bindgen(js_namespace = Math)]
|
||||
pub fn random() -> f64;
|
||||
|
||||
#[wasm_bindgen(js_namespace = Date)]
|
||||
pub fn now() -> f64;
|
||||
}
|
||||
|
||||
macro_rules! console_log {
|
||||
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
pub(crate) use console_log;
|
84
src/lexer.rs
84
src/lexer.rs
|
@ -2,7 +2,7 @@ use crate::spans::*;
|
|||
use chumsky::prelude::*;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Token {
|
||||
Nil,
|
||||
Number(f64),
|
||||
|
@ -13,7 +13,6 @@ pub enum Token {
|
|||
// todo: hard code these types
|
||||
Reserved(&'static str),
|
||||
Punctuation(&'static str),
|
||||
Method(&'static str),
|
||||
}
|
||||
|
||||
impl fmt::Display for Token {
|
||||
|
@ -27,25 +26,6 @@ impl fmt::Display for Token {
|
|||
Token::Reserved(r) => write!(f, "[Reserved {}]", r),
|
||||
Token::Nil => write!(f, "[nil]"),
|
||||
Token::Punctuation(p) => write!(f, "[Punctuation {}]", p),
|
||||
Token::Method(m) => write!(f, "[Method {m}]"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub fn show(&self) -> String {
|
||||
match self {
|
||||
Token::Number(n) => format!("{n}"),
|
||||
Token::Boolean(b) => format!("{b}"),
|
||||
Token::Keyword(k) => format!(":{k}"),
|
||||
Token::Method(m) => format!("::{m}"),
|
||||
Token::Nil => "nil".to_string(),
|
||||
Token::String(s) => format!("\"{s}\""),
|
||||
Token::Reserved(s) | Token::Word(s) => s.to_string(),
|
||||
Token::Punctuation(s) => {
|
||||
let out = if *s == "\n" { "newline" } else { s };
|
||||
out.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,33 +56,16 @@ pub fn lexer(
|
|||
"nil" => Token::Nil,
|
||||
// todo: hard code these as type constructors
|
||||
"as" | "box" | "do" | "else" | "fn" | "if" | "let" | "loop" | "match" | "panic!"
|
||||
| "recur" | "repeat" | "then" | "when" | "with" | "or" | "and" | "receive" => {
|
||||
Token::Reserved(word)
|
||||
}
|
||||
| "recur" | "repeat" | "then" | "when" | "with" | "or" | "and" => Token::Reserved(word),
|
||||
_ => Token::Word(word),
|
||||
});
|
||||
|
||||
let method = just("::").ignore_then(word).map(Token::Method);
|
||||
|
||||
let keyword = just(':').ignore_then(word).map(Token::Keyword);
|
||||
|
||||
let escape = just('\\')
|
||||
.then(choice((
|
||||
just('\\').to('\\'),
|
||||
just('n').to('\n'),
|
||||
just('t').to('\t'),
|
||||
just('r').to('\r'),
|
||||
just('"').to('"'), // TODO: figure out why this isn't working
|
||||
)))
|
||||
.ignored();
|
||||
|
||||
let string = none_of('"')
|
||||
.ignored()
|
||||
.or(escape)
|
||||
.repeated()
|
||||
.to_slice()
|
||||
.map(Token::String)
|
||||
.delimited_by(just('"'), just('"'));
|
||||
let string = just('"')
|
||||
.ignore_then(none_of("\"").repeated().to_slice())
|
||||
.then_ignore(just('"'))
|
||||
.map(Token::String);
|
||||
|
||||
// todo: hard code these as type constructors
|
||||
let punctuation = one_of(",=[]{}()>;\n_")
|
||||
|
@ -116,7 +79,6 @@ pub fn lexer(
|
|||
let token = number
|
||||
.or(reserved_or_word)
|
||||
.or(keyword)
|
||||
.or(method)
|
||||
.or(string)
|
||||
.or(punctuation);
|
||||
|
||||
|
@ -134,37 +96,3 @@ pub fn lexer(
|
|||
.repeated()
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_lexes_nil() {
|
||||
let spanned_toks = lexer().parse("nil").into_output_errors().0.unwrap();
|
||||
let (token, _) = spanned_toks[0].clone();
|
||||
assert_eq!(token, Token::Nil);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_lexes_strings() {
|
||||
let spanned_toks = lexer()
|
||||
.parse("\"foo bar baz\"")
|
||||
.into_output_errors()
|
||||
.0
|
||||
.unwrap();
|
||||
let (token, _) = spanned_toks[0].clone();
|
||||
assert_eq!(token, Token::String("foo bar baz"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_lexes_strings_w_escaped_quotes() {
|
||||
let spanned_toks = lexer()
|
||||
.parse("\"foo \\\"bar baz\"")
|
||||
.into_output_errors()
|
||||
.0
|
||||
.unwrap();
|
||||
let (token, _) = spanned_toks[0].clone();
|
||||
assert_eq!(token, Token::String("foo \"bar baz"));
|
||||
}
|
||||
}
|
||||
|
|
165
src/lib.rs
165
src/lib.rs
|
@ -1,25 +1,14 @@
|
|||
use chumsky::{input::Stream, prelude::*};
|
||||
use imbl::HashMap;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
|
||||
const DEBUG_SCRIPT_COMPILE: bool = false;
|
||||
const DEBUG_SCRIPT_RUN: bool = false;
|
||||
const DEBUG_PRELUDE_COMPILE: bool = false;
|
||||
const DEBUG_PRELUDE_RUN: bool = false;
|
||||
|
||||
mod io;
|
||||
use io::send_err_to_ludus_console;
|
||||
|
||||
mod ast;
|
||||
use crate::ast::Ast;
|
||||
|
||||
mod base;
|
||||
|
||||
mod world;
|
||||
use crate::world::{World, Zoo};
|
||||
|
||||
mod spans;
|
||||
use crate::spans::Spanned;
|
||||
|
||||
|
@ -27,18 +16,13 @@ mod lexer;
|
|||
use crate::lexer::lexer;
|
||||
|
||||
mod parser;
|
||||
use crate::parser::parser;
|
||||
use crate::parser::{parser, Ast};
|
||||
|
||||
mod validator;
|
||||
use crate::validator::Validator;
|
||||
|
||||
mod errors;
|
||||
use crate::errors::{lexing, parsing, validation};
|
||||
|
||||
mod panic;
|
||||
|
||||
mod js;
|
||||
use crate::js::*;
|
||||
use crate::errors::report_invalidation;
|
||||
|
||||
mod chunk;
|
||||
mod op;
|
||||
|
@ -46,24 +30,24 @@ mod op;
|
|||
mod compiler;
|
||||
use crate::compiler::Compiler;
|
||||
|
||||
pub mod value;
|
||||
use value::{Value, Key};
|
||||
mod value;
|
||||
use value::Value;
|
||||
|
||||
mod vm;
|
||||
use vm::Creature;
|
||||
use vm::Vm;
|
||||
|
||||
const PRELUDE: &str = include_str!("../assets/prelude.ld");
|
||||
const PRELUDE: &str = include_str!("../assets/test_prelude.ld");
|
||||
|
||||
fn prelude() -> HashMap<Key, Value> {
|
||||
fn prelude() -> HashMap<&'static str, Value> {
|
||||
let tokens = lexer().parse(PRELUDE).into_output_errors().0.unwrap();
|
||||
let (parsed, parse_errors) = parser()
|
||||
.parse(Stream::from_iter(tokens).map((0..PRELUDE.len()).into(), |(t, s)| (t, s)))
|
||||
.into_output_errors();
|
||||
|
||||
if !parse_errors.is_empty() {
|
||||
console_log!("ERROR PARSING PRELUDE:");
|
||||
console_log!("{:?}", parse_errors);
|
||||
panic!("parsing errors in prelude");
|
||||
println!("ERROR PARSING PRELUDE:");
|
||||
println!("{:?}", parse_errors);
|
||||
panic!();
|
||||
}
|
||||
|
||||
let parsed = parsed.unwrap();
|
||||
|
@ -71,17 +55,16 @@ fn prelude() -> HashMap<Key, Value> {
|
|||
|
||||
let base = base::make_base();
|
||||
let mut base_env = imbl::HashMap::new();
|
||||
base_env.insert(Key::Keyword("base"), base.clone());
|
||||
base_env.insert("base", base.clone());
|
||||
|
||||
let mut validator = Validator::new(ast, span, "prelude", PRELUDE, base_env);
|
||||
|
||||
validator.validate();
|
||||
|
||||
if !validator.errors.is_empty() {
|
||||
console_log!("VALIDATION ERRORS IN PRLUDE:");
|
||||
// report_invalidation(validator.errors);
|
||||
console_log!("{:?}", validator.errors);
|
||||
panic!("validator errors in prelude");
|
||||
println!("VALIDATION ERRORS IN PRLUDE:");
|
||||
report_invalidation(validator.errors);
|
||||
panic!();
|
||||
}
|
||||
|
||||
let parsed: &'static Spanned<Ast> = Box::leak(Box::new(parsed));
|
||||
|
@ -98,10 +81,8 @@ fn prelude() -> HashMap<Key, Value> {
|
|||
compiler.compile();
|
||||
|
||||
let chunk = compiler.chunk;
|
||||
let stub_zoo = Rc::new(RefCell::new(Zoo::new()));
|
||||
let mut prld_sync = Creature::new(chunk, stub_zoo, DEBUG_PRELUDE_RUN);
|
||||
prld_sync.interpret();
|
||||
let prelude = prld_sync.result.unwrap().unwrap();
|
||||
let mut vm = Vm::new(chunk, DEBUG_PRELUDE_RUN);
|
||||
let prelude = vm.run().clone().unwrap();
|
||||
match prelude {
|
||||
Value::Dict(hashmap) => *hashmap,
|
||||
_ => unreachable!(),
|
||||
|
@ -109,17 +90,11 @@ fn prelude() -> HashMap<Key, Value> {
|
|||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn ludus(src: String) {
|
||||
// instrument wasm to report rust panics
|
||||
console_error_panic_hook::set_once();
|
||||
// leak the source so it lives FOREVER
|
||||
pub fn ludus(src: String) -> String {
|
||||
let src = src.to_string().leak();
|
||||
|
||||
// lex the source
|
||||
let (tokens, lex_errs) = lexer().parse(src).into_output_errors();
|
||||
if !lex_errs.is_empty() {
|
||||
send_err_to_ludus_console(lexing(lex_errs, src, "user script")).await;
|
||||
return;
|
||||
return format!("{:?}", lex_errs);
|
||||
}
|
||||
|
||||
let tokens = tokens.unwrap();
|
||||
|
@ -128,13 +103,16 @@ pub async fn ludus(src: String) {
|
|||
.parse(Stream::from_iter(tokens).map((0..src.len()).into(), |(t, s)| (t, s)))
|
||||
.into_output_errors();
|
||||
if !parse_errors.is_empty() {
|
||||
send_err_to_ludus_console(parsing(parse_errors, src, "user script")).await;
|
||||
return;
|
||||
return format!("{:?}", parse_errors);
|
||||
}
|
||||
|
||||
// ::sigh:: The AST should be 'static
|
||||
// This simplifies lifetimes, and
|
||||
// in any event, the AST should live forever
|
||||
let parsed: &'static Spanned<Ast> = Box::leak(Box::new(parse_result.unwrap()));
|
||||
|
||||
let prelude = prelude();
|
||||
let postlude = prelude.clone();
|
||||
// let prelude = imbl::HashMap::new();
|
||||
|
||||
let mut validator = Validator::new(&parsed.0, &parsed.1, "user input", src, prelude.clone());
|
||||
|
@ -142,45 +120,90 @@ pub async fn ludus(src: String) {
|
|||
|
||||
// TODO: validator should generate a string, not print to the console
|
||||
if !validator.errors.is_empty() {
|
||||
send_err_to_ludus_console(validation(validator.errors)).await;
|
||||
return;
|
||||
report_invalidation(validator.errors);
|
||||
return "Ludus found some validation errors.".to_string();
|
||||
}
|
||||
|
||||
let mut compiler = Compiler::new(
|
||||
parsed,
|
||||
"user script",
|
||||
src,
|
||||
0,
|
||||
prelude.clone(),
|
||||
DEBUG_SCRIPT_COMPILE,
|
||||
);
|
||||
let mut compiler = Compiler::new(parsed, "sandbox", src, 0, prelude, DEBUG_SCRIPT_COMPILE);
|
||||
// let base = base::make_base();
|
||||
// compiler.emit_constant(base);
|
||||
// compiler.bind("base");
|
||||
|
||||
compiler.compile();
|
||||
|
||||
if DEBUG_SCRIPT_COMPILE {
|
||||
console_log!("=== source code ===");
|
||||
console_log!("{src}");
|
||||
println!("=== source code ===");
|
||||
println!("{src}");
|
||||
compiler.disassemble();
|
||||
console_log!("\n\n")
|
||||
println!("\n\n")
|
||||
}
|
||||
|
||||
if DEBUG_SCRIPT_RUN {
|
||||
console_log!("=== vm run ===");
|
||||
println!("=== vm run ===");
|
||||
}
|
||||
|
||||
let vm_chunk = compiler.chunk;
|
||||
|
||||
let mut world = World::new(vm_chunk, prelude.clone(), DEBUG_SCRIPT_RUN);
|
||||
world.run().await;
|
||||
let mut vm = Vm::new(vm_chunk, DEBUG_SCRIPT_RUN);
|
||||
let result = vm.run();
|
||||
|
||||
// TODO: actually do something useful on a panic
|
||||
// match result {
|
||||
// Some(Ok(val)) => val.show(),
|
||||
// Some(Err(panic)) => format!("Ludus panicked! {panic}"),
|
||||
// None => "Ludus run terminated by user".to_string()
|
||||
// };
|
||||
// if DEBUG_SCRIPT_RUN {
|
||||
// // vm.print_stack();
|
||||
// }
|
||||
let console = postlude.get("console").unwrap();
|
||||
let Value::Box(console) = console else {
|
||||
unreachable!()
|
||||
};
|
||||
let Value::List(ref lines) = *console.borrow() else {
|
||||
unreachable!()
|
||||
};
|
||||
let mut console = lines
|
||||
.iter()
|
||||
.map(|line| line.stringify())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
let turtle_commands = postlude.get("turtle_commands").unwrap();
|
||||
let Value::Box(commands) = turtle_commands else {
|
||||
unreachable!()
|
||||
};
|
||||
let commands = commands.borrow();
|
||||
dbg!(&commands);
|
||||
let commands = commands.to_json().unwrap();
|
||||
|
||||
let output = match result {
|
||||
Ok(val) => val.show(),
|
||||
Err(panic) => {
|
||||
console = format!("{console}\nLudus panicked! {panic}");
|
||||
"".to_string()
|
||||
}
|
||||
};
|
||||
if DEBUG_SCRIPT_RUN {
|
||||
vm.print_stack();
|
||||
}
|
||||
|
||||
// TODO: use serde_json to make this more robust?
|
||||
format!(
|
||||
"{{\"result\":\"{output}\",\"io\":{{\"stdout\":{{\"proto\":[\"text-stream\",\"0.1.0\"],\"data\":\"{console}\"}},\"turtle\":{{\"proto\":[\"turtle-graphics\",\"0.1.0\"],\"data\":{commands}}}}}}}"
|
||||
)
|
||||
}
|
||||
|
||||
pub fn fmt(src: &'static str) -> Result<String, String> {
|
||||
let (tokens, lex_errs) = lexer().parse(src).into_output_errors();
|
||||
if !lex_errs.is_empty() {
|
||||
println!("{:?}", lex_errs);
|
||||
return Err(format!("{:?}", lex_errs));
|
||||
}
|
||||
|
||||
let tokens = tokens.unwrap();
|
||||
|
||||
let (parse_result, parse_errors) = parser()
|
||||
.parse(Stream::from_iter(tokens).map((0..src.len()).into(), |(t, s)| (t, s)))
|
||||
.into_output_errors();
|
||||
if !parse_errors.is_empty() {
|
||||
return Err(format!("{:?}", parse_errors));
|
||||
}
|
||||
|
||||
// ::sigh:: The AST should be 'static
|
||||
// This simplifies lifetimes, and
|
||||
// in any event, the AST should live forever
|
||||
let parsed: &'static Spanned<Ast> = Box::leak(Box::new(parse_result.unwrap()));
|
||||
|
||||
Ok(parsed.0.show())
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use rudus::value::Value;
|
||||
use rudus::ludus;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
|
||||
pub fn main() {
|
||||
env::set_var("RUST_BACKTRACE", "1");
|
||||
println!("Hello, world.")
|
||||
let src = fs::read_to_string("sandbox.ld").unwrap();
|
||||
let json = ludus(src);
|
||||
println!("{json}");
|
||||
}
|
||||
|
|
70
src/op.rs
70
src/op.rs
|
@ -25,6 +25,7 @@ pub enum Op {
|
|||
MatchNil,
|
||||
MatchTrue,
|
||||
MatchFalse,
|
||||
PanicIfNoMatch,
|
||||
MatchConstant,
|
||||
MatchString,
|
||||
PushStringMatches,
|
||||
|
@ -50,12 +51,10 @@ pub enum Op {
|
|||
DropDictEntry,
|
||||
PushBox,
|
||||
GetKey,
|
||||
PanicWhenFallthrough,
|
||||
PanicNoWhen,
|
||||
JumpIfNoMatch,
|
||||
JumpIfMatch,
|
||||
PanicNoMatch,
|
||||
PanicNoLetMatch,
|
||||
PanicNoFnMatch,
|
||||
TypeOf,
|
||||
JumpBack,
|
||||
JumpIfZero,
|
||||
|
@ -83,6 +82,13 @@ pub enum Op {
|
|||
Assert,
|
||||
Get,
|
||||
At,
|
||||
|
||||
Not,
|
||||
Print,
|
||||
SetUpvalue,
|
||||
GetUpvalue,
|
||||
|
||||
Msg,
|
||||
// Inc,
|
||||
// Dec,
|
||||
// Gt,
|
||||
|
@ -90,25 +96,37 @@ pub enum Op {
|
|||
// Lt,
|
||||
// Lte,
|
||||
// Mod,
|
||||
// First,
|
||||
// Rest
|
||||
// Round,
|
||||
// Ceil,
|
||||
// Floor,
|
||||
// Random,
|
||||
// Sqrt,
|
||||
// Append,
|
||||
Not,
|
||||
Print,
|
||||
SetUpvalue,
|
||||
GetUpvalue,
|
||||
|
||||
Msg,
|
||||
// Assoc,
|
||||
// Concat,
|
||||
// Conj,
|
||||
// Count,
|
||||
// Disj,
|
||||
// Dissoc,
|
||||
// Range,
|
||||
// Rest,
|
||||
// Slice,
|
||||
|
||||
LoadMessage,
|
||||
NextMessage,
|
||||
MatchMessage,
|
||||
ClearMessage,
|
||||
SendMethod,
|
||||
Spawn,
|
||||
|
||||
LoadScrutinee,
|
||||
// "atan_2" math/atan2
|
||||
// "chars" chars
|
||||
// "cos" math/cos
|
||||
// "doc" doc
|
||||
// "downcase" string/ascii-lower
|
||||
// "pi" math/pi
|
||||
// "show" show
|
||||
// "sin" math/sin
|
||||
// "split" string/split
|
||||
// "str_slice" string/slice
|
||||
// "tan" math/tan
|
||||
// "trim" string/trim
|
||||
// "triml" string/triml
|
||||
// "trimr" string/trimr
|
||||
// "upcase" string/ascii-upper
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Op {
|
||||
|
@ -139,6 +157,7 @@ impl std::fmt::Display for Op {
|
|||
MatchTrue => "match_true",
|
||||
MatchFalse => "match_false",
|
||||
ResetMatch => "reset_match",
|
||||
PanicIfNoMatch => "panic_if_no_match",
|
||||
MatchConstant => "match_constant",
|
||||
MatchString => "match_string",
|
||||
PushStringMatches => "push_string_matches",
|
||||
|
@ -164,12 +183,10 @@ impl std::fmt::Display for Op {
|
|||
DropDictEntry => "drop_dict_entry",
|
||||
PushBox => "push_box",
|
||||
GetKey => "get_key",
|
||||
PanicWhenFallthrough => "panic_no_when",
|
||||
PanicNoWhen => "panic_no_when",
|
||||
JumpIfNoMatch => "jump_if_no_match",
|
||||
JumpIfMatch => "jump_if_match",
|
||||
PanicNoMatch => "panic_no_match",
|
||||
PanicNoFnMatch => "panic_no_fn_match",
|
||||
PanicNoLetMatch => "panic_no_let_match",
|
||||
TypeOf => "type_of",
|
||||
JumpBack => "jump_back",
|
||||
JumpIfZero => "jump_if_zero",
|
||||
|
@ -203,15 +220,6 @@ impl std::fmt::Display for Op {
|
|||
|
||||
SetUpvalue => "set_upvalue",
|
||||
GetUpvalue => "get_upvalue",
|
||||
|
||||
LoadMessage => "load_message",
|
||||
NextMessage => "next_message",
|
||||
MatchMessage => "match_message",
|
||||
ClearMessage => "clear_message",
|
||||
SendMethod => "send_method",
|
||||
Spawn => "spawn",
|
||||
|
||||
LoadScrutinee => "load_scrutinee",
|
||||
};
|
||||
write!(f, "{rep}")
|
||||
}
|
||||
|
|
47
src/panic.rs
47
src/panic.rs
|
@ -1,47 +0,0 @@
|
|||
use crate::errors::panic;
|
||||
use crate::value::Value;
|
||||
use crate::vm::CallFrame;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum PanicMsg {
|
||||
NoLetMatch,
|
||||
NoFnMatch,
|
||||
NoMatch,
|
||||
Generic(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PanicMsg {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
use PanicMsg::*;
|
||||
match self {
|
||||
NoLetMatch => write!(f, "no match in `let`"),
|
||||
NoFnMatch => write!(f, "no match calling fn"),
|
||||
NoMatch => write!(f, "no match in `match` form"),
|
||||
Generic(s) => write!(f, "{s}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Panic {
|
||||
pub msg: PanicMsg,
|
||||
pub scrutinee: Option<Value>,
|
||||
pub call_stack: Vec<CallFrame>,
|
||||
}
|
||||
|
||||
fn frame_dump(frame: &CallFrame) -> String {
|
||||
let dump = format!("stack name: {}\nspans: {:?}", frame, frame.chunk().spans);
|
||||
dump
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Panic {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let stub_trace = self
|
||||
.call_stack
|
||||
.iter()
|
||||
.map(frame_dump)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
write!(f, "Panic: {}\n{stub_trace}", self.msg)
|
||||
}
|
||||
}
|
602
src/parser.rs
602
src/parser.rs
|
@ -2,13 +2,467 @@
|
|||
// TODO: remove StringMatcher cruft
|
||||
// TODO: good error messages?
|
||||
|
||||
use crate::ast::{Ast, StringPart};
|
||||
use crate::lexer::*;
|
||||
use crate::spans::*;
|
||||
use chumsky::{input::ValueInput, prelude::*, recursive::Recursive};
|
||||
use std::fmt;
|
||||
|
||||
pub struct StringMatcher();
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum StringPart {
|
||||
Data(String),
|
||||
Word(String),
|
||||
Inline(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for StringPart {
|
||||
fn fmt(self: &StringPart, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let rep = match self {
|
||||
StringPart::Word(s) => format!("{{{s}}}"),
|
||||
StringPart::Data(s) => s.to_string(),
|
||||
StringPart::Inline(s) => s.to_string(),
|
||||
};
|
||||
write!(f, "{}", rep)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Ast {
|
||||
// a special Error node
|
||||
// may come in handy?
|
||||
Error,
|
||||
|
||||
And,
|
||||
Or,
|
||||
|
||||
// expression nodes
|
||||
Placeholder,
|
||||
Nil,
|
||||
Boolean(bool),
|
||||
Number(f64),
|
||||
Keyword(&'static str),
|
||||
Word(&'static str),
|
||||
String(&'static str),
|
||||
Interpolated(Vec<Spanned<StringPart>>),
|
||||
Block(Vec<Spanned<Self>>),
|
||||
If(Box<Spanned<Self>>, Box<Spanned<Self>>, Box<Spanned<Self>>),
|
||||
Tuple(Vec<Spanned<Self>>),
|
||||
Arguments(Vec<Spanned<Self>>),
|
||||
List(Vec<Spanned<Self>>),
|
||||
Dict(Vec<Spanned<Self>>),
|
||||
Let(Box<Spanned<Self>>, Box<Spanned<Self>>),
|
||||
LBox(&'static str, Box<Spanned<Self>>),
|
||||
Synthetic(Box<Spanned<Self>>, Box<Spanned<Self>>, Vec<Spanned<Self>>),
|
||||
When(Vec<Spanned<Self>>),
|
||||
WhenClause(Box<Spanned<Self>>, Box<Spanned<Self>>),
|
||||
Match(Box<Spanned<Self>>, Vec<Spanned<Self>>),
|
||||
MatchClause(
|
||||
Box<Spanned<Self>>,
|
||||
Box<Option<Spanned<Self>>>,
|
||||
Box<Spanned<Self>>,
|
||||
),
|
||||
Fn(&'static str, Box<Spanned<Ast>>, Option<&'static str>),
|
||||
FnBody(Vec<Spanned<Ast>>),
|
||||
FnDeclaration(&'static str),
|
||||
Panic(Box<Spanned<Self>>),
|
||||
Do(Vec<Spanned<Self>>),
|
||||
Repeat(Box<Spanned<Self>>, Box<Spanned<Self>>),
|
||||
Splat(&'static str),
|
||||
Pair(&'static str, Box<Spanned<Self>>),
|
||||
Loop(Box<Spanned<Self>>, Vec<Spanned<Self>>),
|
||||
Recur(Vec<Spanned<Self>>),
|
||||
|
||||
// pattern nodes
|
||||
NilPattern,
|
||||
BooleanPattern(bool),
|
||||
NumberPattern(f64),
|
||||
StringPattern(&'static str),
|
||||
InterpolatedPattern(Vec<Spanned<StringPart>>, StringMatcher),
|
||||
KeywordPattern(&'static str),
|
||||
WordPattern(&'static str),
|
||||
AsPattern(&'static str, &'static str),
|
||||
Splattern(Box<Spanned<Self>>),
|
||||
PlaceholderPattern,
|
||||
TuplePattern(Vec<Spanned<Self>>),
|
||||
ListPattern(Vec<Spanned<Self>>),
|
||||
PairPattern(&'static str, Box<Spanned<Self>>),
|
||||
DictPattern(Vec<Spanned<Self>>),
|
||||
}
|
||||
|
||||
impl Ast {
|
||||
pub fn show(&self) -> String {
|
||||
use Ast::*;
|
||||
match self {
|
||||
And => "and".to_string(),
|
||||
Or => "or".to_string(),
|
||||
Error => unreachable!(),
|
||||
Nil | NilPattern => "nil".to_string(),
|
||||
String(s) | StringPattern(s) => format!("\"{s}\""),
|
||||
Interpolated(strs) | InterpolatedPattern(strs, _) => {
|
||||
let mut out = "".to_string();
|
||||
out = format!("\"{out}");
|
||||
for (part, _) in strs {
|
||||
out = format!("{out}{part}");
|
||||
}
|
||||
format!("{out}\"")
|
||||
}
|
||||
Boolean(b) | BooleanPattern(b) => b.to_string(),
|
||||
Number(n) | NumberPattern(n) => n.to_string(),
|
||||
Keyword(k) | KeywordPattern(k) => format!(":{k}"),
|
||||
Word(w) | WordPattern(w) => w.to_string(),
|
||||
Block(lines) => {
|
||||
let mut out = "{\n".to_string();
|
||||
for (line, _) in lines {
|
||||
out = format!("{out}\n {}", line.show());
|
||||
}
|
||||
format!("{out}\n}}")
|
||||
}
|
||||
If(cond, then, r#else) => format!(
|
||||
"if {}\n then {}\n else {}",
|
||||
cond.0.show(),
|
||||
then.0.show(),
|
||||
r#else.0.show()
|
||||
),
|
||||
Let(pattern, expression) => {
|
||||
format!("let {} = {}", pattern.0.show(), expression.0.show())
|
||||
}
|
||||
Dict(entries) | DictPattern(entries) => {
|
||||
format!(
|
||||
"#{{{}}}",
|
||||
entries
|
||||
.iter()
|
||||
.map(|(pair, _)| pair.show())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
List(members) | ListPattern(members) => format!(
|
||||
"[{}]",
|
||||
members
|
||||
.iter()
|
||||
.map(|(member, _)| member.show())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
Arguments(members) => format!(
|
||||
"({})",
|
||||
members
|
||||
.iter()
|
||||
.map(|(member, _)| member.show())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
Tuple(members) | TuplePattern(members) => format!(
|
||||
"({})",
|
||||
members
|
||||
.iter()
|
||||
.map(|(member, _)| member.show())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
Synthetic(root, first, rest) => format!(
|
||||
"{} {} {}",
|
||||
root.0.show(),
|
||||
first.0.show(),
|
||||
rest.iter()
|
||||
.map(|(term, _)| term.show())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
),
|
||||
When(clauses) => format!(
|
||||
"when {{\n {}\n}}",
|
||||
clauses
|
||||
.iter()
|
||||
.map(|(clause, _)| clause.show())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n ")
|
||||
),
|
||||
Placeholder | PlaceholderPattern => "_".to_string(),
|
||||
LBox(name, rhs) => format!("box {name} = {}", rhs.0.show()),
|
||||
Match(scrutinee, clauses) => format!(
|
||||
"match {} with {{\n {}\n}}",
|
||||
scrutinee.0.show(),
|
||||
clauses
|
||||
.iter()
|
||||
.map(|(clause, _)| clause.show())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n ")
|
||||
),
|
||||
FnBody(clauses) => clauses
|
||||
.iter()
|
||||
.map(|(clause, _)| clause.show())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n "),
|
||||
Fn(name, body, doc) => {
|
||||
let mut out = format!("fn {name} {{\n");
|
||||
if let Some(doc) = doc {
|
||||
out = format!("{out} {doc}\n");
|
||||
}
|
||||
format!("{out} {}\n}}", body.0.show())
|
||||
}
|
||||
FnDeclaration(name) => format!("fn {name}"),
|
||||
Panic(expr) => format!("panic! {}", expr.0.show()),
|
||||
Do(terms) => {
|
||||
format!(
|
||||
"do {}",
|
||||
terms
|
||||
.iter()
|
||||
.map(|(term, _)| term.show())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" > ")
|
||||
)
|
||||
}
|
||||
Repeat(times, body) => format!("repeat {} {{\n{}\n}}", times.0.show(), body.0.show()),
|
||||
Splat(word) => format!("...{}", word),
|
||||
Splattern(pattern) => format!("...{}", pattern.0.show()),
|
||||
AsPattern(word, type_keyword) => format!("{word} as :{type_keyword}"),
|
||||
Pair(key, value) | PairPattern(key, value) => format!(":{key} {}", value.0.show()),
|
||||
Loop(init, body) => format!(
|
||||
"loop {} with {{\n {}\n}}",
|
||||
init.0.show(),
|
||||
body.iter()
|
||||
.map(|(clause, _)| clause.show())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n ")
|
||||
),
|
||||
Recur(args) => format!(
|
||||
"recur ({})",
|
||||
args.iter()
|
||||
.map(|(arg, _)| arg.show())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
MatchClause(pattern, guard, body) => {
|
||||
let mut out = pattern.0.show();
|
||||
if let Some(guard) = guard.as_ref() {
|
||||
out = format!("{out} if {}", guard.0.show());
|
||||
}
|
||||
format!("{out} -> {}", body.0.show())
|
||||
}
|
||||
WhenClause(cond, body) => format!("{} -> {}", cond.0.show(), body.0.show()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Ast {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use Ast::*;
|
||||
match self {
|
||||
And => write!(f, "And"),
|
||||
Or => write!(f, "Or"),
|
||||
Error => write!(f, "Error"),
|
||||
Nil => write!(f, "nil"),
|
||||
String(s) => write!(f, "String: \"{}\"", s),
|
||||
Interpolated(strs) => {
|
||||
write!(
|
||||
f,
|
||||
"Interpolated: \"{}\"",
|
||||
strs.iter()
|
||||
.map(|(s, _)| s.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
)
|
||||
}
|
||||
Boolean(b) => write!(f, "Boolean: {}", b),
|
||||
Number(n) => write!(f, "Number: {}", n),
|
||||
Keyword(k) => write!(f, "Keyword: :{}", k),
|
||||
Word(w) => write!(f, "Word: {}", w),
|
||||
Block(b) => write!(
|
||||
f,
|
||||
"Block: <{}>",
|
||||
b.iter()
|
||||
.map(|(line, _)| line.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
If(cond, then_branch, else_branch) => write!(
|
||||
f,
|
||||
"If: {} Then: {} Else: {}",
|
||||
cond.0, then_branch.0, else_branch.0
|
||||
),
|
||||
Let(pattern, expression) => {
|
||||
write!(f, "Let: {} = {}", pattern.0, expression.0)
|
||||
}
|
||||
Dict(entries) => write!(
|
||||
f,
|
||||
"#{{{}}}",
|
||||
entries
|
||||
.iter()
|
||||
.map(|pair| pair.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
List(l) => write!(
|
||||
f,
|
||||
"List: [{}]",
|
||||
l.iter()
|
||||
.map(|(line, _)| line.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
Arguments(a) => write!(
|
||||
f,
|
||||
"Arguments: ({})",
|
||||
a.iter()
|
||||
.map(|(line, _)| line.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
Tuple(t) => write!(
|
||||
f,
|
||||
"Tuple: ({})",
|
||||
t.iter()
|
||||
.map(|(line, _)| line.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
Synthetic(root, first, rest) => write!(
|
||||
f,
|
||||
"Synth: [{}, {}, {}]",
|
||||
root.0,
|
||||
first.0,
|
||||
rest.iter()
|
||||
.map(|(term, _)| term.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
When(clauses) => write!(
|
||||
f,
|
||||
"When: [{}]",
|
||||
clauses
|
||||
.iter()
|
||||
.map(|clause| clause.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
Placeholder => write!(f, "Placeholder"),
|
||||
LBox(_name, _rhs) => todo!(),
|
||||
Match(value, clauses) => {
|
||||
write!(
|
||||
f,
|
||||
"match: {} with {}",
|
||||
&value.0.to_string(),
|
||||
clauses
|
||||
.iter()
|
||||
.map(|clause| clause.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
)
|
||||
}
|
||||
FnBody(clauses) => {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
clauses
|
||||
.iter()
|
||||
.map(|clause| clause.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
)
|
||||
}
|
||||
Fn(name, body, ..) => {
|
||||
write!(f, "fn: {name}\n{}", body.0)
|
||||
}
|
||||
FnDeclaration(_name) => todo!(),
|
||||
Panic(_expr) => todo!(),
|
||||
Do(terms) => {
|
||||
write!(
|
||||
f,
|
||||
"do: {}",
|
||||
terms
|
||||
.iter()
|
||||
.map(|(term, _)| term.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" > ")
|
||||
)
|
||||
}
|
||||
Repeat(_times, _body) => todo!(),
|
||||
Splat(word) => {
|
||||
write!(f, "splat: {}", word)
|
||||
}
|
||||
Pair(k, v) => {
|
||||
write!(f, "pair: {} {}", k, v.0)
|
||||
}
|
||||
Loop(init, body) => {
|
||||
write!(
|
||||
f,
|
||||
"loop: {} with {}",
|
||||
init.0,
|
||||
body.iter()
|
||||
.map(|clause| clause.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
)
|
||||
}
|
||||
Recur(args) => {
|
||||
write!(
|
||||
f,
|
||||
"recur: {}",
|
||||
args.iter()
|
||||
.map(|(arg, _)| arg.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
MatchClause(pattern, guard, body) => {
|
||||
write!(
|
||||
f,
|
||||
"match clause: {} if {:?} -> {}",
|
||||
pattern.0, guard, body.0
|
||||
)
|
||||
}
|
||||
WhenClause(cond, body) => {
|
||||
write!(f, "when clause: {} -> {}", cond.0, body.0)
|
||||
}
|
||||
|
||||
NilPattern => write!(f, "nil"),
|
||||
BooleanPattern(b) => write!(f, "{}", b),
|
||||
NumberPattern(n) => write!(f, "{}", n),
|
||||
StringPattern(s) => write!(f, "{}", s),
|
||||
KeywordPattern(k) => write!(f, ":{}", k),
|
||||
WordPattern(w) => write!(f, "{}", w),
|
||||
AsPattern(w, t) => write!(f, "{} as :{}", w, t),
|
||||
Splattern(p) => write!(f, "...{}", p.0),
|
||||
PlaceholderPattern => write!(f, "_"),
|
||||
TuplePattern(t) => write!(
|
||||
f,
|
||||
"({})",
|
||||
t.iter()
|
||||
.map(|x| x.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
ListPattern(l) => write!(
|
||||
f,
|
||||
"({})",
|
||||
l.iter()
|
||||
.map(|x| x.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
DictPattern(entries) => write!(
|
||||
f,
|
||||
"#{{{}}}",
|
||||
entries
|
||||
.iter()
|
||||
.map(|(pair, _)| pair.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
PairPattern(key, value) => write!(f, ":{} {}", key, value.0),
|
||||
InterpolatedPattern(strprts, _) => write!(
|
||||
f,
|
||||
"interpolated: \"{}\"",
|
||||
strprts
|
||||
.iter()
|
||||
.map(|part| part.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StringMatcher(pub Box<dyn Fn(String) -> Option<Vec<(String, String)>>>);
|
||||
|
||||
impl PartialEq for StringMatcher {
|
||||
fn eq(&self, _other: &StringMatcher) -> bool {
|
||||
|
@ -79,7 +533,7 @@ fn parse_string(s: &'static str, span: SimpleSpan) -> Result<Vec<Spanned<StringP
|
|||
'}' => {
|
||||
if is_word {
|
||||
parts.push((
|
||||
StringPart::Word(current_part.leak()),
|
||||
StringPart::Word(current_part.clone()),
|
||||
SimpleSpan::new(span.context(), start..start + i),
|
||||
));
|
||||
current_part = String::new();
|
||||
|
@ -122,6 +576,54 @@ fn parse_string(s: &'static str, span: SimpleSpan) -> Result<Vec<Spanned<StringP
|
|||
Ok(parts)
|
||||
}
|
||||
|
||||
pub fn compile_string_pattern(parts: Vec<Spanned<StringPart>>) -> StringMatcher {
|
||||
StringMatcher(Box::new(move |scrutinee| {
|
||||
let mut last_match = 0;
|
||||
let mut parts_iter = parts.iter();
|
||||
let mut matches = vec![];
|
||||
while let Some((part, _)) = parts_iter.next() {
|
||||
match part {
|
||||
StringPart::Data(string) => match scrutinee.find(string.as_str()) {
|
||||
Some(i) => {
|
||||
// if i = 0, we're at the beginning
|
||||
if i == 0 && last_match == 0 {
|
||||
last_match = i + string.len();
|
||||
continue;
|
||||
}
|
||||
// in theory, we only hit this branch if the first part is Data
|
||||
unreachable!("internal Ludus error: bad string pattern")
|
||||
}
|
||||
None => return None,
|
||||
},
|
||||
StringPart::Word(word) => {
|
||||
let to_test = scrutinee.get(last_match..scrutinee.len()).unwrap();
|
||||
match parts_iter.next() {
|
||||
None => matches.push((word.clone(), to_test.to_string())),
|
||||
Some(part) => {
|
||||
let (StringPart::Data(part), _) = part else {
|
||||
unreachable!("internal Ludus error: bad string pattern")
|
||||
};
|
||||
match to_test.find(part) {
|
||||
None => return None,
|
||||
Some(i) => {
|
||||
matches.push((
|
||||
word.clone(),
|
||||
to_test.get(last_match..i).unwrap().to_string(),
|
||||
));
|
||||
last_match = i + part.len();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
Some(matches)
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn parser<I>(
|
||||
) -> impl Parser<'static, I, Spanned<Ast>, extra::Err<Rich<'static, Token, Span>>> + Clone
|
||||
where
|
||||
|
@ -141,15 +643,13 @@ where
|
|||
just(Token::Punctuation(","))
|
||||
.or(just(Token::Punctuation("\n")))
|
||||
.then(separators.clone().repeated())
|
||||
})
|
||||
.labelled("separator");
|
||||
});
|
||||
|
||||
let terminators = recursive(|terminators| {
|
||||
just(Token::Punctuation(";"))
|
||||
.or(just(Token::Punctuation("\n")))
|
||||
.then(terminators.clone().repeated())
|
||||
})
|
||||
.labelled("terminator");
|
||||
});
|
||||
|
||||
let placeholder_pattern =
|
||||
select! {Token::Punctuation("_") => PlaceholderPattern}.map_with(|p, e| (p, e.span()));
|
||||
|
@ -169,7 +669,10 @@ where
|
|||
match parsed {
|
||||
Ok(parts) => match parts[0] {
|
||||
(StringPart::Inline(_), _) => Ok((StringPattern(s), e.span())),
|
||||
_ => Ok((InterpolatedPattern(parts.clone()), e.span())),
|
||||
_ => Ok((
|
||||
InterpolatedPattern(parts.clone(), compile_string_pattern(parts)),
|
||||
e.span(),
|
||||
)),
|
||||
},
|
||||
Err(msg) => Err(Rich::custom(e.span(), msg)),
|
||||
}
|
||||
|
@ -209,29 +712,22 @@ where
|
|||
.allow_trailing()
|
||||
.collect()
|
||||
.delimited_by(just(Token::Punctuation("[")), just(Token::Punctuation("]")))
|
||||
.map_with(|list, e| (ListPattern(list), e.span()))
|
||||
.labelled("list pattern");
|
||||
.map_with(|list, e| (ListPattern(list), e.span()));
|
||||
|
||||
let key_pair_pattern = select! {Token::Keyword(k) => k}
|
||||
let pair_pattern = select! {Token::Keyword(k) => k}
|
||||
.then(pattern.clone())
|
||||
.map_with(|(key, patt), e| (KeyPairPattern(key, Box::new(patt)), e.span()));
|
||||
.map_with(|(key, patt), e| (PairPattern(key, Box::new(patt)), e.span()));
|
||||
|
||||
let shorthand_pattern = select! {Token::Word(w) => w}.map_with(|w, e| {
|
||||
(
|
||||
KeyPairPattern(w, Box::new((WordPattern(w), e.span()))),
|
||||
PairPattern(w, Box::new((WordPattern(w), e.span()))),
|
||||
e.span(),
|
||||
)
|
||||
});
|
||||
|
||||
let str_pair_pattern = select! {Token::String(s) => s}
|
||||
.then(pattern.clone())
|
||||
.map_with(|(key, patt), e| (StrPairPattern(key, Box::new(patt)), e.span()));
|
||||
|
||||
let dict_pattern = key_pair_pattern
|
||||
let dict_pattern = pair_pattern
|
||||
.or(shorthand_pattern)
|
||||
.or(str_pair_pattern)
|
||||
.or(splattern.clone())
|
||||
.labelled("pair pattern")
|
||||
.separated_by(separators.clone())
|
||||
.allow_leading()
|
||||
.allow_trailing()
|
||||
|
@ -242,14 +738,11 @@ where
|
|||
)
|
||||
.map_with(|dict, e| (DictPattern(dict), e.span()));
|
||||
|
||||
let keyword = select! {Token::Keyword(k) => Keyword(k)}
|
||||
.map_with(|k, e| (k, e.span()))
|
||||
.labelled("keyword");
|
||||
let keyword = select! {Token::Keyword(k) => Keyword(k),}.map_with(|k, e| (k, e.span()));
|
||||
|
||||
let as_pattern = select! {Token::Word(w) => w}
|
||||
.then_ignore(just(Token::Reserved("as")))
|
||||
.then(select! {Token::Keyword(k) => k})
|
||||
.labelled("keyword")
|
||||
.map_with(|(w, t), e| (AsPattern(w, t), e.span()));
|
||||
|
||||
pattern.define(
|
||||
|
@ -296,8 +789,7 @@ where
|
|||
.allow_trailing()
|
||||
.collect()
|
||||
.delimited_by(just(Token::Punctuation("(")), just(Token::Punctuation(")")))
|
||||
.map_with(|tuple, e| (Tuple(tuple), e.span()))
|
||||
.labelled("tuple");
|
||||
.map_with(|tuple, e| (Tuple(tuple), e.span()));
|
||||
|
||||
let args = simple
|
||||
.clone()
|
||||
|
@ -307,21 +799,15 @@ where
|
|||
.allow_trailing()
|
||||
.collect()
|
||||
.delimited_by(just(Token::Punctuation("(")), just(Token::Punctuation(")")))
|
||||
.map_with(|args, e| (Arguments(args), e.span()))
|
||||
.labelled("args");
|
||||
.map_with(|args, e| (Arguments(args), e.span()));
|
||||
|
||||
let or = just(Token::Reserved("or")).map_with(|_, e| (Or, e.span()));
|
||||
|
||||
let and = just(Token::Reserved("and")).map_with(|_, e| (And, e.span()));
|
||||
|
||||
let method = select!(Token::Method(m) => m)
|
||||
.then(tuple.clone())
|
||||
.map_with(|(m, t), e| (Ast::Method(m, Box::new(t)), e.span()))
|
||||
.labelled("method");
|
||||
|
||||
let synth_root = or.or(and).or(word).or(keyword);
|
||||
|
||||
let synth_term = keyword.or(args).or(method);
|
||||
let synth_term = keyword.or(args);
|
||||
|
||||
let synthetic = synth_root
|
||||
.then(synth_term.clone())
|
||||
|
@ -337,8 +823,7 @@ where
|
|||
Splat(if let Word(w) = w { w } else { unreachable!() }),
|
||||
e.span(),
|
||||
)
|
||||
})
|
||||
.labelled("...");
|
||||
});
|
||||
|
||||
let list = simple
|
||||
.clone()
|
||||
|
@ -350,20 +835,15 @@ where
|
|||
.delimited_by(just(Token::Punctuation("[")), just(Token::Punctuation("]")))
|
||||
.map_with(|list, e| (List(list), e.span()));
|
||||
|
||||
let key_pair = select! {Token::Keyword(k) => k}
|
||||
let pair = select! {Token::Keyword(k) => k}
|
||||
.then(simple.clone())
|
||||
.map_with(|(key, value), e| (KeywordPair(key, Box::new(value)), e.span()));
|
||||
.map_with(|(key, value), e| (Pair(key, Box::new(value)), e.span()));
|
||||
|
||||
let shorthand = select! {Token::Word(w) => w}
|
||||
.map_with(|w, e| (KeywordPair(w, Box::new((Word(w), e.span()))), e.span()));
|
||||
.map_with(|w, e| (Pair(w, Box::new((Word(w), e.span()))), e.span()));
|
||||
|
||||
let str_pair = select! {Token::String(s) => s}
|
||||
.then(simple.clone())
|
||||
.map_with(|(key, value), e| (StringPair(key, Box::new(value)), e.span()));
|
||||
|
||||
let dict = key_pair
|
||||
let dict = pair
|
||||
.or(shorthand)
|
||||
.or(str_pair)
|
||||
.or(splat.clone())
|
||||
.separated_by(separators.clone())
|
||||
.allow_leading()
|
||||
|
@ -402,7 +882,7 @@ where
|
|||
|span| (Error, span),
|
||||
)));
|
||||
|
||||
let r#if = just(Token::Reserved("if"))
|
||||
let if_ = just(Token::Reserved("if"))
|
||||
.ignore_then(simple.clone())
|
||||
.then_ignore(terminators.clone().or_not())
|
||||
.then_ignore(just(Token::Reserved("then")))
|
||||
|
@ -468,7 +948,7 @@ where
|
|||
.then(
|
||||
match_clause
|
||||
.clone()
|
||||
.or(guarded_clause.clone())
|
||||
.or(guarded_clause)
|
||||
.separated_by(terminators.clone())
|
||||
.allow_leading()
|
||||
.allow_trailing()
|
||||
|
@ -477,28 +957,15 @@ where
|
|||
)
|
||||
.map_with(|(expr, clauses), e| (Match(Box::new(expr), clauses), e.span()));
|
||||
|
||||
let receive = just(Token::Reserved("receive"))
|
||||
.ignore_then(
|
||||
match_clause
|
||||
.clone()
|
||||
.or(guarded_clause)
|
||||
.separated_by(terminators.clone())
|
||||
.allow_leading()
|
||||
.allow_trailing()
|
||||
.collect()
|
||||
.delimited_by(just(Token::Punctuation("{")), just(Token::Punctuation("}"))),
|
||||
)
|
||||
.map_with(|clauses, e| (Receive(clauses), e.span()));
|
||||
|
||||
let conditional = when.or(r#if).or(r#match).or(receive);
|
||||
let conditional = when.or(if_).or(r#match);
|
||||
|
||||
let panic = just(Token::Reserved("panic!"))
|
||||
.ignore_then(nonbinding.clone())
|
||||
.map_with(|expr, e| (Panic(Box::new(expr)), e.span()));
|
||||
|
||||
let r#do = just(Token::Reserved("do"))
|
||||
let do_ = just(Token::Reserved("do"))
|
||||
.ignore_then(
|
||||
simple
|
||||
nonbinding
|
||||
.clone()
|
||||
.separated_by(
|
||||
just(Token::Punctuation(">")).then(just(Token::Punctuation("\n")).repeated()),
|
||||
|
@ -512,10 +979,6 @@ where
|
|||
.then(block.clone())
|
||||
.map_with(|(count, body), e| (Repeat(Box::new(count), Box::new(body)), e.span()));
|
||||
|
||||
let spawn = just(Token::Reserved("spawn!"))
|
||||
.ignore_then(nonbinding.clone())
|
||||
.map_with(|body, e| (Spawn(Box::new(body)), e.span()));
|
||||
|
||||
let fn_guarded = tuple_pattern
|
||||
.clone()
|
||||
.then_ignore(just(Token::Reserved("if")))
|
||||
|
@ -581,9 +1044,7 @@ where
|
|||
.or(tuple.clone())
|
||||
.or(list)
|
||||
.or(dict)
|
||||
.or(panic)
|
||||
.or(string)
|
||||
.or(r#do)
|
||||
.or(lambda.clone())
|
||||
.labelled("simple expression"),
|
||||
);
|
||||
|
@ -593,8 +1054,9 @@ where
|
|||
.clone()
|
||||
.or(conditional)
|
||||
.or(block)
|
||||
.or(panic)
|
||||
.or(do_)
|
||||
.or(repeat)
|
||||
.or(spawn)
|
||||
.or(r#loop)
|
||||
.labelled("nonbinding expression"),
|
||||
);
|
||||
|
|
115
src/validator.rs
115
src/validator.rs
|
@ -1,11 +1,12 @@
|
|||
// TODO:
|
||||
// * [ ] ensure `or` and `and` never get passed by reference
|
||||
// * [ ] ensure no placeholder in `or` and `and` args
|
||||
// * [ ] ensure loops have fixed arity (no splats)
|
||||
// * [ ] ensure fn pattern splats are always highest (and same) arity
|
||||
|
||||
use crate::ast::{Ast, StringPart};
|
||||
use crate::parser::*;
|
||||
use crate::spans::{Span, Spanned};
|
||||
use crate::value::{Key, Value};
|
||||
use std::cmp::max;
|
||||
use crate::value::Value;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -60,7 +61,7 @@ fn match_arities(arities: &HashSet<Arity>, num_args: u8) -> bool {
|
|||
#[derive(Debug, PartialEq)]
|
||||
pub struct Validator<'a> {
|
||||
pub locals: Vec<(String, &'a Span, FnInfo)>,
|
||||
pub prelude: imbl::HashMap<Key, Value>,
|
||||
pub prelude: imbl::HashMap<&'static str, Value>,
|
||||
pub input: &'static str,
|
||||
pub src: &'static str,
|
||||
pub ast: &'a Ast,
|
||||
|
@ -76,7 +77,7 @@ impl<'a> Validator<'a> {
|
|||
span: &'a Span,
|
||||
input: &'static str,
|
||||
src: &'static str,
|
||||
prelude: imbl::HashMap<Key, Value>,
|
||||
prelude: imbl::HashMap<&'static str, Value>,
|
||||
) -> Validator<'a> {
|
||||
Validator {
|
||||
input,
|
||||
|
@ -103,27 +104,18 @@ impl<'a> Validator<'a> {
|
|||
}
|
||||
|
||||
fn declare_fn(&mut self, name: String) {
|
||||
if name.is_empty() {
|
||||
return;
|
||||
}
|
||||
self.locals.push((name, self.span, FnInfo::Declared));
|
||||
}
|
||||
|
||||
fn define_fn(&mut self, name: String, info: FnInfo) {
|
||||
if name.is_empty() {
|
||||
return;
|
||||
}
|
||||
let i = self.locals.iter().position(|(n, ..)| *n == name).unwrap();
|
||||
let new_binding = (name, self.locals[i].1, info);
|
||||
self.locals[i] = new_binding;
|
||||
}
|
||||
|
||||
fn resolved(&self, name: &'static str) -> bool {
|
||||
fn resolved(&self, name: &str) -> bool {
|
||||
self.locals.iter().any(|(bound, ..)| name == bound.as_str())
|
||||
|| self
|
||||
.prelude
|
||||
.iter()
|
||||
.any(|(bound, _)| Key::Keyword(name) == *bound)
|
||||
|| self.prelude.iter().any(|(bound, _)| name == *bound)
|
||||
}
|
||||
|
||||
fn bound(&self, name: &str) -> Option<&(String, &Span, FnInfo)> {
|
||||
|
@ -169,7 +161,6 @@ impl<'a> Validator<'a> {
|
|||
let root = self.ast;
|
||||
match root {
|
||||
Error => unreachable!(),
|
||||
Spawn(body) => self.visit(body),
|
||||
Word(name) | Splat(name) => {
|
||||
if !self.resolved(name) {
|
||||
self.err(format!("unbound name `{name}`"))
|
||||
|
@ -181,7 +172,7 @@ impl<'a> Validator<'a> {
|
|||
for part in parts {
|
||||
if let (StringPart::Word(name), span) = part {
|
||||
self.span = span;
|
||||
if !self.resolved(name) {
|
||||
if !self.resolved(name.as_str()) {
|
||||
self.err(format!("unbound name `{name}`"));
|
||||
} else {
|
||||
self.use_name(name.to_string());
|
||||
|
@ -235,9 +226,6 @@ impl<'a> Validator<'a> {
|
|||
if members.is_empty() {
|
||||
return;
|
||||
}
|
||||
if members.len() > 7 {
|
||||
self.err("tuples may not have more than 7 members".to_string());
|
||||
}
|
||||
let tailpos = self.status.tail_position;
|
||||
self.status.tail_position = false;
|
||||
for member in members {
|
||||
|
@ -250,9 +238,6 @@ impl<'a> Validator<'a> {
|
|||
if args.is_empty() {
|
||||
return;
|
||||
}
|
||||
if args.len() > 7 {
|
||||
self.err("tuples may not have more than 7 members".to_string());
|
||||
}
|
||||
let tailpos = self.status.tail_position;
|
||||
self.status.tail_position = false;
|
||||
for arg in args {
|
||||
|
@ -282,7 +267,7 @@ impl<'a> Validator<'a> {
|
|||
|
||||
self.status.tail_position = tailpos;
|
||||
}
|
||||
KeywordPair(_, value) | StringPair(_, value) => self.visit(value.as_ref()),
|
||||
Pair(_, value) => self.visit(value.as_ref()),
|
||||
Dict(dict) => {
|
||||
if dict.is_empty() {
|
||||
return;
|
||||
|
@ -299,13 +284,6 @@ impl<'a> Validator<'a> {
|
|||
// check arity against fn info if first term is word and second term is args
|
||||
Synthetic(first, second, rest) => {
|
||||
match (&first.0, &second.0) {
|
||||
(Ast::Word(_), Ast::Method(_, args)) => {
|
||||
self.visit(first.as_ref());
|
||||
self.visit(args);
|
||||
}
|
||||
(Ast::Keyword(_), Ast::Method(_, args)) => {
|
||||
self.visit(args);
|
||||
}
|
||||
(Ast::And, Ast::Arguments(_)) | (Ast::Or, Ast::Arguments(_)) => {
|
||||
self.visit(second.as_ref())
|
||||
}
|
||||
|
@ -386,11 +364,6 @@ impl<'a> Validator<'a> {
|
|||
self.visit(clause);
|
||||
}
|
||||
}
|
||||
Receive(clauses) => {
|
||||
for clause in clauses {
|
||||
self.visit(clause);
|
||||
}
|
||||
}
|
||||
FnDeclaration(name) => {
|
||||
let tailpos = self.status.tail_position;
|
||||
self.status.tail_position = false;
|
||||
|
@ -434,43 +407,6 @@ impl<'a> Validator<'a> {
|
|||
self.validate();
|
||||
}
|
||||
|
||||
let mut max_arity = 0;
|
||||
let mut has_splat = false;
|
||||
for arity in arities.iter() {
|
||||
match arity {
|
||||
Arity::Fixed(n) => {
|
||||
max_arity = max(*n, max_arity);
|
||||
}
|
||||
Arity::Splat(n) => {
|
||||
max_arity = max(*n, max_arity);
|
||||
has_splat = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for arity in arities.iter() {
|
||||
match arity {
|
||||
Arity::Fixed(n) => {
|
||||
if *n == max_arity && has_splat {
|
||||
self.err(
|
||||
"splats must be the longest arity in function clauses"
|
||||
.to_string(),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Arity::Splat(n) => {
|
||||
if *n > max_arity {
|
||||
self.err(
|
||||
"splats must be the longest arity in function clauses"
|
||||
.to_string(),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collect info about what the function closes over
|
||||
let mut closed_over = HashSet::new();
|
||||
for binding in self.status.used_bindings.iter().skip(from) {
|
||||
|
@ -545,7 +481,7 @@ impl<'a> Validator<'a> {
|
|||
}
|
||||
Arity::Splat(clause_arity) => {
|
||||
if clause_arity > loop_arity {
|
||||
self.err("loop clauses may not have splats".to_string())
|
||||
self.err(format!("mismathced arity: expected {loop_arity} arguments in `loop` clause; this clause takes {clause_arity} or more"))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -564,9 +500,6 @@ impl<'a> Validator<'a> {
|
|||
if !self.status.tail_position {
|
||||
self.err("you may only use `recur` in tail position".to_string());
|
||||
}
|
||||
if args.len() > 7 {
|
||||
self.err("tuples may only have 7 members".to_string());
|
||||
}
|
||||
|
||||
let num_args = args.len() as u8;
|
||||
let loop_arity = self.status.loop_arity;
|
||||
|
@ -587,7 +520,7 @@ impl<'a> Validator<'a> {
|
|||
self.bind(name.to_string());
|
||||
}
|
||||
},
|
||||
InterpolatedPattern(parts) => {
|
||||
InterpolatedPattern(parts, _) => {
|
||||
for (part, span) in parts {
|
||||
if let StringPart::Word(name) = part {
|
||||
self.span = span;
|
||||
|
@ -634,25 +567,7 @@ impl<'a> Validator<'a> {
|
|||
}
|
||||
}
|
||||
}
|
||||
TuplePattern(terms) => {
|
||||
if terms.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if terms.len() > 7 {
|
||||
self.err("tuples may only have 7 arguments".to_string())
|
||||
}
|
||||
|
||||
for term in terms.iter().take(terms.len() - 1) {
|
||||
self.visit(term);
|
||||
}
|
||||
|
||||
self.status.last_term = true;
|
||||
let last = terms.last().unwrap();
|
||||
self.visit(last);
|
||||
self.status.last_term = false;
|
||||
}
|
||||
ListPattern(terms) | DictPattern(terms) => {
|
||||
TuplePattern(terms) | ListPattern(terms) | DictPattern(terms) => {
|
||||
if terms.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
@ -665,9 +580,9 @@ impl<'a> Validator<'a> {
|
|||
self.visit(last);
|
||||
self.status.last_term = false;
|
||||
}
|
||||
KeyPairPattern(_, patt) | StrPairPattern(_, patt) => self.visit(patt.as_ref()),
|
||||
PairPattern(_, patt) => self.visit(patt.as_ref()),
|
||||
// terminals can never be invalid
|
||||
Nil | Boolean(_) | Number(_) | Keyword(_) | String(_) | And | Or | Method(..) => (),
|
||||
Nil | Boolean(_) | Number(_) | Keyword(_) | String(_) | And | Or => (),
|
||||
// terminal patterns can never be invalid
|
||||
NilPattern | BooleanPattern(..) | NumberPattern(..) | StringPattern(..)
|
||||
| KeywordPattern(..) | PlaceholderPattern => (),
|
||||
|
|
277
src/value.rs
277
src/value.rs
|
@ -1,8 +1,8 @@
|
|||
use crate::base::BaseFn;
|
||||
use crate::chunk::Chunk;
|
||||
// use crate::parser::Ast;
|
||||
// use crate::spans::Spanned;
|
||||
use imbl::{HashMap, Vector};
|
||||
use ordered_float::NotNan;
|
||||
use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
@ -18,7 +18,6 @@ pub enum LFn {
|
|||
chunks: Vec<Chunk>,
|
||||
splat: u8,
|
||||
closed: RefCell<Vec<Value>>,
|
||||
patterns: Vec<String>,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -37,26 +36,19 @@ impl LFn {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn patterns(&self) -> Value {
|
||||
match self {
|
||||
LFn::Declared { .. } => unreachable!(),
|
||||
LFn::Defined { patterns, .. } => Value::from_string(
|
||||
patterns
|
||||
.iter()
|
||||
.map(|pattern| format!(" {pattern}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn doc(&self) -> Value {
|
||||
match self {
|
||||
LFn::Declared { name } => {
|
||||
Value::String(Rc::new(format!("fn {name}: undefined function")))
|
||||
}
|
||||
LFn::Defined { doc: Some(doc), .. } => Value::String(Rc::new(doc.to_string())),
|
||||
LFn::Defined { .. } => Value::String(Rc::new("no documentation found".to_string())),
|
||||
LFn::Defined {
|
||||
name,
|
||||
doc: Some(doc),
|
||||
..
|
||||
} => Value::String(Rc::new(format!("fn {name}\n{doc}"))),
|
||||
LFn::Defined { name, .. } => {
|
||||
Value::String(Rc::new(format!("fn {name}: no documentation")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,55 +114,6 @@ pub struct Partial {
|
|||
pub function: Value,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Key {
|
||||
Keyword(&'static str),
|
||||
Interned(&'static str),
|
||||
String(Rc<String>),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Key {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Key::Keyword(s) => write!(f, ":{s}"),
|
||||
Key::Interned(s) => write!(f, "\"{s}\""),
|
||||
Key::String(s) => write!(f, "\"{s}\""),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Key {
|
||||
fn serialize<S>(&self, srlzr: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
Key::Keyword(s) => srlzr.serialize_str(s),
|
||||
Key::Interned(s) => srlzr.serialize_str(s),
|
||||
Key::String(s) => srlzr.serialize_str(s.as_str()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Key {
|
||||
pub fn to_value(&self) -> Value {
|
||||
match self {
|
||||
Key::Keyword(s) => Value::Keyword(s),
|
||||
Key::Interned(s) => Value::Interned(s),
|
||||
Key::String(s) => Value::String(s.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_value(value: Value) -> Key {
|
||||
match value {
|
||||
Value::Keyword(s) => Key::Keyword(s),
|
||||
Value::Interned(s) => Key::Interned(s),
|
||||
Value::String(s) => Key::String(s.clone()),
|
||||
_ => unreachable!("dict keys must be keywords or strings"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Value {
|
||||
Nothing,
|
||||
|
@ -180,15 +123,14 @@ pub enum Value {
|
|||
Keyword(&'static str),
|
||||
Interned(&'static str),
|
||||
String(Rc<String>),
|
||||
Number(NotNan<f64>),
|
||||
Number(f64),
|
||||
Tuple(Rc<Vec<Value>>),
|
||||
List(Box<Vector<Value>>),
|
||||
Dict(Box<HashMap<Key, Value>>), // not hashable b/c why?
|
||||
Box(Rc<RefCell<Value>>), // not hashable b/c refcell
|
||||
Fn(Rc<LFn>), // not hashable b/c refcell
|
||||
BaseFn(Box<BaseFn>),
|
||||
Dict(Box<HashMap<&'static str, Value>>),
|
||||
Box(Rc<RefCell<Value>>),
|
||||
Fn(Rc<LFn>),
|
||||
BaseFn(BaseFn),
|
||||
Partial(Rc<Partial>),
|
||||
Process,
|
||||
}
|
||||
|
||||
impl PartialEq for Value {
|
||||
|
@ -225,7 +167,6 @@ impl std::fmt::Display for Value {
|
|||
Interned(str) => write!(f, "\"{str}\""),
|
||||
String(str) => write!(f, "\"{str}\""),
|
||||
Number(n) => write!(f, "{n}"),
|
||||
Process => write!(f, "Process"),
|
||||
Tuple(members) => write!(
|
||||
f,
|
||||
"({})",
|
||||
|
@ -256,7 +197,7 @@ impl std::fmt::Display for Value {
|
|||
Box(value) => write!(f, "box {{ {} }}", value.as_ref().borrow()),
|
||||
Fn(lfn) => write!(f, "fn {}", lfn.name()),
|
||||
BaseFn(inner) => {
|
||||
let name = match **inner {
|
||||
let name = match inner {
|
||||
crate::base::BaseFn::Nullary(name, _)
|
||||
| crate::base::BaseFn::Unary(name, _)
|
||||
| crate::base::BaseFn::Binary(name, _)
|
||||
|
@ -269,56 +210,10 @@ impl std::fmt::Display for Value {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for Value {
|
||||
fn serialize<S>(&self, srlzr: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
use Value::*;
|
||||
match self {
|
||||
Nil => srlzr.serialize_none(),
|
||||
True => srlzr.serialize_bool(true),
|
||||
False => srlzr.serialize_bool(false),
|
||||
Number(n) => srlzr.serialize_f64(f64::from(*n)),
|
||||
Interned(s) => srlzr.serialize_str(s),
|
||||
Keyword(k) => srlzr.serialize_str(k),
|
||||
String(s) => srlzr.serialize_str(s.as_str()),
|
||||
Tuple(t) => {
|
||||
let mut seq = srlzr.serialize_seq(Some(t.len()))?;
|
||||
for e in t.iter() {
|
||||
seq.serialize_element(e)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
List(l) => {
|
||||
let mut seq = srlzr.serialize_seq(Some(l.len()))?;
|
||||
for e in l.iter() {
|
||||
seq.serialize_element(e)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
Dict(d) => {
|
||||
let mut map = srlzr.serialize_map(Some(d.len()))?;
|
||||
for (k, v) in d.iter() {
|
||||
map.serialize_entry(k, v)?;
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
Box(b) => {
|
||||
let boxed = b.borrow();
|
||||
(*boxed).serialize(srlzr)
|
||||
}
|
||||
Fn(..) | BaseFn(..) | Partial(..) => unreachable!(),
|
||||
Process | Nothing => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn show(&self) -> String {
|
||||
use Value::*;
|
||||
let mut out = match &self {
|
||||
Process => "Process".to_string(),
|
||||
Nil => "nil".to_string(),
|
||||
True => "true".to_string(),
|
||||
False => "false".to_string(),
|
||||
|
@ -338,8 +233,9 @@ impl Value {
|
|||
let members = d
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
let key_show = Value::Keyword(k).show();
|
||||
let value_show = v.show();
|
||||
format!("{k} {value_show}")
|
||||
format!("{key_show} {value_show}")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
@ -351,18 +247,67 @@ impl Value {
|
|||
BaseFn(_) => format!("{self}"),
|
||||
Nothing => "_".to_string(),
|
||||
};
|
||||
if out.len() > 1000 {
|
||||
out.truncate(997);
|
||||
if out.len() > 20 {
|
||||
out.truncate(20);
|
||||
format!("{out}...")
|
||||
} else {
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_json(&self) -> Option<String> {
|
||||
use Value::*;
|
||||
match self {
|
||||
True | False | String(..) | Interned(..) | Number(..) => Some(self.show()),
|
||||
Keyword(str) => Some(format!("\"{str}\"")),
|
||||
List(members) => {
|
||||
let mut joined = "".to_string();
|
||||
let mut members = members.iter();
|
||||
if let Some(member) = members.next() {
|
||||
joined = member.to_json()?;
|
||||
}
|
||||
for member in members {
|
||||
let json = member.to_json()?;
|
||||
joined = format!("{joined},{json}");
|
||||
}
|
||||
Some(format!("[{joined}]"))
|
||||
}
|
||||
Tuple(members) => {
|
||||
let mut joined = "".to_string();
|
||||
let mut members = members.iter();
|
||||
if let Some(member) = members.next() {
|
||||
joined = member.to_json()?;
|
||||
}
|
||||
for member in members {
|
||||
let json = member.to_json()?;
|
||||
joined = format!("{joined},{json}");
|
||||
}
|
||||
Some(format!("[{joined}]"))
|
||||
}
|
||||
Dict(members) => {
|
||||
let mut joined = "".to_string();
|
||||
let mut members = members.iter();
|
||||
if let Some((key, value)) = members.next() {
|
||||
let json = value.to_json()?;
|
||||
joined = format!("\"{key}\":{json}")
|
||||
}
|
||||
for (key, value) in members {
|
||||
let json = value.to_json()?;
|
||||
joined = format!("{joined},\"{key}\": {json}");
|
||||
}
|
||||
Some(format!("{{{joined}}}"))
|
||||
}
|
||||
not_serializable => {
|
||||
println!("Cannot convert to json:");
|
||||
dbg!(not_serializable);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stringify(&self) -> String {
|
||||
use Value::*;
|
||||
match &self {
|
||||
Process => "process".to_string(),
|
||||
Nil => "nil".to_string(),
|
||||
True => "true".to_string(),
|
||||
False => "false".to_string(),
|
||||
|
@ -389,8 +334,9 @@ impl Value {
|
|||
let members = d
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
let key_show = Value::Keyword(k).stringify();
|
||||
let value_show = v.stringify();
|
||||
format!("{k} {value_show}")
|
||||
format!("{key_show} {value_show}")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
@ -423,88 +369,13 @@ impl Value {
|
|||
Fn(..) => "fn",
|
||||
BaseFn(..) => "fn",
|
||||
Partial(..) => "fn",
|
||||
Process => "process",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_fn(&self) -> &LFn {
|
||||
match self {
|
||||
Value::Fn(ref inner) => inner,
|
||||
_ => unreachable!("expected value to be fn"),
|
||||
Value::Fn(inner) => inner.as_ref(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_list(&self) -> &Vector<Value> {
|
||||
match self {
|
||||
Value::List(ref inner) => inner,
|
||||
_ => unreachable!("expected value to be list"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_box(&self) -> Rc<RefCell<Value>> {
|
||||
match self {
|
||||
Value::Box(inner) => inner.clone(),
|
||||
_ => unreachable!("expected value to be a box"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_string(&self) -> Rc<String> {
|
||||
match self {
|
||||
Value::String(str) => str.clone(),
|
||||
Value::Interned(str) => Rc::new(str.to_string()),
|
||||
_ => unreachable!("expected value to be a string"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_tuple(&self) -> Rc<Vec<Value>> {
|
||||
match self {
|
||||
Value::Tuple(members) => members.clone(),
|
||||
_ => unreachable!("expected value to be a tuple"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_string(str: String) -> Value {
|
||||
Value::String(Rc::new(str))
|
||||
}
|
||||
|
||||
pub fn list(list: Vector<Value>) -> Value {
|
||||
Value::List(Box::new(list))
|
||||
}
|
||||
|
||||
pub fn new_list() -> Value {
|
||||
Value::list(Vector::new())
|
||||
}
|
||||
|
||||
pub fn r#box(value: Value) -> Value {
|
||||
Value::Box(Rc::new(RefCell::new(value)))
|
||||
}
|
||||
|
||||
pub fn tuple(vec: Vec<Value>) -> Value {
|
||||
Value::Tuple(Rc::new(vec))
|
||||
}
|
||||
|
||||
pub fn from_f64(f: f64) -> Value {
|
||||
Value::Number(NotNan::new(f).unwrap())
|
||||
}
|
||||
|
||||
pub fn from_usize(n: usize) -> Value {
|
||||
Value::Number(NotNan::new(n as f64).unwrap())
|
||||
}
|
||||
|
||||
pub fn as_f64(&self) -> f64 {
|
||||
match self {
|
||||
Value::Number(n) => f64::from(*n),
|
||||
_ => unreachable!("expected value to be a number"),
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn get_shared_box(&self, name: &'static str) -> Value {
|
||||
// match self {
|
||||
// Value::Dict(dict) => dict
|
||||
// .get(name)
|
||||
// .expect("expected dict to have requested value")
|
||||
// .clone(),
|
||||
// _ => unreachable!("expected dict"),
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
540
src/world.rs
540
src/world.rs
|
@ -1,540 +0,0 @@
|
|||
use chumsky::primitive::NoneOf;
|
||||
|
||||
use crate::chunk::Chunk;
|
||||
use crate::value::{Value, Key};
|
||||
use crate::vm::Creature;
|
||||
use crate::panic::Panic;
|
||||
use crate::errors::panic;
|
||||
use crate::js::*;
|
||||
use crate::io::{MsgOut, MsgIn, do_io};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::mem::swap;
|
||||
use std::rc::Rc;
|
||||
|
||||
const ANIMALS: [&str; 32] = [
|
||||
"tortoise",
|
||||
"hare",
|
||||
"squirrel",
|
||||
"hawk",
|
||||
"woodpecker",
|
||||
"cardinal",
|
||||
"coyote",
|
||||
"raccoon",
|
||||
"rat",
|
||||
"axolotl",
|
||||
"cormorant",
|
||||
"duck",
|
||||
"orca",
|
||||
"humbpack",
|
||||
"tern",
|
||||
"quokka",
|
||||
"koala",
|
||||
"kangaroo",
|
||||
"zebra",
|
||||
"hyena",
|
||||
"giraffe",
|
||||
"hippopotamus",
|
||||
"capybara",
|
||||
"python",
|
||||
"gopher",
|
||||
"crab",
|
||||
"trout",
|
||||
"osprey",
|
||||
"lemur",
|
||||
"wobbegong",
|
||||
"walrus",
|
||||
"opossum",
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum Status {
|
||||
Empty,
|
||||
Borrowed,
|
||||
Nested(Creature),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Status {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Status::Empty => write!(f, "empty"),
|
||||
Status::Borrowed => write!(f, "borrowed"),
|
||||
Status::Nested(creature) => write!(f, "nested {creature}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Status {
|
||||
pub fn receive(&mut self, msg: Value) {
|
||||
match self {
|
||||
Status::Nested(creature) => creature.receive(msg),
|
||||
Status::Borrowed => println!("sending a message to a borrowed process"),
|
||||
Status::Empty => println!("sending a message to a dead process"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Zoo {
|
||||
procs: Vec<Status>,
|
||||
empty: Vec<usize>,
|
||||
ids: HashMap<&'static str, usize>,
|
||||
dead: HashSet<&'static str>,
|
||||
kill_list: Vec<&'static str>,
|
||||
sleeping: HashMap<&'static str, f64>,
|
||||
active_idx: usize,
|
||||
active_id: &'static str,
|
||||
msgs: Vec<String>,
|
||||
}
|
||||
|
||||
impl Zoo {
|
||||
pub fn new() -> Zoo {
|
||||
Zoo {
|
||||
procs: vec![],
|
||||
empty: vec![],
|
||||
ids: HashMap::new(),
|
||||
kill_list: vec![],
|
||||
dead: HashSet::new(),
|
||||
sleeping: HashMap::new(),
|
||||
active_idx: 0,
|
||||
active_id: "",
|
||||
msgs: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn random_id(&self) -> String {
|
||||
let rand_idx = (random() * 32.0) as usize;
|
||||
let idx = self.procs.len();
|
||||
format!("{}_{idx}", ANIMALS[rand_idx])
|
||||
}
|
||||
|
||||
fn new_id(&self) -> &'static str {
|
||||
let mut new = self.random_id();
|
||||
while self.dead.iter().any(|old| *old == new) {
|
||||
new = self.random_id();
|
||||
}
|
||||
new.leak()
|
||||
}
|
||||
|
||||
pub fn put(&mut self, mut proc: Creature) -> &'static str {
|
||||
if self.empty.is_empty() {
|
||||
let id = self.new_id();
|
||||
let idx = self.procs.len();
|
||||
proc.pid = id;
|
||||
self.procs.push(Status::Nested(proc));
|
||||
self.ids.insert(id, idx);
|
||||
id
|
||||
} else {
|
||||
let idx = self.empty.pop().unwrap();
|
||||
let rand = (random() * 32.0) as usize;
|
||||
let id = format!("{}_{idx}", ANIMALS[rand]).leak();
|
||||
proc.pid = id;
|
||||
self.ids.insert(id, idx);
|
||||
self.procs[idx] = Status::Nested(proc);
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kill_linked(&mut self, id: &'static str) {
|
||||
self.msgs.push(format!("process :{id} terminated by linked process"));
|
||||
self.kill_list.push(id);
|
||||
}
|
||||
|
||||
pub fn kill(&mut self, id: &'static str) {
|
||||
self.kill_list.push(id);
|
||||
}
|
||||
|
||||
pub fn sleep(&mut self, id: &'static str, ms: f64) {
|
||||
self.sleeping
|
||||
.insert(id, now() + ms);
|
||||
}
|
||||
|
||||
pub fn is_alive(&self, id: &'static str) -> bool {
|
||||
if self.kill_list.contains(&id) {
|
||||
return false;
|
||||
}
|
||||
let idx = self.ids.get(id);
|
||||
match idx {
|
||||
Some(idx) => match self.procs.get(*idx) {
|
||||
Some(proc) => match proc {
|
||||
Status::Empty => false,
|
||||
Status::Borrowed => true,
|
||||
Status::Nested(_) => true,
|
||||
},
|
||||
None => false,
|
||||
},
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clean_up(&mut self) {
|
||||
while let Some(id) = self.kill_list.pop() {
|
||||
if let Some(idx) = self.ids.get(id) {
|
||||
console_log!("process :{id} terminated");
|
||||
self.procs[*idx] = Status::Empty;
|
||||
self.empty.push(*idx);
|
||||
self.ids.remove(id);
|
||||
self.dead.insert(id);
|
||||
}
|
||||
}
|
||||
|
||||
self.sleeping
|
||||
.retain(|_, wakeup_time| now() < *wakeup_time);
|
||||
|
||||
// console_log!(
|
||||
// "currently sleeping processes: {}",
|
||||
// self.sleeping
|
||||
// .keys()
|
||||
// .map(|id| id.to_string())
|
||||
// .collect::<Vec<_>>()
|
||||
// .join(" | ")
|
||||
// );
|
||||
}
|
||||
|
||||
pub fn catch(&mut self, id: &'static str) -> Creature {
|
||||
if let Some(idx) = self.ids.get(id) {
|
||||
let mut proc = Status::Borrowed;
|
||||
swap(&mut proc, &mut self.procs[*idx]);
|
||||
let Status::Nested(proc) = proc else {
|
||||
unreachable!("tried to borrow an empty or already-borrowed process {id}");
|
||||
};
|
||||
proc
|
||||
} else {
|
||||
unreachable!("tried to borrow a non-existent process {id}");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn release(&mut self, proc: Creature) {
|
||||
let id = proc.pid;
|
||||
if let Some(idx) = self.ids.get(id) {
|
||||
let mut proc = Status::Nested(proc);
|
||||
swap(&mut proc, &mut self.procs[*idx]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_available(&self) -> bool {
|
||||
match &self.procs[self.active_idx] {
|
||||
Status::Empty => false,
|
||||
Status::Borrowed => false,
|
||||
Status::Nested(proc) => !self.sleeping.contains_key(proc.pid),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> &'static str {
|
||||
self.clean_up();
|
||||
let starting_idx = self.active_idx;
|
||||
self.active_idx = (self.active_idx + 1) % self.procs.len();
|
||||
while !self.is_available() {
|
||||
// we've gone round the process queue already
|
||||
// that means no process is active
|
||||
// but we may have processes that are alive and asleep
|
||||
// if nothing is active, yield back to the world's event loop
|
||||
if self.active_idx == starting_idx {
|
||||
return ""
|
||||
}
|
||||
self.active_idx = (self.active_idx + 1) % self.procs.len();
|
||||
}
|
||||
match &self.procs[self.active_idx] {
|
||||
Status::Empty | Status::Borrowed => unreachable!(),
|
||||
Status::Nested(proc) => proc.pid,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_msg(&mut self, id: &'static str, msg: Value) {
|
||||
let Some(idx) = self.ids.get(id) else {
|
||||
return;
|
||||
};
|
||||
self.procs[*idx].receive(msg);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Buffers {
|
||||
console: Value,
|
||||
commands: Value,
|
||||
fetch_out: Value,
|
||||
fetch_in: Value,
|
||||
input: Value,
|
||||
keys_down: Value,
|
||||
}
|
||||
|
||||
impl Buffers {
|
||||
pub fn new (prelude: imbl::HashMap<Key, Value>) -> Buffers {
|
||||
Buffers {
|
||||
console: prelude.get(&Key::Keyword("console")).unwrap().clone(),
|
||||
commands: prelude.get(&Key::Keyword("turtle_commands")).unwrap().clone(),
|
||||
fetch_out: prelude.get(&Key::Keyword("fetch_outbox")).unwrap().clone(),
|
||||
fetch_in: prelude.get(&Key::Keyword("fetch_inbox")).unwrap().clone(),
|
||||
input: prelude.get(&Key::Keyword("input")).unwrap().clone(),
|
||||
keys_down: prelude.get(&Key::Keyword("keys_down")).unwrap().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn console (&self) -> Rc<RefCell<Value>> {
|
||||
self.console.as_box()
|
||||
}
|
||||
|
||||
pub fn input (&self) -> Rc<RefCell<Value>> {
|
||||
self.input.as_box()
|
||||
}
|
||||
|
||||
pub fn commands (&self) -> Rc<RefCell<Value>> {
|
||||
self.commands.as_box()
|
||||
}
|
||||
|
||||
pub fn fetch_out (&self) -> Rc<RefCell<Value>> {
|
||||
self.fetch_out.as_box()
|
||||
}
|
||||
|
||||
pub fn fetch_in (&self) -> Rc<RefCell<Value>> {
|
||||
self.fetch_in.as_box()
|
||||
}
|
||||
|
||||
pub fn keys_down (&self) -> Rc<RefCell<Value>> {
|
||||
self.keys_down.as_box()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct World {
|
||||
zoo: Rc<RefCell<Zoo>>,
|
||||
active: Option<Creature>,
|
||||
main: &'static str,
|
||||
pub result: Option<Result<Value, Panic>>,
|
||||
buffers: Buffers,
|
||||
last_io: f64,
|
||||
kill_signal: bool,
|
||||
}
|
||||
|
||||
impl World {
|
||||
pub fn new(chunk: Chunk, prelude: imbl::HashMap<Key, Value>, debug: bool) -> World {
|
||||
let zoo = Rc::new(RefCell::new(Zoo::new()));
|
||||
let main = Creature::new(chunk, zoo.clone(), debug);
|
||||
let id = zoo.borrow_mut().put(main);
|
||||
let buffers = Buffers::new(prelude);
|
||||
|
||||
World {
|
||||
zoo,
|
||||
active: None,
|
||||
main: id,
|
||||
result: None,
|
||||
buffers,
|
||||
last_io: 0.0,
|
||||
kill_signal: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn next(&mut self) {
|
||||
let mut active = None;
|
||||
swap(&mut active, &mut self.active);
|
||||
let mut zoo = self.zoo.borrow_mut();
|
||||
if let Some(active) = active {
|
||||
zoo.release(active);
|
||||
}
|
||||
let new_active_id = zoo.next();
|
||||
if new_active_id.is_empty() {
|
||||
self.active = None;
|
||||
return;
|
||||
}
|
||||
let mut new_active_proc = zoo.catch(new_active_id);
|
||||
new_active_proc.reset_reductions();
|
||||
let mut new_active_opt = Some(new_active_proc);
|
||||
swap(&mut new_active_opt, &mut self.active);
|
||||
}
|
||||
|
||||
fn activate_main(&mut self) {
|
||||
let main = self.zoo.borrow_mut().catch(self.main);
|
||||
self.active = Some(main);
|
||||
}
|
||||
|
||||
fn active_id(&mut self) -> Option<&'static str> {
|
||||
match &self.active {
|
||||
Some(creature) => Some(creature.pid),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn kill_active(&mut self) {
|
||||
if let Some(pid) = self.active_id() {
|
||||
self.zoo.borrow_mut().kill(pid);
|
||||
}
|
||||
}
|
||||
|
||||
fn active_result(&mut self) -> &Option<Result<Value, Panic>> {
|
||||
if self.active.is_none() { return &None; }
|
||||
&self.active.as_ref().unwrap().result
|
||||
}
|
||||
|
||||
fn flush_buffers(&mut self) -> Vec<MsgOut> {
|
||||
let mut outbox = vec![];
|
||||
if let Some(console) = self.flush_console() {
|
||||
outbox.push(console);
|
||||
}
|
||||
if let Some(commands) = self.flush_commands() {
|
||||
outbox.push(commands);
|
||||
}
|
||||
if let Some(fetch) = self.make_fetch_happen() {
|
||||
outbox.push(fetch);
|
||||
}
|
||||
if let Some(msgs) = self.flush_zoo() {
|
||||
outbox.push(msgs);
|
||||
}
|
||||
outbox
|
||||
}
|
||||
|
||||
fn flush_zoo(&mut self) -> Option<MsgOut> {
|
||||
let mut zoo_msgs = vec![];
|
||||
let mut zoo = self.zoo.borrow_mut();
|
||||
swap(&mut zoo_msgs, &mut zoo.msgs);
|
||||
if zoo_msgs.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let inner = zoo_msgs.into_iter().map(Value::from_string).collect::<imbl::Vector<_>>();
|
||||
Some(MsgOut::Console(Value::list(inner)))
|
||||
}
|
||||
}
|
||||
|
||||
fn make_fetch_happen(&self) -> Option<MsgOut> {
|
||||
let out = self.buffers.fetch_out();
|
||||
let working = RefCell::new(Value::Interned(""));
|
||||
out.swap(&working);
|
||||
let working = working.borrow();
|
||||
if working.as_string().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(MsgOut::Fetch(working.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_console(&self) -> Option<MsgOut> {
|
||||
let console = self.buffers.console();
|
||||
let working_copy = RefCell::new(Value::new_list());
|
||||
console.swap(&working_copy);
|
||||
let working_value = working_copy.borrow();
|
||||
if working_value.as_list().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(MsgOut::Console(working_value.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_commands(&self) -> Option<MsgOut> {
|
||||
let commands = self.buffers.commands();
|
||||
let working_copy = RefCell::new(Value::new_list());
|
||||
commands.swap(&working_copy);
|
||||
let commands = working_copy.borrow();
|
||||
if commands.as_list().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(MsgOut::Commands(commands.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
fn complete_main(&mut self) -> Vec<MsgOut> {
|
||||
let mut outbox = self.flush_buffers();
|
||||
// TODO: if we have a panic, actually add the panic message to the console
|
||||
let result = self.active_result().clone().unwrap();
|
||||
self.result = Some(result.clone());
|
||||
match result {
|
||||
Ok(value) => outbox.push(MsgOut::Complete(Value::from_string(value.show()))),
|
||||
Err(p) => {
|
||||
outbox.push(MsgOut::Console(Value::list(imbl::vector!(Value::from_string("Ludus panicked!".to_string())))));
|
||||
outbox.push(MsgOut::Error(panic(p)))
|
||||
}
|
||||
}
|
||||
outbox
|
||||
}
|
||||
|
||||
fn interpret_active(&mut self) {
|
||||
self.active.as_mut().unwrap().interpret();
|
||||
}
|
||||
|
||||
async fn maybe_do_io(&mut self) {
|
||||
if self.last_io + 10.0 < now() {
|
||||
let outbox = self.flush_buffers();
|
||||
let inbox = do_io(outbox).await;
|
||||
self.fill_buffers(inbox);
|
||||
self.last_io = now();
|
||||
}
|
||||
}
|
||||
|
||||
fn fill_input(&mut self, str: String) {
|
||||
let value = Value::from_string(str);
|
||||
let working = RefCell::new(value);
|
||||
let input = self.buffers.input();
|
||||
input.swap(&working);
|
||||
}
|
||||
|
||||
fn fetch_reply(&mut self, reply: Value) {
|
||||
let inbox_rc = self.buffers.fetch_in();
|
||||
inbox_rc.replace(reply);
|
||||
}
|
||||
|
||||
fn register_keys(&mut self, keys: Value) {
|
||||
// console_log!("New keys: {keys}");
|
||||
let keys_down_rc = self.buffers.keys_down();
|
||||
keys_down_rc.replace(keys);
|
||||
}
|
||||
|
||||
fn fill_buffers(&mut self, inbox: Vec<MsgIn>) {
|
||||
for msg in inbox {
|
||||
match msg {
|
||||
MsgIn::Input(str) => self.fill_input(str),
|
||||
MsgIn::Kill => self.kill_signal = true,
|
||||
MsgIn::Fetch(..) => self.fetch_reply(msg.into_value()),
|
||||
MsgIn::Keys(..) => self.register_keys(msg.into_value()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn ready_io(&mut self) {
|
||||
let inbox = do_io(vec![MsgOut::Ready]).await;
|
||||
self.fill_buffers(inbox);
|
||||
self.last_io = now();
|
||||
}
|
||||
|
||||
fn send_ludus_msg(&mut self, msg: String) {
|
||||
let console = self.buffers.console();
|
||||
let mut console = console.as_ref().borrow_mut();
|
||||
let Value::List(ref mut console) = *console else {unreachable!("expect console to be a list")};
|
||||
console.push_back(Value::from_string(msg));
|
||||
}
|
||||
|
||||
fn report_process_end(&mut self) {
|
||||
let result = self.active_result().clone().unwrap();
|
||||
if let Err(panic) = result {
|
||||
let msg = format!("Process :{} panicked: {}", self.active_id().unwrap(), crate::errors::panic(panic));
|
||||
self.send_ludus_msg(msg)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) {
|
||||
self.activate_main();
|
||||
self.ready_io().await;
|
||||
loop {
|
||||
self.maybe_do_io().await;
|
||||
if self.kill_signal {
|
||||
let mut outbox = self.flush_buffers();
|
||||
outbox.push(MsgOut::Error("Ludus stopped by user".to_string()));
|
||||
do_io(outbox).await;
|
||||
return;
|
||||
}
|
||||
if self.active.is_some() {
|
||||
self.interpret_active();
|
||||
}
|
||||
if self.active_result().is_some() {
|
||||
if self.active_id().unwrap() == self.main {
|
||||
let outbox = self.complete_main();
|
||||
do_io(outbox).await;
|
||||
return;
|
||||
} else {
|
||||
self.report_process_end()
|
||||
}
|
||||
self.kill_active();
|
||||
}
|
||||
self.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user