1
0

Compare commits

..

No commits in common. "main" and "rewrite-3" have entirely different histories.

48 changed files with 10887 additions and 8557 deletions

427
Cargo.lock generated
View File

@ -4,9 +4,9 @@ version = 3
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.1.3" version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -23,65 +23,52 @@ dependencies = [
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.18" version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"anstyle-parse", "anstyle-parse",
"anstyle-query", "anstyle-query",
"anstyle-wincon", "anstyle-wincon",
"colorchoice", "colorchoice",
"is_terminal_polyfill",
"utf8parse", "utf8parse",
] ]
[[package]] [[package]]
name = "anstyle" name = "anstyle"
version = "1.0.10" version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
[[package]] [[package]]
name = "anstyle-parse" name = "anstyle-parse"
version = "0.2.6" version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
dependencies = [ dependencies = [
"utf8parse", "utf8parse",
] ]
[[package]] [[package]]
name = "anstyle-query" name = "anstyle-query"
version = "1.1.2" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys",
] ]
[[package]] [[package]]
name = "anstyle-wincon" name = "anstyle-wincon"
version = "3.0.6" version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"windows-sys 0.59.0", "windows-sys",
] ]
[[package]]
name = "bumpalo"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
@ -90,9 +77,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.20" version = "4.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" checksum = "c937d4061031a6d0c8da4b9a4f98a172fc2976dfb1c19213a9cf7d0d3c837e36"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -100,9 +87,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.20" version = "4.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" checksum = "85379ba512b21a328adf887e85f7742d12e96eb31f3ef077df4ffc26b506ffed"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -112,9 +99,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.18" version = "4.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -130,30 +117,44 @@ checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
version = "1.0.3" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]] [[package]]
name = "colored" name = "crossbeam-deque"
version = "2.1.0" version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
dependencies = [ dependencies = [
"lazy_static", "crossbeam-epoch",
"windows-sys 0.48.0", "crossbeam-utils",
] ]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]] [[package]]
name = "dust-lang" name = "dust-lang"
version = "0.5.0" version = "0.5.0"
dependencies = [ dependencies = [
"annotate-snippets", "annotate-snippets",
"colored",
"env_logger", "env_logger",
"getrandom",
"log", "log",
"rand", "rand",
"rayon",
"serde", "serde",
"serde_json", "serde_json",
] ]
@ -163,17 +164,21 @@ name = "dust-shell"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"colored",
"dust-lang", "dust-lang",
"env_logger", "env_logger",
"log",
] ]
[[package]] [[package]]
name = "env_filter" name = "either"
version = "0.1.2" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]]
name = "env_filter"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
dependencies = [ dependencies = [
"log", "log",
"regex", "regex",
@ -194,15 +199,13 @@ dependencies = [
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.15" version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys",
"libc", "libc",
"wasi", "wasi",
"wasm-bindgen",
] ]
[[package]] [[package]]
@ -217,38 +220,17 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.11" version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "js-sys"
version = "0.3.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.161" version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]] [[package]]
name = "log" name = "log"
@ -258,39 +240,30 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "once_cell"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.20" version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
dependencies = [
"zerocopy",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.89" version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.37" version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -326,10 +299,30 @@ dependencies = [
] ]
[[package]] [[package]]
name = "regex" name = "rayon"
version = "1.11.1" version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "regex"
version = "1.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -339,9 +332,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.4.8" version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -350,30 +343,30 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.8.5" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.18" version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.214" version = "1.0.203"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.214" version = "1.0.203"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -382,12 +375,11 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.132" version = "1.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr",
"ryu", "ryu",
"serde", "serde",
] ]
@ -400,9 +392,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.87" version = "2.0.53"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -411,21 +403,21 @@ dependencies = [
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.13" version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.14" version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.2.2" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]] [[package]]
name = "wasi" name = "wasi"
@ -433,217 +425,68 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
dependencies = [
"cfg-if",
"once_cell",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.48.0" version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [ dependencies = [
"windows-targets 0.48.5", "windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
] ]
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.48.5" version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm 0.48.5", "windows_aarch64_gnullvm",
"windows_aarch64_msvc 0.48.5", "windows_aarch64_msvc",
"windows_i686_gnu 0.48.5", "windows_i686_gnu",
"windows_i686_msvc 0.48.5", "windows_i686_msvc",
"windows_x86_64_gnu 0.48.5", "windows_x86_64_gnu",
"windows_x86_64_gnullvm 0.48.5", "windows_x86_64_gnullvm",
"windows_x86_64_msvc 0.48.5", "windows_x86_64_msvc",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
] ]
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.48.5" version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.48.5" version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.48.5" version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
[[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]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.48.5" version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.48.5" version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.48.5" version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.48.5" version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

125
README.md
View File

@ -1,50 +1,109 @@
# Dust # Dust
Dust is a high-level interpreted programming language with static types that focuses on ease of use, High-level programming language with effortless concurrency, automatic memory management and type
performance and correctness. safety.
## Implementation Dust is a work in progress. Because it aims to deliver a high level of safety, extensive testing
is required. The language is still in the design phase, and the syntax is subject to change.
Dust is implemented in Rust and is divided into several parts, primarily the lexer, compiler, and ## Usage
virtual machine. All of Dust's components are designed with performance in mind and the codebase
uses as few dependencies as possible.
### Lexer The Dust command line tool can be used to run Dust programs. It is not yet available outside of
this repository.
The lexer emits tokens from the source code. Dust makes extensive use of Rust's zero-copy ```sh
capabilities to avoid unnecessary allocations when creating tokens. A token, depending on its type, cargo run --package dust-shell -- examples/hello_world.ds
may contain a reference to some data from the source code. The data is only copied in the case of an ```
error, because it improves the usability of the codebase for errors to own their data when possible.
In a successfully executed program, no part of the source code is copied unless it is a string
literal or identifier.
### Compiler ```sh
cargo run --package dust-shell -- -c '"Hello my name is " + read_line() + "!"'
```
The compiler creates a chunk, which contains all of the data needed by the virtual machine to run a Dust is easily embedded in another program. You can run a dust program of any size or complexity
Dust program. It does so by emitting bytecode instructions, constants and locals while parsing the with a single function.
tokens, which are generated one at a time by the lexer.
#### Parsing ```rust
use dust_lang::{run, Value};
Dust's compiler uses a custom Pratt parser, a kind of recursive descent parser, to translate a fn main() {
sequence of tokens into a chunk. let code = "
let x = 'Dust'
let y = ' is awesome!'
#### Optimizing write_line(x + y)
When generating instructions for a register-based virtual machine, there are opportunities to 42
optimize the generated code, usually by consolidating register use or reusing registers within an ";
expression. While it is best to output optimal code in the first place, it is not always possible.
Dust's compiler has a simple peephole optimizer that can be used to modify isolated sections of the
instruction list through a mutable reference.
### Instructions let result = run(code);
### Virtual Machine assert_eq!(result, Ok(Some(Value::integer(42))));
}
```
## Previous Implementations ## Concepts
## Inspiration ### Effortless Concurrency
- [The Implementation of Lua 5.0](https://www.lua.org/doc/jucs05.pdf) Dust makes concurrency as effortless as possible. Dust is organized into **statements**, and any
- [A No-Frills Introduction to Lua 5.1 VM Instructions](https://www.mcours.net/cours/pdf/hasclic3/hasssclic818.pdf) sequence of statements can be run concurrently by simply adding the `async` keyword before the block
- [Crafting Interpreters](https://craftinginterpreters.com/) of statements.
```rust
// Counts from 0 to 9, sleeping for an increasing amount of time between each.
let count_slowly = fn (multiplier: int) {
i = 0
while i < 10 {
sleep(i * multiplier)
write_line(i.to_string())
i += 1
}
}
async {
count_slowly(200) // Finishes last
count_slowly(100) // Finishes second
count_slowly(50) // Finishes first
}
```
### Automatic Memory Management
Dust uses a garbage collector to automatically manage memory.
```rust
let x = 0 // x is assigned but never used
// x is removed from memory
let y = 41 // y is assigned
let z = y + 1 // y is kept alive for this statement
// y is removed from memory
write_line(z) // z is kept alive for this statement
// z is removed from memory
```
### Type Safety
Dust is statically typed and null-free, but the type of a value can usually be inferred from its
usage. Dust will refuse to run programs with type errors, but will usually not require type
annotations.
```rust
// These two statements are identical to Dust
let x = 1
let x: int = 1
// Numbers with decimals are floats
let y = 10.0
let y: float = 10.0
// Strings are enclosed in double quotes and are guaranteed to be valid UTF-8
let z = "Hello, world!"
let z: str = "Hello, world!"
```
Aside from the ubiqutous `bool`, `int`, `float`, and `str` types, Dust also has lists, maps,
ranges, structures, enums and functions.

View File

@ -10,14 +10,12 @@ repository.workspace = true
[dependencies] [dependencies]
annotate-snippets = "0.11.4" annotate-snippets = "0.11.4"
colored = "2.1.0" env_logger = "0.11.3"
log = "0.4.22" log = "0.4.22"
rand = "0.8.5" rand = "0.8.5"
rayon = "1.9.0"
serde = { version = "1.0.203", features = ["derive"] } serde = { version = "1.0.203", features = ["derive"] }
serde_json = "1.0.117" serde_json = "1.0.117"
getrandom = { version = "0.2", features = [
"js",
] } # Indirect dependency, for wasm builds
[dev-dependencies] [dev-dependencies]
env_logger = "0.11.5" env_logger = "0.11.5"

1385
dust-lang/src/analyzer.rs Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

192
dust-lang/src/ast/mod.rs Normal file
View File

@ -0,0 +1,192 @@
//! In-memory representation of a Dust program.
mod expression;
mod statement;
pub use expression::*;
pub use statement::*;
use std::{
collections::VecDeque,
fmt::{self, Debug, Display, Formatter},
num::TryFromIntError,
};
use serde::{Deserialize, Serialize};
use crate::{core_library, Context, ContextError};
pub type Span = (usize, usize);
/// In-memory representation of a Dust program.
#[derive(Clone, Serialize, Deserialize)]
pub struct AbstractSyntaxTree {
pub statements: VecDeque<Statement>,
#[serde(skip)]
pub context: Context,
}
impl Debug for AbstractSyntaxTree {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("AbstractSyntaxTree")
.field("statements", &self.statements)
.finish()
}
}
impl Eq for AbstractSyntaxTree {}
impl PartialEq for AbstractSyntaxTree {
fn eq(&self, other: &Self) -> bool {
self.statements == other.statements
}
}
impl PartialOrd for AbstractSyntaxTree {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for AbstractSyntaxTree {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.statements.cmp(&other.statements)
}
}
impl AbstractSyntaxTree {
pub fn new() -> Self {
Self {
statements: VecDeque::new(),
context: Context::new(),
}
}
pub fn with_statements<T: Into<VecDeque<Statement>>>(statements: T) -> Self {
Self {
statements: statements.into(),
context: Context::new(),
}
}
pub fn with_core_library() -> Self {
Self {
statements: VecDeque::new(),
context: core_library().create_child(),
}
}
}
impl Default for AbstractSyntaxTree {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Node<T> {
pub inner: T,
pub position: Span,
}
impl<T> Node<T> {
pub fn new(inner: T, position: Span) -> Self {
Self { inner, position }
}
}
impl<T: Display> Display for Node<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.inner)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum AstError {
ContextError {
error: ContextError,
position: Span,
},
ExpectedFunctionOrConstructor {
position: Span,
},
ExpectedInteger {
position: Span,
},
ExpectedListType {
position: Span,
},
ExpectedNonEmptyEvaluation {
position: Span,
},
ExpectedNonEmptyList {
position: Span,
},
ExpectedRangeableType {
position: Span,
},
ExpectedStructFieldsType {
position: Span,
},
ExpectedTupleType {
position: Span,
},
FromIntError {
error: TryFromIntError,
position: Span,
},
}
impl AstError {
pub fn position(&self) -> Span {
match self {
AstError::ContextError { position, .. } => *position,
AstError::ExpectedFunctionOrConstructor { position } => *position,
AstError::ExpectedInteger { position } => *position,
AstError::ExpectedListType { position } => *position,
AstError::ExpectedNonEmptyEvaluation { position } => *position,
AstError::ExpectedNonEmptyList { position } => *position,
AstError::ExpectedRangeableType { position } => *position,
AstError::ExpectedStructFieldsType { position } => *position,
AstError::ExpectedTupleType { position } => *position,
AstError::FromIntError { position, .. } => *position,
}
}
}
impl Display for AstError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
AstError::ContextError { error, position } => {
write!(f, "Context error at {:?}: {}", position, error)
}
AstError::ExpectedFunctionOrConstructor { position } => {
write!(f, "Expected a function or constructor at {:?}", position)
}
AstError::ExpectedInteger { position } => {
write!(f, "Expected an integer at {:?}", position)
}
AstError::ExpectedListType { position } => {
write!(f, "Expected a type at {:?}", position)
}
AstError::ExpectedTupleType { position } => {
write!(f, "Expected a tuple type at {:?}", position)
}
AstError::ExpectedNonEmptyEvaluation { position } => {
write!(f, "Expected a type at {:?}", position)
}
AstError::ExpectedNonEmptyList { position } => {
write!(f, "Expected a non-empty list at {:?}", position)
}
AstError::ExpectedRangeableType { position } => {
write!(f, "Expected a rangeable type at {:?}", position)
}
AstError::ExpectedStructFieldsType { position } => {
write!(f, "Expected a struct type with fields at {:?}", position)
}
AstError::FromIntError { error, position } => {
write!(f, "Integer conversion error at {:?}: {}", position, error)
}
}
}
}

View File

@ -0,0 +1,187 @@
use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
use crate::{Context, Identifier, Type, TypeEvaluation};
use super::{AstError, Expression, Node, Span};
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Statement {
Expression(Expression),
ExpressionNullified(Node<Expression>),
Let(Node<LetStatement>),
StructDefinition(Node<StructDefinition>),
}
impl Statement {
pub fn struct_definition(struct_definition: StructDefinition, position: Span) -> Self {
Statement::StructDefinition(Node::new(struct_definition, position))
}
pub fn position(&self) -> Span {
match self {
Statement::Expression(expression) => expression.position(),
Statement::ExpressionNullified(expression_node) => expression_node.position,
Statement::Let(r#let) => r#let.position,
Statement::StructDefinition(definition) => definition.position,
}
}
pub fn type_evaluation(&self, context: &Context) -> Result<TypeEvaluation, AstError> {
match self {
Statement::Expression(expression) => expression.type_evaluation(context),
Statement::ExpressionNullified(expression_node) => {
let type_evaluation = expression_node.inner.type_evaluation(context)?;
if let TypeEvaluation::Break(_) = type_evaluation {
Ok(type_evaluation)
} else {
Ok(TypeEvaluation::Return(None))
}
}
Statement::Let(_) => Ok(TypeEvaluation::Return(None)),
Statement::StructDefinition(_) => Ok(TypeEvaluation::Return(None)),
}
}
}
impl Display for Statement {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Statement::Expression(expression) => write!(f, "{}", expression),
Statement::ExpressionNullified(expression) => write!(f, "{};", expression),
Statement::Let(r#let) => write!(f, "{};", r#let),
Statement::StructDefinition(struct_definition) => match &struct_definition.inner {
StructDefinition::Unit { name } => write!(f, "struct {};", name),
StructDefinition::Tuple { name, items } => {
write!(f, "struct {name} {{ ")?;
for (index, item) in items.iter().enumerate() {
write!(f, "{}: {}", item, index)?;
if index < items.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, " }}")
}
StructDefinition::Fields { name, fields } => {
write!(f, "struct {name} {{ ")?;
for (index, (field, r#type)) in fields.iter().enumerate() {
write!(f, "{}: {}", field, r#type)?;
if index < fields.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, " }}")
}
},
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum LetStatement {
Let {
identifier: Node<Identifier>,
value: Expression,
},
LetMut {
identifier: Node<Identifier>,
value: Expression,
},
LetType {
identifier: Node<Identifier>,
r#type: Node<Type>,
value: Expression,
},
LetMutType {
identifier: Node<Identifier>,
r#type: Node<Type>,
value: Expression,
},
}
impl Display for LetStatement {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
LetStatement::Let { identifier, value } => {
write!(f, "let {identifier} = {value}")
}
LetStatement::LetMut { identifier, value } => {
write!(f, "let mut {identifier} = {value}")
}
LetStatement::LetType {
identifier,
r#type,
value,
} => {
write!(f, "let {identifier}: {type} = {value}")
}
LetStatement::LetMutType {
identifier,
r#type,
value,
} => {
write!(f, "let mut {identifier}: {type} = {value}")
}
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum StructDefinition {
Unit {
name: Node<Identifier>,
},
Tuple {
name: Node<Identifier>,
items: Vec<Node<Type>>,
},
Fields {
name: Node<Identifier>,
fields: Vec<(Node<Identifier>, Node<Type>)>,
},
}
impl Display for StructDefinition {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
StructDefinition::Unit { name } => write!(f, "struct {name}"),
StructDefinition::Tuple {
name,
items: fields,
} => {
write!(f, "struct {name} {{")?;
for (i, field) in fields.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{field}")?;
}
write!(f, "}}")
}
StructDefinition::Fields { name, fields } => {
write!(f, "struct {name} {{")?;
for (i, (field_name, field_type)) in fields.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{field_name}: {field_type}")?;
}
write!(f, "}}")
}
}
}
}

View File

@ -0,0 +1,208 @@
//! Integrated functions that can be called from Dust code.
use std::{
error::Error,
fmt::{self, Display, Formatter},
io::{self, stdin, stdout, Write},
};
use serde::{Deserialize, Serialize};
use crate::{FunctionType, Identifier, Type, Value, ValueData, ValueError};
/// Integrated function that can be called from Dust code.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum BuiltInFunction {
// String tools
ToString,
// Integer and float tools
IsEven,
IsOdd,
// I/O
ReadLine,
WriteLine,
}
impl BuiltInFunction {
pub fn name(&self) -> &'static str {
match self {
BuiltInFunction::IsEven => "is_even",
BuiltInFunction::IsOdd => "is_odd",
BuiltInFunction::ReadLine => "read_line",
BuiltInFunction::ToString { .. } => "to_string",
BuiltInFunction::WriteLine => "write_line",
}
}
pub fn type_parameters(&self) -> Option<Vec<Identifier>> {
match self {
BuiltInFunction::ToString { .. } => None,
BuiltInFunction::IsEven => None,
BuiltInFunction::IsOdd => None,
BuiltInFunction::ReadLine => None,
BuiltInFunction::WriteLine => None,
}
}
pub fn value_parameters(&self) -> Option<Vec<(Identifier, Type)>> {
match self {
BuiltInFunction::ToString { .. } => Some(vec![("value".into(), Type::Any)]),
BuiltInFunction::IsEven => Some(vec![("value".into(), Type::Number)]),
BuiltInFunction::IsOdd => Some(vec![("value".into(), Type::Number)]),
BuiltInFunction::ReadLine => None,
BuiltInFunction::WriteLine => Some(vec![("value".into(), Type::Any)]),
}
}
pub fn return_type(&self) -> Option<Type> {
match self {
BuiltInFunction::ToString { .. } => Some(Type::String { length: None }),
BuiltInFunction::IsEven => Some(Type::Boolean),
BuiltInFunction::IsOdd => Some(Type::Boolean),
BuiltInFunction::ReadLine => Some(Type::String { length: None }),
BuiltInFunction::WriteLine => None,
}
}
pub fn r#type(&self) -> Type {
Type::Function(FunctionType {
name: Identifier::new(self.name()),
type_parameters: self.type_parameters(),
value_parameters: self.value_parameters(),
return_type: self.return_type().map(Box::new),
})
}
pub fn call(
&self,
_type_arguments: Option<Vec<Type>>,
value_arguments: Option<Vec<Value>>,
) -> Result<Option<Value>, BuiltInFunctionError> {
match (self.value_parameters(), &value_arguments) {
(Some(value_parameters), Some(value_arguments)) => {
if value_parameters.len() != value_arguments.len() {
return Err(BuiltInFunctionError::WrongNumberOfValueArguments);
}
}
(Some(_), None) | (None, Some(_)) => {
return Err(BuiltInFunctionError::WrongNumberOfValueArguments);
}
(None, None) => {}
}
match self {
BuiltInFunction::ToString => {
Ok(Some(Value::string(value_arguments.unwrap()[0].to_string())))
}
BuiltInFunction::IsEven => {
let is_even = value_arguments.unwrap()[0].is_even()?;
Ok(Some(is_even))
}
BuiltInFunction::IsOdd => {
let is_odd = value_arguments.unwrap()[0].is_odd()?;
Ok(Some(is_odd))
}
BuiltInFunction::ReadLine => {
let mut input = String::new();
stdin().read_line(&mut input)?;
Ok(Some(Value::string(input.trim_end_matches('\n'))))
}
BuiltInFunction::WriteLine => {
let first_argument = &value_arguments.unwrap()[0];
match first_argument {
Value::Raw(ValueData::String(string)) => {
let mut stdout = stdout();
stdout.write_all(string.as_bytes())?;
stdout.write_all(b"\n")?;
Ok(None)
}
Value::Reference(reference) => match reference.as_ref() {
ValueData::String(string) => {
let mut stdout = stdout();
stdout.write_all(string.as_bytes())?;
stdout.write_all(b"\n")?;
Ok(None)
}
_ => Err(BuiltInFunctionError::ExpectedString),
},
Value::Mutable(locked) => {
let value_data = &*locked.read().unwrap();
let string = match value_data {
ValueData::String(string) => string,
_ => return Err(BuiltInFunctionError::ExpectedString),
};
let mut stdout = stdout();
stdout.write_all(string.as_bytes())?;
stdout.write_all(b"\n")?;
Ok(None)
}
_ => Err(BuiltInFunctionError::ExpectedString),
}
}
}
}
}
impl Display for BuiltInFunction {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum BuiltInFunctionError {
Io(io::ErrorKind),
ValueError(ValueError),
ExpectedString,
ExpectedList,
ExpectedInteger,
WrongNumberOfValueArguments,
}
impl From<ValueError> for BuiltInFunctionError {
fn from(v: ValueError) -> Self {
Self::ValueError(v)
}
}
impl From<io::Error> for BuiltInFunctionError {
fn from(error: io::Error) -> Self {
Self::Io(error.kind())
}
}
impl Error for BuiltInFunctionError {}
impl Display for BuiltInFunctionError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
BuiltInFunctionError::Io(error_kind) => write!(f, "I/O error: {}", error_kind),
BuiltInFunctionError::ValueError(value_error) => {
write!(f, "Value error: {}", value_error)
}
BuiltInFunctionError::ExpectedInteger => write!(f, "Expected an integer"),
BuiltInFunctionError::ExpectedString => write!(f, "Expected a string"),
BuiltInFunctionError::ExpectedList => write!(f, "Expected a list"),
BuiltInFunctionError::WrongNumberOfValueArguments => {
write!(f, "Wrong number of value arguments")
}
}
}
}

View File

@ -1,402 +0,0 @@
//! In-memory representation of a Dust program or function.
//!
//! A chunk consists of a sequence of instructions and their positions, a list of constants, and a
//! list of locals that can be executed by the Dust virtual machine. Chunks have a name when they
//! belong to a named function.
use std::{
cmp::Ordering,
fmt::{self, Debug, Display, Formatter},
};
use serde::{Deserialize, Serialize};
use crate::{Disassembler, Instruction, Operation, Span, Type, Value};
/// In-memory representation of a Dust program or function.
///
/// See the [module-level documentation](index.html) for more information.
#[derive(Clone, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Chunk {
name: Option<String>,
pub is_poisoned: bool,
instructions: Vec<(Instruction, Span)>,
constants: Vec<Value>,
locals: Vec<Local>,
current_scope: Scope,
block_index: u8,
}
impl Chunk {
pub fn new(name: Option<String>) -> Self {
Self {
name,
is_poisoned: false,
instructions: Vec::new(),
constants: Vec::new(),
locals: Vec::new(),
current_scope: Scope::default(),
block_index: 0,
}
}
pub fn with_data(
name: Option<String>,
instructions: Vec<(Instruction, Span)>,
constants: Vec<Value>,
locals: Vec<Local>,
) -> Self {
Self {
name,
is_poisoned: false,
instructions,
constants,
locals,
current_scope: Scope::default(),
block_index: 0,
}
}
pub fn name(&self) -> Option<&String> {
self.name.as_ref()
}
pub fn set_name(&mut self, name: String) {
self.name = Some(name);
}
pub fn len(&self) -> usize {
self.instructions.len()
}
pub fn is_empty(&self) -> bool {
self.instructions.is_empty()
}
pub fn constants(&self) -> &Vec<Value> {
&self.constants
}
pub fn constants_mut(&mut self) -> &mut Vec<Value> {
&mut self.constants
}
pub fn take_constants(self) -> Vec<Value> {
self.constants
}
pub fn instructions(&self) -> &Vec<(Instruction, Span)> {
&self.instructions
}
pub fn instructions_mut(&mut self) -> &mut Vec<(Instruction, Span)> {
&mut self.instructions
}
pub fn get_instruction(&self, index: usize) -> Result<&(Instruction, Span), ChunkError> {
self.instructions
.get(index)
.ok_or(ChunkError::InstructionIndexOutOfBounds { index })
}
pub fn locals(&self) -> &Vec<Local> {
&self.locals
}
pub fn locals_mut(&mut self) -> &mut Vec<Local> {
&mut self.locals
}
pub fn get_local(&self, index: u8) -> Result<&Local, ChunkError> {
self.locals
.get(index as usize)
.ok_or(ChunkError::LocalIndexOutOfBounds {
index: index as usize,
})
}
pub fn get_local_mut(&mut self, index: u8) -> Result<&mut Local, ChunkError> {
self.locals
.get_mut(index as usize)
.ok_or(ChunkError::LocalIndexOutOfBounds {
index: index as usize,
})
}
pub fn current_scope(&self) -> Scope {
self.current_scope
}
pub fn get_constant(&self, index: u8) -> Result<&Value, ChunkError> {
self.constants
.get(index as usize)
.ok_or(ChunkError::ConstantIndexOutOfBounds {
index: index as usize,
})
}
pub fn push_or_get_constant(&mut self, value: Value) -> u8 {
if let Some(index) = self
.constants
.iter()
.position(|constant| constant == &value)
{
return index as u8;
}
self.constants.push(value);
(self.constants.len() - 1) as u8
}
pub fn get_identifier(&self, local_index: u8) -> Option<String> {
self.locals.get(local_index as usize).and_then(|local| {
self.constants
.get(local.identifier_index as usize)
.map(|value| value.to_string())
})
}
pub fn begin_scope(&mut self) {
self.block_index += 1;
self.current_scope.block_index = self.block_index;
self.current_scope.depth += 1;
}
pub fn end_scope(&mut self) {
self.current_scope.depth -= 1;
if self.current_scope.depth == 0 {
self.current_scope.block_index = 0;
} else {
self.current_scope.block_index -= 1;
}
}
pub fn expect_not_poisoned(&self) -> Result<(), ChunkError> {
if self.is_poisoned {
Err(ChunkError::PoisonedChunk)
} else {
Ok(())
}
}
pub fn get_constant_type(&self, constant_index: u8) -> Option<Type> {
self.constants
.get(constant_index as usize)
.map(|value| value.r#type())
}
pub fn get_local_type(&self, local_index: u8) -> Option<Type> {
self.locals.get(local_index as usize)?.r#type.clone()
}
pub fn get_register_type(&self, register_index: u8) -> Option<Type> {
let local_type_option = self
.locals
.iter()
.find(|local| local.register_index == register_index)
.map(|local| local.r#type.clone());
if let Some(local_type) = local_type_option {
return local_type;
}
self.instructions
.iter()
.enumerate()
.find_map(|(index, (instruction, _))| {
if let Operation::LoadList = instruction.operation() {
if instruction.a() == register_index {
let mut length = (instruction.c() - instruction.b() + 1) as usize;
let mut item_type = Type::Any;
let distance_to_end = self.len() - index;
for (instruction, _) in self
.instructions()
.iter()
.rev()
.skip(distance_to_end)
.take(length)
{
if let Operation::Close = instruction.operation() {
length -= (instruction.c() - instruction.b()) as usize;
} else if let Type::Any = item_type {
item_type = instruction.yielded_type(self).unwrap_or(Type::Any);
}
}
return Some(Type::List {
item_type: Box::new(item_type),
length,
});
}
}
if instruction.yields_value() && instruction.a() == register_index {
instruction.yielded_type(self)
} else {
None
}
})
}
pub fn return_type(&self) -> Option<Type> {
let returns_value = self
.instructions()
.last()
.map(|(instruction, _)| {
debug_assert!(matches!(instruction.operation(), Operation::Return));
instruction.b_as_boolean()
})
.unwrap_or(false);
if returns_value {
self.instructions.iter().rev().find_map(|(instruction, _)| {
if instruction.yields_value() {
instruction.yielded_type(self)
} else {
None
}
})
} else {
None
}
}
pub fn disassembler(&self) -> Disassembler {
Disassembler::new(self)
}
}
impl Display for Chunk {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let disassembler = self.disassembler().styled(false);
write!(f, "{}", disassembler.disassemble())
}
}
impl Debug for Chunk {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let disassembly = self.disassembler().styled(false).disassemble();
if cfg!(debug_assertions) {
write!(f, "\n{}", disassembly)
} else {
write!(f, "{}", disassembly)
}
}
}
impl Eq for Chunk {}
impl PartialEq for Chunk {
fn eq(&self, other: &Self) -> bool {
self.instructions == other.instructions
&& self.constants == other.constants
&& self.locals == other.locals
}
}
/// A scoped variable.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Local {
/// The index of the identifier in the constants table.
pub identifier_index: u8,
/// The expected type of the local's value.
pub r#type: Option<Type>,
/// Whether the local is mutable.
pub is_mutable: bool,
/// Scope where the variable was declared.
pub scope: Scope,
/// Expected location of a local's value.
pub register_index: u8,
}
impl Local {
/// Creates a new Local instance.
pub fn new(
identifier_index: u8,
r#type: Option<Type>,
mutable: bool,
scope: Scope,
register_index: u8,
) -> Self {
Self {
identifier_index,
r#type,
is_mutable: mutable,
scope,
register_index,
}
}
}
/// Variable locality, as defined by its depth and block index.
///
/// The `block index` is a unique identifier for a block within a chunk. It is used to differentiate
/// between blocks that are not nested together but have the same depth, i.e. sibling scopes. If the
/// `block_index` is 0, then the scope is the root scope of the chunk. The `block_index` is always 0
/// when the `depth` is 0. See [Chunk::begin_scope][] and [Chunk::end_scope][] to see how scopes are
/// incremented and decremented.
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Scope {
/// Level of block nesting.
pub depth: u8,
/// Index of the block in the chunk.
pub block_index: u8,
}
impl Scope {
pub fn new(depth: u8, block_index: u8) -> Self {
Self { depth, block_index }
}
pub fn contains(&self, other: &Self) -> bool {
match self.depth.cmp(&other.depth) {
Ordering::Less => false,
Ordering::Greater => self.block_index >= other.block_index,
Ordering::Equal => self.block_index == other.block_index,
}
}
}
impl Display for Scope {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.depth, self.block_index)
}
}
/// Errors that can occur when using a [`Chunk`].
#[derive(Clone, Debug, PartialEq)]
pub enum ChunkError {
ConstantIndexOutOfBounds { index: usize },
InstructionIndexOutOfBounds { index: usize },
LocalIndexOutOfBounds { index: usize },
PoisonedChunk,
}
impl Display for ChunkError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
ChunkError::ConstantIndexOutOfBounds { index } => {
write!(f, "Constant index {} out of bounds", index)
}
ChunkError::InstructionIndexOutOfBounds { index } => {
write!(f, "Instruction index {} out of bounds", index)
}
ChunkError::LocalIndexOutOfBounds { index } => {
write!(f, "Local index {} out of bounds", index)
}
ChunkError::PoisonedChunk => write!(f, "Chunk is poisoned"),
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,107 @@
use std::{
collections::HashMap,
fmt::{self, Display, Formatter},
};
use serde::{Deserialize, Serialize};
use crate::{Identifier, Struct, StructType, TypeConflict, Value};
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Constructor {
pub struct_type: StructType,
}
impl Constructor {
pub fn construct_unit(&self) -> Result<Value, ConstructError> {
if let StructType::Unit { name } = &self.struct_type {
Ok(Value::r#struct(Struct::Unit { name: name.clone() }))
} else {
Err(ConstructError::ExpectedUnit)
}
}
pub fn construct_tuple(&self, fields: Vec<Value>) -> Result<Value, ConstructError> {
if let StructType::Tuple {
name: expected_name,
fields: expected_fields,
} = &self.struct_type
{
if fields.len() != expected_fields.len() {
return Err(ConstructError::FieldCountMismatch);
}
for (i, value) in fields.iter().enumerate() {
let expected_type = expected_fields.get(i).unwrap();
let actual_type = value.r#type();
expected_type.check(&actual_type)?;
}
Ok(Value::r#struct(Struct::Tuple {
name: expected_name.clone(),
fields,
}))
} else {
Err(ConstructError::ExpectedTuple)
}
}
pub fn construct_fields(
&self,
fields: HashMap<Identifier, Value>,
) -> Result<Value, ConstructError> {
if let StructType::Fields {
name: expected_name,
fields: expected_fields,
} = &self.struct_type
{
if fields.len() != expected_fields.len() {
return Err(ConstructError::FieldCountMismatch);
}
for (field_name, field_value) in fields.iter() {
let expected_type = expected_fields.get(field_name).unwrap();
let actual_type = field_value.r#type();
expected_type.check(&actual_type)?;
}
Ok(Value::r#struct(Struct::Fields {
name: expected_name.clone(),
fields,
}))
} else {
Err(ConstructError::ExpectedFields)
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum ConstructError {
FieldCountMismatch,
ExpectedUnit,
ExpectedTuple,
ExpectedFields,
TypeConflict(TypeConflict),
}
impl From<TypeConflict> for ConstructError {
fn from(conflict: TypeConflict) -> Self {
Self::TypeConflict(conflict)
}
}
impl Display for ConstructError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
ConstructError::FieldCountMismatch => write!(f, "Field count mismatch"),
ConstructError::ExpectedUnit => write!(f, "Expected unit struct"),
ConstructError::ExpectedTuple => write!(f, "Expected tuple struct"),
ConstructError::ExpectedFields => write!(f, "Expected fields struct"),
ConstructError::TypeConflict(TypeConflict { expected, actual }) => {
write!(f, "Type conflict: expected {}, got {}", expected, actual)
}
}
}
}

651
dust-lang/src/context.rs Normal file
View File

@ -0,0 +1,651 @@
//! Garbage-collecting context for variables.
use std::{
collections::HashMap,
fmt::{self, Display, Formatter},
sync::{
atomic::{AtomicUsize, Ordering},
Arc, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak,
},
};
use crate::{Constructor, Identifier, StructType, Type, Value};
pub type Associations = HashMap<Identifier, (ContextData, usize)>;
static ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
fn next_id() -> usize {
ID_COUNTER.fetch_add(1, Ordering::SeqCst)
}
/// Garbage-collecting context for variables.
#[derive(Debug, Clone)]
pub struct Context {
inner: Arc<ContextInner>,
}
impl Context {
pub fn new() -> Self {
Self::with_data(HashMap::new())
}
pub fn with_data(data: Associations) -> Self {
Self {
inner: Arc::new(ContextInner {
associations: RwLock::new(data),
parent: None,
is_immutable: false,
id: next_id(),
}),
}
}
pub fn with_data_immutable(data: Associations) -> Self {
Self {
inner: Arc::new(ContextInner {
associations: RwLock::new(data),
parent: None,
is_immutable: true,
id: next_id(),
}),
}
}
/// Creates a deep copy of another context.
pub fn with_data_from(other: &Self) -> Result<Self, ContextError> {
let mut associations = HashMap::new();
for (identifier, (context_data, position)) in other.inner.associations.read()?.iter() {
associations.insert(identifier.clone(), (context_data.clone(), *position));
}
Ok(Self::with_data(associations))
}
pub fn create_child(&self) -> Self {
Self {
inner: Arc::new(ContextInner {
associations: RwLock::new(HashMap::new()),
parent: Some(Arc::downgrade(&self.inner)),
is_immutable: false,
id: next_id(),
}),
}
}
pub fn id(&self) -> usize {
self.inner.id
}
/// Returns the number of associated identifiers in the context.
pub fn association_count(&self) -> Result<usize, ContextError> {
self.inner.association_count()
}
/// Returns a boolean indicating whether the identifier is in the context.
pub fn contains(&self, identifier: &Identifier) -> Result<bool, ContextError> {
self.inner.contains(identifier)
}
/// Returns the full ContextData and Span if the context contains the given identifier.
pub fn get(
&self,
identifier: &Identifier,
) -> Result<Option<(ContextData, usize)>, ContextError> {
self.inner.get(identifier)
}
/// Returns the type associated with the given identifier.
pub fn get_type(&self, identifier: &Identifier) -> Result<Option<Type>, ContextError> {
self.inner.get_type(identifier)
}
/// Returns the ContextData associated with the identifier.
pub fn get_data(&self, identifier: &Identifier) -> Result<Option<ContextData>, ContextError> {
self.inner.get_data(identifier)
}
/// Returns the value associated with the identifier.
pub fn get_variable_value(
&self,
identifier: &Identifier,
) -> Result<Option<Value>, ContextError> {
self.inner.get_variable_value(identifier)
}
/// Returns the constructor associated with the identifier.
pub fn get_constructor(
&self,
identifier: &Identifier,
) -> Result<Option<Constructor>, ContextError> {
self.inner.get_constructor(identifier)
}
/// Returns the constructor type associated with the identifier.
pub fn get_constructor_type(
&self,
identifier: &Identifier,
) -> Result<Option<StructType>, ContextError> {
self.inner.get_constructor_type(identifier)
}
/// Associates an identifier with a variable type, with a position given for garbage collection.
pub fn set_variable_type(
&self,
identifier: Identifier,
r#type: Type,
) -> Result<(), ContextError> {
self.inner.set_variable_type(identifier, r#type)
}
/// Associates an identifier with a variable value.
pub fn set_variable_value(
&self,
identifier: Identifier,
value: Value,
) -> Result<(), ContextError> {
self.inner.set_variable_value(identifier, value)
}
/// Associates an identifier with a constructor.
pub fn set_constructor(
&self,
identifier: Identifier,
constructor: Constructor,
) -> Result<(), ContextError> {
self.inner.set_constructor(identifier, constructor)
}
/// Associates an identifier with a constructor type, with a position given for garbage
/// collection.
pub fn set_constructor_type(
&self,
identifier: Identifier,
struct_type: StructType,
) -> Result<(), ContextError> {
self.inner.set_constructor_type(identifier, struct_type)
}
/// Collects garbage up to the given position, removing all variables with lesser positions.
pub fn collect_garbage(&self, position: usize) -> Result<(), ContextError> {
self.inner.collect_garbage(position)
}
/// Updates an associated identifier's last known position, allowing it to live longer in the
/// program. Returns a boolean indicating whether the identifier was found. If the identifier is
/// not found in the current context, the parent context is searched but parent context's
/// position is not updated.
pub fn set_position(
&self,
identifier: &Identifier,
position: usize,
) -> Result<bool, ContextError> {
self.inner.set_position(identifier, position)
}
/// Recovers the context from a poisoned state by recovering data from an error.
///
/// This method is not used.
pub fn _recover_from_poison(&mut self, recovered: &RwLockReadGuard<Associations>) {
log::debug!("Context is recovering from poison error");
let mut new_associations = HashMap::new();
for (identifier, (context_data, position)) in recovered.iter() {
new_associations.insert(identifier.clone(), (context_data.clone(), *position));
}
self.inner = Arc::new(ContextInner {
associations: RwLock::new(new_associations),
parent: None,
is_immutable: false,
id: next_id(),
});
}
}
impl Default for Context {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct ContextInner {
id: usize,
associations: RwLock<Associations>,
parent: Option<Weak<ContextInner>>,
is_immutable: bool,
}
impl ContextInner {
fn parent(&self) -> Option<Arc<ContextInner>> {
self.parent.as_ref().and_then(|parent| parent.upgrade())
}
/// Returns the number of associated identifiers in the context.
pub fn association_count(&self) -> Result<usize, ContextError> {
Ok(self.associations.read()?.len())
}
/// Returns a boolean indicating whether the identifier is in the context.
pub fn contains(&self, identifier: &Identifier) -> Result<bool, ContextError> {
if self.associations.read()?.contains_key(identifier) {
Ok(true)
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
parent.contains(identifier)
} else {
Ok(false)
}
} else {
Ok(false)
}
}
/// Returns the full ContextData and Span if the context contains the given identifier.
pub fn get(
&self,
identifier: &Identifier,
) -> Result<Option<(ContextData, usize)>, ContextError> {
if let Some((variable_data, position)) = self.associations.read()?.get(identifier) {
return Ok(Some((variable_data.clone(), *position)));
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get(identifier);
}
}
Ok(None)
}
/// Returns the type associated with the given identifier.
pub fn get_type(&self, identifier: &Identifier) -> Result<Option<Type>, ContextError> {
match self.associations.read()?.get(identifier) {
Some((ContextData::VariableType(r#type), _)) => return Ok(Some(r#type.clone())),
Some((ContextData::VariableValue(value), _)) => return Ok(Some(value.r#type())),
Some((ContextData::ConstructorType(struct_type), _)) => {
return Ok(Some(Type::Struct(struct_type.clone())))
}
_ => {}
}
if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get_type(identifier);
}
}
Ok(None)
}
/// Returns the ContextData associated with the identifier.
pub fn get_data(&self, identifier: &Identifier) -> Result<Option<ContextData>, ContextError> {
if let Some((variable_data, _)) = self.associations.read()?.get(identifier) {
return Ok(Some(variable_data.clone()));
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get_data(identifier);
}
}
Ok(None)
}
/// Returns the value associated with the identifier.
pub fn get_variable_value(
&self,
identifier: &Identifier,
) -> Result<Option<Value>, ContextError> {
if let Some((ContextData::VariableValue(value), _)) =
self.associations.read()?.get(identifier)
{
return Ok(Some(value.clone()));
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get_variable_value(identifier);
}
}
Ok(None)
}
/// Returns the constructor associated with the identifier.
pub fn get_constructor(
&self,
identifier: &Identifier,
) -> Result<Option<Constructor>, ContextError> {
if let Some((ContextData::Constructor(constructor), _)) =
self.associations.read()?.get(identifier)
{
return Ok(Some(constructor.clone()));
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get_constructor(identifier);
}
}
Ok(None)
}
/// Returns the constructor type associated with the identifier.
pub fn get_constructor_type(
&self,
identifier: &Identifier,
) -> Result<Option<StructType>, ContextError> {
let read_associations = self.associations.read()?;
if let Some((context_data, _)) = read_associations.get(identifier) {
return match context_data {
ContextData::Constructor(constructor) => Ok(Some(constructor.struct_type.clone())),
ContextData::ConstructorType(struct_type) => Ok(Some(struct_type.clone())),
_ => Ok(None),
};
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get_constructor_type(identifier);
}
}
Ok(None)
}
/// Associates an identifier with a variable type.
pub fn set_variable_type(
&self,
identifier: Identifier,
r#type: Type,
) -> Result<(), ContextError> {
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
log::trace!("Setting {identifier} to type {type} in context {}", self.id);
let mut associations = self.associations.write()?;
let last_position = associations
.get(&identifier)
.map(|(_, last_position)| *last_position)
.unwrap_or_default();
associations.insert(
identifier,
(ContextData::VariableType(r#type), last_position),
);
Ok(())
}
/// Associates an identifier with a variable value.
pub fn set_variable_value(
&self,
identifier: Identifier,
value: Value,
) -> Result<(), ContextError> {
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
log::trace!(
"Setting {identifier} to value {value} in context {}",
self.id
);
let mut associations = self.associations.write()?;
let last_position = associations
.get(&identifier)
.map(|(_, last_position)| *last_position)
.unwrap_or_default();
associations.insert(
identifier,
(ContextData::VariableValue(value), last_position),
);
Ok(())
}
/// Associates an identifier with a constructor.
pub fn set_constructor(
&self,
identifier: Identifier,
constructor: Constructor,
) -> Result<(), ContextError> {
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
log::trace!(
"Setting {identifier} to constructor {constructor:?} in context {}",
self.id
);
let mut associations = self.associations.write()?;
let last_position = associations
.get(&identifier)
.map(|(_, last_position)| *last_position)
.unwrap_or_default();
associations.insert(
identifier,
(ContextData::Constructor(constructor), last_position),
);
Ok(())
}
/// Associates an identifier with a constructor type, with a position given for garbage
/// collection.
pub fn set_constructor_type(
&self,
identifier: Identifier,
struct_type: StructType,
) -> Result<(), ContextError> {
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
log::trace!(
"Setting {identifier} to constructor of type {struct_type} in context {}",
self.id
);
let mut variables = self.associations.write()?;
let last_position = variables
.get(&identifier)
.map(|(_, last_position)| *last_position)
.unwrap_or_default();
variables.insert(
identifier,
(ContextData::ConstructorType(struct_type), last_position),
);
Ok(())
}
/// Collects garbage up to the given position, removing all variables with lesser positions.
pub fn collect_garbage(&self, position: usize) -> Result<(), ContextError> {
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
log::trace!("Collecting garbage up to {position} in context {}", self.id);
let mut variables = self.associations.write()?;
variables.retain(|identifier, (_, last_used)| {
let should_drop = position >= *last_used;
if should_drop {
log::trace!("Removing {identifier} from context {}", self.id);
}
!should_drop
});
variables.shrink_to_fit();
Ok(())
}
/// Updates an associated identifier's last known position, allowing it to live longer in the
/// program. Returns a boolean indicating whether the identifier was found. If the identifier is
/// not found in the current context, the parent context is searched but parent context's
/// position is not updated.
pub fn set_position(
&self,
identifier: &Identifier,
position: usize,
) -> Result<bool, ContextError> {
let found = self.update_position_if_found(identifier, position)?;
if found {
return Ok(true);
}
let found_in_ancestor = if let Some(parent) = &self.parent() {
if parent.is_immutable {
false
} else {
parent.update_position_if_found(identifier, position)?
}
} else {
false
};
if found_in_ancestor {
return Ok(true);
}
let mut associations = self.associations.write()?;
log::trace!(
"Reserving {identifier} at position {position:?} in context {}",
self.id
);
associations.insert(identifier.clone(), (ContextData::Reserved, position));
Ok(false)
}
fn update_position_if_found(
&self,
identifier: &Identifier,
position: usize,
) -> Result<bool, ContextError> {
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
let mut associations = self.associations.write()?;
if let Some((_, last_position)) = associations.get_mut(identifier) {
log::trace!(
"Updating {identifier}'s last position to {position:?} in context {}",
self.id
);
*last_position = position;
Ok(true)
} else {
Ok(false)
}
}
}
#[derive(Debug, Clone)]
pub enum ContextData {
Constructor(Constructor),
ConstructorType(StructType),
VariableValue(Value),
VariableType(Type),
Reserved,
}
#[derive(Debug, Clone)]
pub enum ContextError {
CannotMutateImmutableContext,
PoisonErrorRecovered(Arc<Associations>),
}
impl From<PoisonError<RwLockWriteGuard<'_, Associations>>> for ContextError {
fn from(error: PoisonError<RwLockWriteGuard<'_, Associations>>) -> Self {
let associations = error.into_inner().clone();
Self::PoisonErrorRecovered(Arc::new(associations))
}
}
impl From<PoisonError<RwLockReadGuard<'_, Associations>>> for ContextError {
fn from(error: PoisonError<RwLockReadGuard<'_, Associations>>) -> Self {
let associations = error.into_inner().clone();
Self::PoisonErrorRecovered(Arc::new(associations))
}
}
impl PartialEq for ContextError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::CannotMutateImmutableContext, Self::CannotMutateImmutableContext) => true,
(Self::PoisonErrorRecovered(left), Self::PoisonErrorRecovered(right)) => {
Arc::ptr_eq(left, right)
}
_ => false,
}
}
}
impl Display for ContextError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::CannotMutateImmutableContext => write!(f, "Cannot mutate immutable context"),
Self::PoisonErrorRecovered(associations) => {
write!(
f,
"Context poisoned with {} associations recovered",
associations.len()
)
}
}
}
}
#[cfg(test)]
mod tests {
use crate::{parse, Vm};
use super::*;
#[test]
fn context_removes_variables() {
let source = "
let x = 5;
let y = 10;
let z = x + y;
z
";
let ast = parse(source).unwrap();
let context = ast.context.clone();
assert_eq!(Vm.run(ast), Ok(Some(Value::integer(15))));
assert_eq!(context.association_count().unwrap(), 0);
}
#[test]
fn garbage_collector_does_not_break_loops() {
let source = "
let mut z = 0;
while z < 10 {
z += 1;
}
";
let ast = parse(source).unwrap();
let context = ast.context.clone();
assert_eq!(Vm.run(ast), Ok(None));
assert_eq!(context.association_count().unwrap(), 0);
}
}

View File

@ -0,0 +1,39 @@
use std::{collections::HashMap, sync::OnceLock};
use crate::{BuiltInFunction, Context, ContextData, Function, Identifier, Value};
static CORE_LIBRARY: OnceLock<Context> = OnceLock::new();
pub fn core_library<'a>() -> &'a Context {
CORE_LIBRARY.get_or_init(|| {
Context::with_data_immutable(HashMap::from([
(
Identifier::new("to_string"),
(
ContextData::VariableValue(Value::function(Function::BuiltIn(
BuiltInFunction::ToString,
))),
0,
),
),
(
Identifier::new("read_line"),
(
ContextData::VariableValue(Value::function(Function::BuiltIn(
BuiltInFunction::ReadLine,
))),
0,
),
),
(
Identifier::new("write_line"),
(
ContextData::VariableValue(Value::function(Function::BuiltIn(
BuiltInFunction::WriteLine,
))),
0,
),
),
]))
})
}

View File

@ -1,366 +0,0 @@
//! Tool for disassembling chunks into a human-readable format.
//!
//! A disassembler can be created by calling [Chunk::disassembler][] or by instantiating one with
//! [Disassembler::new][].
//!
//! # Options
//!
//! The disassembler can be customized with the 'styled' option, which will apply ANSI color codes
//! to the output.
//!
//! # Output
//!
//! The output of [Disassembler::disassemble] is a string that can be printed to the console or
//! written to a file. Below is an example of the disassembly for a simple "Hello, world!" program.
//!
//! ```text
//! ┌──────────────────────────────────────────────────────────────────────────────┐
//! │ <file name omitted> │
//! │ │
//! │ write_line("Hello, world!") │
//! │ │
//! │ 3 instructions, 1 constants, 0 locals, returns none │
//! │ │
//! │ Instructions │
//! │ ------------ │
//! │ i BYTECODE OPERATION INFO TYPE POSITION │
//! │--- -------- ------------- -------------------- ---------------- ------------ │
//! │ 0 03 LOAD_CONSTANT R0 = C0 str (11, 26) │
//! │ 1 1390117 CALL_NATIVE write_line(R0) (0, 27) │
//! │ 2 18 RETURN (27, 27) │
//! │┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈│
//! │ Locals │
//! │ ------ │
//! │ i IDENTIFIER TYPE MUTABLE SCOPE REGISTER │
//! │ --- ---------- ---------------- ------- ------- -------- │
//! │┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈│
//! │ Constants │
//! │ --------- │
//! │ i VALUE │
//! │ --- --------------- │
//! │ 0 Hello, world! │
//! └──────────────────────────────────────────────────────────────────────────────┘
//! ```
use std::env::current_exe;
use colored::Colorize;
use crate::{Chunk, ConcreteValue, Local, Value};
const INSTRUCTION_HEADER: [&str; 4] = [
"Instructions",
"------------",
" i BYTECODE OPERATION INFO TYPE POSITION ",
"--- -------- ------------- -------------------- ---------------- ----------",
];
const CONSTANT_HEADER: [&str; 4] = [
"Constants",
"---------",
" i VALUE ",
"--- ---------------",
];
const LOCAL_HEADER: [&str; 4] = [
"Locals",
"------",
" i IDENTIFIER TYPE MUTABLE SCOPE REGISTER",
"--- ---------- ---------------- ------- ------- --------",
];
/// Builder that constructs a human-readable representation of a chunk.
///
/// See the [module-level documentation](index.html) for more information.
pub struct Disassembler<'a> {
output: String,
chunk: &'a Chunk,
source: Option<&'a str>,
// Options
styled: bool,
indent: usize,
}
impl<'a> Disassembler<'a> {
pub fn new(chunk: &'a Chunk) -> Self {
Self {
output: String::new(),
chunk,
source: None,
styled: false,
indent: 0,
}
}
/// The default width of the disassembly output. To correctly align the output, this should
/// return the width of the longest line that the disassembler is guaranteed to produce.
pub fn default_width() -> usize {
let longest_line = INSTRUCTION_HEADER[3];
longest_line.chars().count().max(80)
}
pub fn source(mut self, source: &'a str) -> Self {
self.source = Some(source);
self
}
pub fn styled(mut self, styled: bool) -> Self {
self.styled = styled;
self
}
pub fn indent(mut self, indent: usize) -> Self {
self.indent = indent;
self
}
fn push(
&mut self,
text: &str,
center: bool,
style_bold: bool,
style_dim: bool,
add_border: bool,
) {
let width = Disassembler::default_width();
let characters = text.chars().collect::<Vec<char>>();
let content_width = if add_border { width - 2 } else { width };
let (line_characters, remainder) = characters
.split_at_checked(content_width)
.unwrap_or((characters.as_slice(), &[]));
let (left_pad_length, right_pad_length) = {
let extra_space = content_width.saturating_sub(characters.len());
if center {
(extra_space / 2, extra_space / 2 + extra_space % 2)
} else {
(0, extra_space)
}
};
let content = if style_bold {
line_characters
.iter()
.collect::<String>()
.bold()
.to_string()
} else if style_dim {
line_characters
.iter()
.collect::<String>()
.dimmed()
.to_string()
} else {
line_characters.iter().collect::<String>()
};
let length_before_content = self.output.chars().count();
for _ in 0..self.indent {
self.output.push_str("");
}
if add_border {
self.output.push('│');
}
self.output.push_str(&" ".repeat(left_pad_length));
self.output.push_str(&content);
self.output.push_str(&" ".repeat(right_pad_length));
let length_after_content = self.output.chars().count();
let line_length = length_after_content - length_before_content;
if line_length < content_width - 1 {
self.output
.push_str(&" ".repeat(content_width - line_length));
}
if add_border {
self.output.push('│');
}
self.output.push('\n');
if !remainder.is_empty() {
self.push(
remainder.iter().collect::<String>().as_str(),
center,
style_bold,
style_dim,
add_border,
);
}
}
fn push_header(&mut self, header: &str) {
self.push(header, true, self.styled, false, true);
}
fn push_details(&mut self, details: &str) {
self.push(details, true, false, false, true);
}
fn push_border(&mut self, border: &str) {
self.push(border, false, false, false, false);
}
fn push_empty(&mut self) {
self.push("", false, false, false, true);
}
pub fn disassemble(mut self) -> String {
let width = Disassembler::default_width();
let top_border = "".to_string() + &"".repeat(width - 2) + "";
let section_border = "".to_string() + &"".repeat(width - 2) + "";
let bottom_border = "".to_string() + &"".repeat(width - 2) + "";
let name_display = self
.chunk
.name()
.map(|identifier| identifier.to_string())
.unwrap_or_else(|| {
current_exe()
.map(|path| path.to_string_lossy().to_string())
.unwrap_or("Chunk Disassembly".to_string())
});
self.push_border(&top_border);
self.push_header(&name_display);
if let Some(source) = self.source {
self.push_empty();
self.push_details(
&source
.replace(" ", "")
.replace("\n\n", " ")
.replace('\n', " "),
);
self.push_empty();
}
let info_line = format!(
"{} instructions, {} constants, {} locals, returns {}",
self.chunk.len(),
self.chunk.constants().len(),
self.chunk.locals().len(),
self.chunk
.return_type()
.map(|r#type| r#type.to_string())
.unwrap_or("none".to_string())
);
self.push(&info_line, true, false, true, true);
self.push_empty();
for line in INSTRUCTION_HEADER {
self.push_header(line);
}
for (index, (instruction, position)) in self.chunk.instructions().iter().enumerate() {
let bytecode = format!("{:02X}", u32::from(instruction));
let operation = instruction.operation().to_string();
let info = instruction.disassembly_info(self.chunk);
let type_display = instruction
.yielded_type(self.chunk)
.map(|r#type| {
let type_string = r#type.to_string();
if type_string.len() > 16 {
format!("{type_string:.13}...")
} else {
type_string
}
})
.unwrap_or(String::with_capacity(0));
let position = position.to_string();
let instruction_display = format!(
"{index:^3} {bytecode:>8} {operation:13} {info:^20} {type_display:^16} {position:10}"
);
self.push_details(&instruction_display);
}
self.push_border(&section_border);
for line in LOCAL_HEADER {
self.push_header(line);
}
for (
index,
Local {
identifier_index,
r#type,
scope,
register_index,
is_mutable: mutable,
},
) in self.chunk.locals().iter().enumerate()
{
let identifier_display = self
.chunk
.constants()
.get(*identifier_index as usize)
.map(|value| value.to_string())
.unwrap_or_else(|| "unknown".to_string());
let type_display = r#type
.as_ref()
.map(|r#type| {
let type_string = r#type.to_string();
if type_string.len() > 16 {
format!("{type_string:.13}...")
} else {
type_string
}
})
.unwrap_or("unknown".to_string());
let local_display = format!(
"{index:^3} {identifier_display:10} {type_display:16} {mutable:7} {scope:7} {register_index:8}"
);
self.push_details(&local_display);
}
self.push_border(&section_border);
for line in CONSTANT_HEADER {
self.push_header(line);
}
for (index, value) in self.chunk.constants().iter().enumerate() {
let value_display = {
let value_string = value.to_string();
if value_string.len() > 15 {
format!("{value_string:.12}...")
} else {
value_string
}
};
let constant_display = format!("{index:^3} {value_display:^15}");
self.push_details(&constant_display);
if let Value::Concrete(ConcreteValue::Function(function)) = value {
let function_disassembly = function
.chunk()
.disassembler()
.styled(self.styled)
.indent(self.indent + 1)
.disassemble();
self.output.push_str(&function_disassembly);
}
}
self.push_border(&bottom_border);
let _ = self.output.trim_end_matches('\n');
self.output
}
}

View File

@ -1,53 +1,167 @@
//! Top-level Dust errors with source code annotations. //! Top-level error handling for the Dust language.
use annotate_snippets::{Level, Renderer, Snippet}; use annotate_snippets::{Level, Renderer, Snippet};
use std::fmt::Display;
use crate::{vm::VmError, CompileError, Span}; use crate::{AnalysisError, ContextError, LexError, ParseError, RuntimeError};
/// A top-level error that can occur during the execution of Dust code. /// An error that occurred during the execution of the Dust language and its
/// /// corresponding source code.
/// This error can display nicely formatted messages with source code annotations. #[derive(Debug, Clone, PartialEq)]
#[derive(Debug, PartialEq)]
pub enum DustError<'src> { pub enum DustError<'src> {
Compile { ContextError(ContextError),
error: CompileError, Runtime {
runtime_error: RuntimeError,
source: &'src str, source: &'src str,
}, },
Runtime { Analysis {
error: VmError, analysis_errors: Vec<AnalysisError>,
source: &'src str,
},
Parse {
parse_error: ParseError,
source: &'src str,
},
Lex {
lex_error: LexError,
source: &'src str, source: &'src str,
}, },
} }
impl<'src> From<ContextError> for DustError<'src> {
fn from(error: ContextError) -> Self {
Self::ContextError(error)
}
}
impl<'src> DustError<'src> { impl<'src> DustError<'src> {
pub fn runtime(runtime_error: RuntimeError, source: &'src str) -> Self {
DustError::Runtime {
runtime_error,
source,
}
}
pub fn analysis<T: Into<Vec<AnalysisError>>>(analysis_errors: T, source: &'src str) -> Self {
DustError::Analysis {
analysis_errors: analysis_errors.into(),
source,
}
}
pub fn parse(parse_error: ParseError, source: &'src str) -> Self {
DustError::Parse {
parse_error,
source,
}
}
pub fn lex(lex_error: LexError, source: &'src str) -> Self {
DustError::Lex { lex_error, source }
}
pub fn title(&self) -> &'static str {
match self {
DustError::ContextError(_) => "Context error",
DustError::Runtime { .. } => "Runtime error",
DustError::Analysis { .. } => "Analysis error",
DustError::Parse { .. } => "Parse error",
DustError::Lex { .. } => "Lex error",
}
}
pub fn source(&self) -> &'src str {
match self {
DustError::ContextError(_) => "",
DustError::Runtime { source, .. } => source,
DustError::Analysis { source, .. } => source,
DustError::Parse { source, .. } => source,
DustError::Lex { source, .. } => source,
}
}
pub fn report(&self) -> String { pub fn report(&self) -> String {
let mut report = String::new(); let mut report = String::new();
let renderer = Renderer::styled(); let renderer = Renderer::styled();
match self { match self {
DustError::Runtime { error, source } => { DustError::ContextError(_) => {
let message = Level::Error.title("Context error");
report.push_str(&renderer.render(message).to_string());
}
DustError::Runtime {
runtime_error,
source,
} => {
let error = runtime_error.root_error();
let position = error.position(); let position = error.position();
let label = format!("{}: {}", VmError::title(), error.description()); let label = error.to_string();
let details = error let message = Level::Error
.details() .title("Runtime error")
.unwrap_or_else(|| "While running this code".to_string()); .snippet(
let message = Level::Error.title(&label).snippet(
Snippet::source(source) Snippet::source(source)
.fold(false) .fold(true)
.annotation(Level::Error.span(position.0..position.1).label(&details)), .annotation(Level::Error.span(position.0..position.1).label(&label)),
)
.footer(
Level::Error
.title("This error occured during the execution of the Dust program."),
);
report.push_str(&renderer.render(message).to_string());
report.push_str("\n\n");
}
DustError::Analysis {
analysis_errors,
source,
} => {
for error in analysis_errors {
let position = error.position();
let label = error.to_string();
let message =
Level::Warning
.title("Analysis error")
.snippet(Snippet::source(source).fold(true).annotation(
Level::Warning.span(position.0..position.1).label(&label),
))
.footer(
Level::Warning
.title("This error was found without running the program."),
);
report.push_str(&renderer.render(message).to_string());
report.push_str("\n\n");
}
}
DustError::Parse {
parse_error,
source,
} => {
if let ParseError::Lex(lex_error) = parse_error {
let lex_error_report = DustError::lex(lex_error.clone(), source).report();
report.push_str(&lex_error_report);
return report;
}
let position = parse_error.position();
let label = parse_error.to_string();
let message = Level::Error.title("Parse error").snippet(
Snippet::source(source)
.fold(true)
.annotation(Level::Error.span(position.0..position.1).label(&label)),
); );
report.push_str(&renderer.render(message).to_string()); report.push_str(&renderer.render(message).to_string());
} }
DustError::Compile { error, source } => { DustError::Lex { lex_error, source } => {
let position = error.position(); let position = lex_error.position();
let label = format!("{}: {}", CompileError::title(), error.description()); let label = lex_error.to_string();
let details = error let message = Level::Error.title("Lex error").snippet(
.details()
.unwrap_or_else(|| "While parsing this code".to_string());
let message = Level::Error.title(&label).snippet(
Snippet::source(source) Snippet::source(source)
.fold(false) .fold(true)
.annotation(Level::Error.span(position.0..position.1).label(&details)), .annotation(Level::Error.span(position.0..position.1).label(&label)),
); );
report.push_str(&renderer.render(message).to_string()); report.push_str(&renderer.render(message).to_string());
@ -58,9 +172,22 @@ impl<'src> DustError<'src> {
} }
} }
pub trait AnnotatedError { impl Display for DustError<'_> {
fn title() -> &'static str; fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn description(&self) -> &'static str; match self {
fn details(&self) -> Option<String>; DustError::ContextError(context_error) => write!(f, "{context_error}"),
fn position(&self) -> Span; DustError::Runtime { runtime_error, .. } => write!(f, "{runtime_error}"),
DustError::Analysis {
analysis_errors, ..
} => {
for error in analysis_errors {
write!(f, "{error} ")?;
}
Ok(())
}
DustError::Parse { parse_error, .. } => write!(f, "{parse_error}"),
DustError::Lex { lex_error, .. } => write!(f, "{lex_error}"),
}
}
} }

View File

@ -0,0 +1,41 @@
use crate::{Constructor, RuntimeError, Span, StructType, Type, Value};
#[derive(Debug, Clone, PartialEq)]
pub enum Evaluation {
Break(Option<Value>),
Constructor(Constructor),
Return(Option<Value>),
}
impl Evaluation {
pub fn value(self) -> Option<Value> {
match self {
Evaluation::Return(value_option) => value_option,
_ => None,
}
}
pub fn expect_value(self, position: Span) -> Result<Value, RuntimeError> {
if let Evaluation::Return(Some(value)) = self {
Ok(value)
} else {
Err(RuntimeError::ExpectedValue { position })
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum TypeEvaluation {
Break(Option<Type>),
Constructor(StructType),
Return(Option<Type>),
}
impl TypeEvaluation {
pub fn r#type(self) -> Option<Type> {
match self {
TypeEvaluation::Return(type_option) => type_option,
_ => None,
}
}
}

View File

@ -1,224 +0,0 @@
//! Formatting tools
use std::mem::replace;
use colored::{ColoredString, Colorize, CustomColor};
use crate::{CompileError, DustError, LexError, Lexer, Token};
pub fn format(source: &str, line_numbers: bool, colored: bool) -> Result<String, DustError> {
let lexer = Lexer::new(source);
let formatted = Formatter::new(lexer)
.line_numbers(line_numbers)
.colored(colored)
.format()
.map_err(|error| DustError::Compile {
error: CompileError::Lex(error),
source,
})?;
Ok(formatted)
}
#[derive(Debug)]
pub struct Formatter<'src> {
lexer: Lexer<'src>,
output_lines: Vec<(String, LineKind, usize)>,
next_line: String,
indent: usize,
current_token: Token<'src>,
previous_token: Token<'src>,
// Options
line_numbers: bool,
colored: bool,
}
impl<'src> Formatter<'src> {
pub fn new(mut lexer: Lexer<'src>) -> Self {
let (current_token, _) = lexer.next_token().unwrap();
Self {
lexer,
output_lines: Vec::new(),
next_line: String::new(),
indent: 0,
current_token,
previous_token: Token::Eof,
line_numbers: false,
colored: false,
}
}
pub fn line_numbers(mut self, line_numbers: bool) -> Self {
self.line_numbers = line_numbers;
self
}
pub fn colored(mut self, colored: bool) -> Self {
self.colored = colored;
self
}
pub fn format(&mut self) -> Result<String, LexError> {
let mut line_kind = LineKind::Empty;
self.advance()?;
while self.current_token != Token::Eof {
use Token::*;
if self.current_token.is_expression() && line_kind != LineKind::Assignment {
line_kind = LineKind::Expression;
}
match self.current_token {
Boolean(boolean) => {
self.push_colored(boolean.red());
}
Byte(byte) => {
self.push_colored(byte.green());
}
Character(character) => {
self.push_colored(
character
.to_string()
.custom_color(CustomColor::new(225, 150, 150)),
);
}
Float(float) => {
self.push_colored(float.yellow());
}
Identifier(identifier) => {
self.push_colored(identifier.blue());
self.next_line.push(' ');
}
Integer(integer) => {
self.push_colored(integer.cyan());
}
String(string) => {
self.push_colored(string.magenta());
}
LeftCurlyBrace => {
self.next_line.push_str(self.current_token.as_str());
self.commit_line(LineKind::OpenBlock);
self.indent += 1;
}
RightCurlyBrace => {
self.commit_line(LineKind::CloseBlock);
self.next_line.push_str(self.current_token.as_str());
self.indent -= 1;
}
Semicolon => {
if line_kind != LineKind::Assignment {
line_kind = LineKind::Statement;
}
self.next_line.push_str(self.current_token.as_str());
self.commit_line(line_kind);
}
Let => {
line_kind = LineKind::Assignment;
self.push_colored(self.current_token.as_str().bold());
self.next_line.push(' ');
}
Break | Loop | Return | While => {
line_kind = LineKind::Statement;
self.push_colored(self.current_token.as_str().bold());
self.next_line.push(' ');
}
token => {
self.next_line.push_str(token.as_str());
self.next_line.push(' ');
}
}
}
let mut previous_index = 0;
let mut current_index = 1;
while current_index < self.output_lines.len() {
let (_, previous, _) = &self.output_lines[previous_index];
let (_, current, _) = &self.output_lines[current_index];
match (previous, current) {
(LineKind::Empty, _)
| (_, LineKind::Empty)
| (LineKind::OpenBlock, _)
| (_, LineKind::CloseBlock) => {}
(left, right) if left == right => {}
_ => {
self.output_lines
.insert(current_index, ("".to_string(), LineKind::Empty, 0));
}
}
previous_index += 1;
current_index += 1;
}
let formatted = String::with_capacity(
self.output_lines
.iter()
.fold(0, |total, (line, _, _)| total + line.len()),
);
Ok(self.output_lines.iter().enumerate().fold(
formatted,
|acc, (index, (line, _, indent))| {
let index = if index == 0 {
format!("{:<3}| ", index + 1).dimmed()
} else {
format!("\n{:<3}| ", index + 1).dimmed()
};
let left_pad = " ".repeat(*indent);
format!("{}{}{}{}", acc, index, left_pad, line)
},
))
}
fn advance(&mut self) -> Result<(), LexError> {
if self.lexer.is_eof() {
return Ok(());
}
let (new_token, position) = self.lexer.next_token()?;
log::info!(
"Parsing {} at {}",
new_token.to_string().bold(),
position.to_string()
);
self.previous_token = replace(&mut self.current_token, new_token);
Ok(())
}
fn push_colored(&mut self, colored: ColoredString) {
self.next_line.push_str(&format!("{}", colored));
}
fn commit_line(&mut self, line_kind: LineKind) {
self.output_lines
.push((self.next_line.clone(), line_kind, self.indent));
self.next_line.clear();
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LineKind {
Empty,
Assignment,
Expression,
Statement,
OpenBlock,
CloseBlock,
}

137
dust-lang/src/identifier.rs Normal file
View File

@ -0,0 +1,137 @@
//! Key used to identify a value or type.
//!
//! Identifiers are used to uniquely identify values and types in Dust programs. They are
//! cached to avoid duplication. This means that two identifiers with the same text are the same
//! object in memory.
//!
//! # Examples
//! ```
//! # use dust_lang::Identifier;
//! let foo = Identifier::new("foo");
//! let also_foo = Identifier::new("foo");
//! let another_foo = Identifier::new("foo");
//!
//! assert_eq!(foo.strong_count(), 4); // One for each of the above and one for the cache.
//! ```
use std::{
collections::HashMap,
fmt::{self, Display, Formatter},
hash::Hash,
sync::{Arc, OnceLock, RwLock},
};
use serde::{de::Visitor, Deserialize, Serialize};
/// In-use identifiers.
static IDENTIFIER_CACHE: OnceLock<RwLock<HashMap<String, Identifier>>> = OnceLock::new();
/// Returns the identifier cache.
fn identifier_cache<'a>() -> &'a RwLock<HashMap<String, Identifier>> {
IDENTIFIER_CACHE.get_or_init(|| RwLock::new(HashMap::new()))
}
/// Key used to identify a value or type.
///
/// See the [module-level documentation](index.html) for more information.
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct Identifier(Arc<String>);
impl Identifier {
/// Creates a new identifier or returns a clone of an existing one from a cache.
pub fn new<T: ToString>(text: T) -> Self {
let string = text.to_string();
let mut cache = identifier_cache().write().unwrap();
if let Some(old) = cache.get(&string) {
old.clone()
} else {
let new = Identifier(Arc::new(string.clone()));
cache.insert(string, new.clone());
new
}
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn strong_count(&self) -> usize {
Arc::strong_count(&self.0)
}
}
impl From<String> for Identifier {
fn from(string: String) -> Self {
Identifier::new(string)
}
}
impl From<&str> for Identifier {
fn from(slice: &str) -> Self {
Identifier::new(slice)
}
}
impl Display for Identifier {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl Serialize for Identifier {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.0.as_str())
}
}
impl<'de> Deserialize<'de> for Identifier {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_identifier(IdentifierVisitor)
}
}
struct IdentifierVisitor;
impl<'de> Visitor<'de> for IdentifierVisitor {
type Value = Identifier;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a UTF-8 string")
}
fn visit_char<E>(self, v: char) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(v.encode_utf8(&mut [0u8; 4]))
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Identifier::new(v))
}
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(v)
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(&v)
}
}

View File

@ -1,902 +0,0 @@
//! An operation and its arguments for the Dust virtual machine.
//!
//! Each instruction is a 32-bit unsigned integer that is divided into five fields:
//! - Bits 0-6: The operation code.
//! - Bit 7: A flag indicating whether the B argument is a constant.
//! - Bit 8: A flag indicating whether the C argument is a constant.
//! - Bits 9-16: The A argument,
//! - Bits 17-24: The B argument.
//! - Bits 25-32: The C argument.
//!
//! Be careful when working with instructions directly. When modifying an instruction, be sure to
//! account for the fact that setting the A, B, or C arguments to 0 will have no effect. It is
//! usually best to remove instructions and insert new ones in their place instead of mutating them.
use serde::{Deserialize, Serialize};
use crate::{Chunk, NativeFunction, Operation, Type};
/// An operation and its arguments for the Dust virtual machine.
///
/// See the [module-level documentation](index.html) for more information.
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Instruction(u32);
impl Instruction {
pub fn with_operation(operation: Operation) -> Instruction {
Instruction(operation as u32)
}
pub fn r#move(to_register: u8, from_register: u8) -> Instruction {
let mut instruction = Instruction(Operation::Move as u32);
instruction.set_a(to_register);
instruction.set_b(from_register);
instruction
}
pub fn close(from_register: u8, to_register: u8) -> Instruction {
let mut instruction = Instruction(Operation::Close as u32);
instruction.set_b(from_register);
instruction.set_c(to_register);
instruction
}
pub fn load_boolean(to_register: u8, value: bool, skip: bool) -> Instruction {
let mut instruction = Instruction(Operation::LoadBoolean as u32);
instruction.set_a(to_register);
instruction.set_b_to_boolean(value);
instruction.set_c_to_boolean(skip);
instruction
}
pub fn load_constant(to_register: u8, constant_index: u8, skip: bool) -> Instruction {
let mut instruction = Instruction(Operation::LoadConstant as u32);
instruction.set_a(to_register);
instruction.set_b(constant_index);
instruction.set_c_to_boolean(skip);
instruction
}
pub fn load_list(to_register: u8, start_register: u8) -> Instruction {
let mut instruction = Instruction(Operation::LoadList as u32);
instruction.set_a(to_register);
instruction.set_b(start_register);
instruction
}
pub fn load_self(to_register: u8) -> Instruction {
let mut instruction = Instruction(Operation::LoadSelf as u32);
instruction.set_a(to_register);
instruction
}
pub fn define_local(to_register: u8, local_index: u8, is_mutable: bool) -> Instruction {
let mut instruction = Instruction(Operation::DefineLocal as u32);
instruction.set_a(to_register);
instruction.set_b(local_index);
instruction.set_c(if is_mutable { 1 } else { 0 });
instruction
}
pub fn get_local(to_register: u8, local_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::GetLocal as u32);
instruction.set_a(to_register);
instruction.set_b(local_index);
instruction
}
pub fn set_local(from_register: u8, local_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::SetLocal as u32);
instruction.set_a(from_register);
instruction.set_b(local_index);
instruction
}
pub fn add(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Add as u32);
instruction.set_a(to_register);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn subtract(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Subtract as u32);
instruction.set_a(to_register);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn multiply(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Multiply as u32);
instruction.set_a(to_register);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn divide(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Divide as u32);
instruction.set_a(to_register);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn modulo(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Modulo as u32);
instruction.set_a(to_register);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn test(to_register: u8, test_value: bool) -> Instruction {
let mut instruction = Instruction(Operation::Test as u32);
instruction.set_a(to_register);
instruction.set_c_to_boolean(test_value);
instruction
}
pub fn test_set(to_register: u8, argument_index: u8, test_value: bool) -> Instruction {
let mut instruction = Instruction(Operation::TestSet as u32);
instruction.set_a(to_register);
instruction.set_b(argument_index);
instruction.set_c_to_boolean(test_value);
instruction
}
pub fn equal(comparison_boolean: bool, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Equal as u32);
instruction.set_a_to_boolean(comparison_boolean);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn less(comparison_boolean: bool, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Less as u32);
instruction.set_a_to_boolean(comparison_boolean);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn less_equal(comparison_boolean: bool, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::LessEqual as u32);
instruction.set_a_to_boolean(comparison_boolean);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn negate(to_register: u8, from_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Negate as u32);
instruction.set_a(to_register);
instruction.set_b(from_index);
instruction
}
pub fn not(to_register: u8, from_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Not as u32);
instruction.set_a(to_register);
instruction.set_b(from_index);
instruction
}
pub fn jump(jump_offset: u8, is_positive: bool) -> Instruction {
let mut instruction = Instruction(Operation::Jump as u32);
instruction.set_b(jump_offset);
instruction.set_c_to_boolean(is_positive);
instruction
}
pub fn call(to_register: u8, function_register: u8, argument_count: u8) -> Instruction {
let mut instruction = Instruction(Operation::Call as u32);
instruction.set_a(to_register);
instruction.set_b(function_register);
instruction.set_c(argument_count);
instruction
}
pub fn call_native(
to_register: u8,
native_fn: NativeFunction,
argument_count: u8,
) -> Instruction {
let mut instruction = Instruction(Operation::CallNative as u32);
let native_fn_byte = native_fn as u8;
instruction.set_a(to_register);
instruction.set_b(native_fn_byte);
instruction.set_c(argument_count);
instruction
}
pub fn r#return(should_return_value: bool) -> Instruction {
let mut instruction = Instruction(Operation::Return as u32);
instruction.set_b_to_boolean(should_return_value);
instruction
}
pub fn operation(&self) -> Operation {
Operation::from((self.0 & 0b0000_0000_0011_1111) as u8)
}
pub fn set_operation(&mut self, operation: Operation) {
self.0 |= u8::from(operation) as u32;
}
pub fn data(&self) -> (Operation, u8, u8, u8, bool, bool) {
(
self.operation(),
self.a(),
self.b(),
self.c(),
self.b_is_constant(),
self.c_is_constant(),
)
}
pub fn a(&self) -> u8 {
(self.0 >> 24) as u8
}
pub fn b(&self) -> u8 {
(self.0 >> 16) as u8
}
pub fn c(&self) -> u8 {
(self.0 >> 8) as u8
}
pub fn a_as_boolean(&self) -> bool {
self.a() != 0
}
pub fn b_as_boolean(&self) -> bool {
self.b() != 0
}
pub fn c_as_boolean(&self) -> bool {
self.c() != 0
}
pub fn set_a_to_boolean(&mut self, boolean: bool) -> &mut Self {
self.set_a(if boolean { 1 } else { 0 });
self
}
pub fn set_b_to_boolean(&mut self, boolean: bool) -> &mut Self {
self.set_b(if boolean { 1 } else { 0 });
self
}
pub fn set_c_to_boolean(&mut self, boolean: bool) -> &mut Self {
self.set_c(if boolean { 1 } else { 0 });
self
}
pub fn set_a(&mut self, to_register: u8) {
self.0 |= (to_register as u32) << 24;
}
pub fn set_b(&mut self, argument: u8) {
self.0 |= (argument as u32) << 16;
}
pub fn set_c(&mut self, argument: u8) {
self.0 |= (argument as u32) << 8;
}
pub fn b_is_constant(&self) -> bool {
self.0 & 0b1000_0000 != 0
}
pub fn c_is_constant(&self) -> bool {
self.0 & 0b0100_0000 != 0
}
pub fn set_b_is_constant(&mut self) -> &mut Self {
self.0 |= 0b1000_0000;
self
}
pub fn set_c_is_constant(&mut self) -> &mut Self {
self.0 |= 0b0100_0000;
self
}
pub fn yields_value(&self) -> bool {
match self.operation() {
Operation::Add
| Operation::Call
| Operation::Divide
| Operation::GetLocal
| Operation::LoadBoolean
| Operation::LoadConstant
| Operation::LoadList
| Operation::LoadSelf
| Operation::Modulo
| Operation::Multiply
| Operation::Negate
| Operation::Not
| Operation::Subtract => true,
Operation::CallNative => {
let native_function = NativeFunction::from(self.b());
native_function.r#type().return_type.is_some()
}
_ => false,
}
}
pub fn yielded_type(&self, chunk: &Chunk) -> Option<Type> {
use Operation::*;
match self.operation() {
Add | Divide | Modulo | Multiply | Subtract => {
if self.b_is_constant() {
chunk.get_constant_type(self.b())
} else {
chunk.get_register_type(self.b())
}
}
LoadBoolean | Not => Some(Type::Boolean),
Negate => {
if self.b_is_constant() {
chunk.get_constant_type(self.b())
} else {
chunk.get_register_type(self.b())
}
}
LoadConstant => chunk.get_constant_type(self.b()),
LoadList => chunk.get_register_type(self.a()),
GetLocal => chunk.get_local_type(self.b()),
CallNative => {
let native_function = NativeFunction::from(self.b());
native_function.r#type().return_type.map(|boxed| *boxed)
}
_ => None,
}
}
pub fn disassembly_info(&self, chunk: &Chunk) -> String {
let format_arguments = || {
let first_argument = if self.b_is_constant() {
format!("C{}", self.b())
} else {
format!("R{}", self.b())
};
let second_argument = if self.c_is_constant() {
format!("C{}", self.c())
} else {
format!("R{}", self.c())
};
(first_argument, second_argument)
};
match self.operation() {
Operation::Move => format!("R{} = R{}", self.a(), self.b()),
Operation::Close => {
let from_register = self.b();
let to_register = self.c().saturating_sub(1);
format!("R{from_register}..=R{to_register}")
}
Operation::LoadBoolean => {
let to_register = self.a();
let boolean = self.b_as_boolean();
let jump = self.c_as_boolean();
if jump {
format!("R{to_register} = {boolean} && SKIP")
} else {
format!("R{to_register} = {boolean}")
}
}
Operation::LoadConstant => {
let register_index = self.a();
let constant_index = self.b();
let jump = self.c_as_boolean();
if jump {
format!("R{register_index} = C{constant_index} && SKIP")
} else {
format!("R{register_index} = C{constant_index}")
}
}
Operation::LoadList => {
let to_register = self.a();
let first_index = self.b();
let last_index = self.c();
format!("R{to_register} = [R{first_index}..=R{last_index}]",)
}
Operation::LoadSelf => {
let to_register = self.a();
let name = chunk
.name()
.map(|idenifier| idenifier.as_str())
.unwrap_or("self");
format!("R{to_register} = {name}")
}
Operation::DefineLocal => {
let to_register = self.a();
let local_index = self.b();
let identifier_display = match chunk.get_identifier(local_index) {
Some(identifier) => identifier.to_string(),
None => "???".to_string(),
};
let mutable_display = if self.c_as_boolean() { "mut" } else { "" };
format!("R{to_register} = L{local_index} {mutable_display} {identifier_display}")
}
Operation::GetLocal => {
let local_index = self.b();
format!("R{} = L{}", self.a(), local_index)
}
Operation::SetLocal => {
let local_index = self.b();
let identifier_display = match chunk.get_identifier(local_index) {
Some(identifier) => identifier.to_string(),
None => "???".to_string(),
};
format!("L{} = R{} {}", local_index, self.a(), identifier_display)
}
Operation::Add => {
let to_register = self.a();
let (first_argument, second_argument) = format_arguments();
format!("R{to_register} = {first_argument} + {second_argument}",)
}
Operation::Subtract => {
let to_register = self.a();
let (first_argument, second_argument) = format_arguments();
format!("R{to_register} = {first_argument} - {second_argument}",)
}
Operation::Multiply => {
let to_register = self.a();
let (first_argument, second_argument) = format_arguments();
format!("R{to_register} = {first_argument} * {second_argument}",)
}
Operation::Divide => {
let to_register = self.a();
let (first_argument, second_argument) = format_arguments();
format!("R{to_register} = {first_argument} / {second_argument}",)
}
Operation::Modulo => {
let to_register = self.a();
let (first_argument, second_argument) = format_arguments();
format!("R{to_register} = {first_argument} % {second_argument}",)
}
Operation::Test => {
let to_register = self.a();
let test_value = self.c_as_boolean();
format!("if R{to_register} != {test_value} {{ SKIP }}")
}
Operation::TestSet => {
let to_register = self.a();
let argument = format!("R{}", self.b());
let test_value = self.c_as_boolean();
let bang = if test_value { "" } else { "!" };
format!("if {bang}R{to_register} {{ R{to_register} = R{argument} }}",)
}
Operation::Equal => {
let comparison_symbol = if self.a_as_boolean() { "==" } else { "!=" };
let (first_argument, second_argument) = format_arguments();
format!("if {first_argument} {comparison_symbol} {second_argument} {{ SKIP }}")
}
Operation::Less => {
let comparison_symbol = if self.a_as_boolean() { "<" } else { ">=" };
let (first_argument, second_argument) = format_arguments();
format!("if {first_argument} {comparison_symbol} {second_argument} {{ SKIP }}")
}
Operation::LessEqual => {
let comparison_symbol = if self.a_as_boolean() { "<=" } else { ">" };
let (first_argument, second_argument) = format_arguments();
format!("if {first_argument} {comparison_symbol} {second_argument} {{ SKIP }}")
}
Operation::Negate => {
let to_register = self.a();
let argument = if self.b_is_constant() {
format!("C{}", self.b())
} else {
format!("R{}", self.b())
};
format!("R{to_register} = -{argument}")
}
Operation::Not => {
let to_register = self.a();
let argument = if self.b_is_constant() {
format!("C{}", self.b())
} else {
format!("R{}", self.b())
};
format!("R{to_register} = !{argument}")
}
Operation::Jump => {
let jump_distance = self.b();
let is_positive = self.c_as_boolean();
if is_positive {
format!("JUMP +{jump_distance}")
} else {
format!("JUMP -{jump_distance}")
}
}
Operation::Call => {
let to_register = self.a();
let function_register = self.b();
let argument_count = self.c();
let mut output = format!("R{to_register} = R{function_register}(");
if argument_count != 0 {
let first_argument = function_register + 1;
for (index, register) in
(first_argument..first_argument + argument_count).enumerate()
{
if index > 0 {
output.push_str(", ");
}
output.push_str(&format!("R{}", register));
}
}
output.push(')');
output
}
Operation::CallNative => {
let to_register = self.a();
let native_function = NativeFunction::from(self.b());
let argument_count = self.c();
let mut output = String::new();
let native_function_name = native_function.as_str();
if native_function.r#type().return_type.is_some() {
output.push_str(&format!("R{} = {}(", to_register, native_function_name));
} else {
output.push_str(&format!("{}(", native_function_name));
}
if argument_count != 0 {
let first_argument = to_register.saturating_sub(argument_count);
for (index, register) in (first_argument..to_register).enumerate() {
if index > 0 {
output.push_str(", ");
}
output.push_str(&format!("R{}", register));
}
}
output.push(')');
output
}
Operation::Return => {
let should_return_value = self.b_as_boolean();
if should_return_value {
"->".to_string()
} else {
"".to_string()
}
}
}
}
}
impl From<&Instruction> for u32 {
fn from(instruction: &Instruction) -> Self {
instruction.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn r#move() {
let mut instruction = Instruction::r#move(0, 1);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::Move);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
}
#[test]
fn close() {
let instruction = Instruction::close(1, 2);
assert_eq!(instruction.operation(), Operation::Close);
assert_eq!(instruction.b(), 1);
assert_eq!(instruction.c(), 2);
}
#[test]
fn load_boolean() {
let instruction = Instruction::load_boolean(4, true, true);
assert_eq!(instruction.operation(), Operation::LoadBoolean);
assert_eq!(instruction.a(), 4);
assert!(instruction.a_as_boolean());
assert!(instruction.c_as_boolean());
}
#[test]
fn load_constant() {
let mut instruction = Instruction::load_constant(0, 1, true);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::LoadConstant);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
assert!(instruction.c_as_boolean());
}
#[test]
fn load_list() {
let instruction = Instruction::load_list(0, 1);
assert_eq!(instruction.operation(), Operation::LoadList);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
}
#[test]
fn load_self() {
let instruction = Instruction::load_self(10);
assert_eq!(instruction.operation(), Operation::LoadSelf);
assert_eq!(instruction.a(), 10);
}
#[test]
fn declare_local() {
let mut instruction = Instruction::define_local(0, 1, true);
instruction.set_b_is_constant();
assert_eq!(instruction.operation(), Operation::DefineLocal);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert_eq!(instruction.c(), true as u8);
assert!(instruction.b_is_constant());
}
#[test]
fn add() {
let mut instruction = Instruction::add(1, 1, 0);
instruction.set_b_is_constant();
assert_eq!(instruction.operation(), Operation::Add);
assert_eq!(instruction.a(), 1);
assert_eq!(instruction.b(), 1);
assert_eq!(instruction.c(), 0);
assert!(instruction.b_is_constant());
}
#[test]
fn subtract() {
let mut instruction = Instruction::subtract(0, 1, 2);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::Subtract);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert_eq!(instruction.c(), 2);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
}
#[test]
fn multiply() {
let mut instruction = Instruction::multiply(0, 1, 2);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::Multiply);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert_eq!(instruction.c(), 2);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
}
#[test]
fn divide() {
let mut instruction = Instruction::divide(0, 1, 2);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::Divide);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert_eq!(instruction.c(), 2);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
}
#[test]
fn and() {
let instruction = Instruction::test(4, true);
assert_eq!(instruction.operation(), Operation::Test);
assert_eq!(instruction.a(), 4);
assert!(instruction.c_as_boolean());
}
#[test]
fn or() {
let instruction = Instruction::test_set(4, 1, true);
assert_eq!(instruction.operation(), Operation::TestSet);
assert_eq!(instruction.a(), 4);
assert_eq!(instruction.b(), 1);
assert!(instruction.c_as_boolean());
}
#[test]
fn equal() {
let mut instruction = Instruction::equal(true, 1, 2);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::Equal);
assert!(instruction.a_as_boolean());
assert_eq!(instruction.b(), 1);
assert_eq!(instruction.c(), 2);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
}
#[test]
fn negate() {
let mut instruction = Instruction::negate(0, 1);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::Negate);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
}
#[test]
fn not() {
let mut instruction = Instruction::not(0, 1);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::Not);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
}
#[test]
fn jump() {
let instruction = Instruction::jump(4, true);
assert_eq!(instruction.operation(), Operation::Jump);
assert_eq!(instruction.b(), 4);
assert!(instruction.c_as_boolean());
}
#[test]
fn call() {
let instruction = Instruction::call(1, 3, 4);
assert_eq!(instruction.operation(), Operation::Call);
assert_eq!(instruction.a(), 1);
assert_eq!(instruction.b(), 3);
assert_eq!(instruction.c(), 4);
}
#[test]
fn r#return() {
let instruction = Instruction::r#return(true);
assert_eq!(instruction.operation(), Operation::Return);
assert!(instruction.b_as_boolean());
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,44 +1,48 @@
//! The Dust programming language library. //! The Dust programming language.
//!
pub mod chunk; //! To get started, you can use the `run` function to run a Dust program.
pub mod compiler; //!
pub mod disassembler; //! ```rust
//! use dust_lang::{run, Value};
//!
//! let program = "
//! let foo = 21
//! let bar = 2
//! foo * bar
//! ";
//!
//! let the_answer = run(program).unwrap();
//!
//! assert_eq!(the_answer, Some(Value::integer(42)));
//! ```
pub mod analyzer;
pub mod ast;
pub mod built_in_function;
pub mod constructor;
pub mod context;
pub mod core_library;
pub mod dust_error; pub mod dust_error;
pub mod formatter; pub mod evaluation;
pub mod instruction; pub mod identifier;
pub mod lexer; pub mod lexer;
pub mod native_function; pub mod parser;
pub mod operation;
pub mod optimizer;
pub mod token; pub mod token;
pub mod r#type; pub mod r#type;
pub mod value; pub mod value;
pub mod vm; pub mod vm;
pub use crate::chunk::{Chunk, ChunkError, Local, Scope}; pub use analyzer::{analyze, AnalysisError, Analyzer};
pub use crate::compiler::{compile, CompileError, Compiler}; pub use ast::{AbstractSyntaxTree, AstError, Expression, Node, Span, Statement};
pub use crate::disassembler::Disassembler; pub use built_in_function::{BuiltInFunction, BuiltInFunctionError};
pub use crate::dust_error::{AnnotatedError, DustError}; pub use constructor::{ConstructError, Constructor};
pub use crate::formatter::{format, Formatter}; pub use context::{Context, ContextData, ContextError};
pub use crate::instruction::Instruction; pub use core_library::core_library;
pub use crate::lexer::{lex, LexError, Lexer}; pub use dust_error::DustError;
pub use crate::native_function::{NativeFunction, NativeFunctionError}; pub use evaluation::{Evaluation, TypeEvaluation};
pub use crate::operation::Operation; pub use identifier::Identifier;
pub use crate::optimizer::{optimize, Optimizer}; pub use lexer::{lex, LexError, Lexer};
pub use crate::r#type::{EnumType, FunctionType, RangeableType, StructType, Type, TypeConflict}; pub use parser::{parse, ParseError, Parser};
pub use crate::token::{Token, TokenKind, TokenOwned}; pub use r#type::*;
pub use crate::value::{ConcreteValue, Function, Value, ValueError}; pub use token::{Token, TokenKind, TokenOwned};
pub use crate::vm::{run, Vm, VmError}; pub use value::*;
pub use vm::{run, RuntimeError, Vm};
use std::fmt::Display;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Span(pub usize, pub usize);
impl Display for Span {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({}, {})", self.0, self.1)
}
}

View File

@ -1,418 +0,0 @@
//! Built-in functions that implement extended functionality.
//!
//! Native functions are used either to implement features that are not possible to implement in
//! Dust itself or that are more efficient to implement in Rust.
use std::{
fmt::{self, Display, Formatter},
io::{self, stdin, stdout, Write},
string::{self},
};
use serde::{Deserialize, Serialize};
use crate::{
AnnotatedError, ConcreteValue, FunctionType, Instruction, Span, Type, Value, Vm, VmError,
};
macro_rules! define_native_function {
($(($name:ident, $byte:literal, $str:expr, $type:expr)),*) => {
/// A dust-native function.
///
/// See the [module-level documentation](index.html) for more information.
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum NativeFunction {
$(
$name = $byte as isize,
)*
}
impl NativeFunction {
pub fn as_str(&self) -> &'static str {
match self {
$(
NativeFunction::$name => $str,
)*
}
}
#[allow(clippy::should_implement_trait)]
pub fn from_str(string: &str) -> Option<Self> {
match string {
$(
$str => Some(NativeFunction::$name),
)*
_ => None,
}
}
pub fn r#type(&self) -> FunctionType {
match self {
$(
NativeFunction::$name => $type,
)*
}
}
pub fn returns_value(&self) -> bool {
match self {
$(
NativeFunction::$name => $type.return_type.is_some(),
)*
}
}
}
impl From<u8> for NativeFunction {
fn from(byte: u8) -> Self {
match byte {
$(
$byte => NativeFunction::$name,
)*
_ => {
if cfg!(test) {
panic!("Invalid native function byte: {}", byte)
} else {
NativeFunction::Panic
}
}
}
}
}
impl From<NativeFunction> for u8 {
fn from(native_function: NativeFunction) -> Self {
match native_function {
$(
NativeFunction::$name => $byte,
)*
}
}
}
};
}
define_native_function! {
// Assertion
(
Assert,
0_u8,
"assert",
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: None
}
),
// (AssertEqual, 1_u8, "assert_equal", false),
// (AssertNotEqual, 2_u8, "assert_not_equal", false),
(
Panic,
3_u8,
"panic",
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Some(Box::new(Type::Any))
}
),
// // Type conversion
// (Parse, 4_u8, "parse", true),
// (ToByte, 5_u8, "to_byte", true),
// (ToFloat, 6_u8, "to_float", true),
// (ToInteger, 7_u8, "to_integer", true),
(
ToString,
8_u8,
"to_string",
FunctionType {
type_parameters: None,
value_parameters: Some(vec![(0, Type::Any)]),
return_type: Some(Box::new(Type::String { length: None }))
}
),
// // List and string
// (All, 9_u8, "all", true),
// (Any, 10_u8, "any", true),
// (Append, 11_u8, "append", false),
// (Contains, 12_u8, "contains", true),
// (Dedup, 13_u8, "dedup", false),
// (EndsWith, 14_u8, "ends_with", true),
// (Find, 15_u8, "find", true),
// (Get, 16_u8, "get", true),
// (IndexOf, 17_u8, "index_of", true),
// (Length, 18_u8, "length", true),
// (Prepend, 19_u8, "prepend", false),
// (Replace, 20_u8, "replace", false),
// (Set, 21_u8, "set", false),
// (StartsWith, 22_u8, "starts_with", true),
// (Slice, 23_u8, "slice", true),
// (Sort, 24_u8, "sort", false),
// (Split, 25_u8, "split", true),
// // List
// (Flatten, 26_u8, "flatten", false),
// (Join, 27_u8, "join", true),
// (Map, 28_u8, "map", true),
// (Reduce, 29_u8, "reduce", true),
// (Remove, 30_u8, "remove", false),
// (Reverse, 31_u8, "reverse", false),
// (Unzip, 32_u8, "unzip", true),
// (Zip, 33_u8, "zip", true),
// // String
// (Bytes, 34_u8, "bytes", true),
// (CharAt, 35_u8, "char_at", true),
// (CharCodeAt, 36_u8, "char_code_at", true),
// (Chars, 37_u8, "chars", true),
// (Format, 38_u8, "format", true),
// (Repeat, 39_u8, "repeat", true),
// (SplitAt, 40_u8, "split_at", true),
// (SplitLines, 41_u8, "split_lines", true),
// (SplitWhitespace, 42_u8, "split_whitespace", true),
// (ToLowerCase, 43_u8, "to_lower_case", true),
// (ToUpperCase, 44_u8, "to_upper_case", true),
// (Trim, 45_u8, "trim", true),
// (TrimEnd, 46_u8, "trim_end", true),
// (TrimStart, 47_u8, "trim_start", true),
// // I/O
// // Read
// (Read, 48_u8, "read", true),
// (ReadFile, 49_u8, "read_file", true),
(
ReadLine,
50_u8,
"read_line",
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Some(Box::new(Type::String { length: None }))
}
),
// (ReadTo, 51_u8, "read_to", false),
// (ReadUntil, 52_u8, "read_until", true),
// // Write
// (AppendFile, 53_u8, "append_file", false),
// (PrependFile, 54_u8, "prepend_file", false),
(
Write,
55_u8,
"write",
FunctionType {
type_parameters: None,
value_parameters: Some(vec![(0, Type::String { length: None })]),
return_type: None
}
),
// (WriteFile, 56_u8, "write_file", false),
(
WriteLine,
57_u8,
"write_line",
FunctionType {
type_parameters: None,
value_parameters: Some(vec![(0, Type::String { length: None })]),
return_type: None
}
)
// // Random
// (Random, 58_u8, "random", true),
// (RandomInRange, 59_u8, "random_in_range", true)
}
impl NativeFunction {
pub fn call(
&self,
instruction: Instruction,
vm: &Vm,
position: Span,
) -> Result<Option<Value>, VmError> {
let to_register = instruction.a();
let argument_count = instruction.c();
let return_value = match self {
NativeFunction::Panic => {
let message = if argument_count == 0 {
None
} else {
let mut message = String::new();
for argument_index in 0..argument_count {
if argument_index != 0 {
message.push(' ');
}
let argument = vm.open_register(argument_index, position)?;
message.push_str(&argument.to_string());
}
Some(message)
};
return Err(VmError::NativeFunction(NativeFunctionError::Panic {
message,
position,
}));
}
// Type conversion
NativeFunction::ToString => {
let mut string = String::new();
for argument_index in 0..argument_count {
let argument = vm.open_register(argument_index, position)?;
string.push_str(&argument.to_string());
}
Some(Value::Concrete(ConcreteValue::String(string)))
}
// I/O
NativeFunction::ReadLine => {
let mut buffer = String::new();
stdin().read_line(&mut buffer).map_err(|io_error| {
VmError::NativeFunction(NativeFunctionError::Io {
error: io_error.kind(),
position,
})
})?;
buffer = buffer.trim_end_matches('\n').to_string();
Some(Value::Concrete(ConcreteValue::String(buffer)))
}
NativeFunction::Write => {
let to_register = instruction.a();
let mut stdout = stdout();
let map_err = |io_error: io::Error| {
VmError::NativeFunction(NativeFunctionError::Io {
error: io_error.kind(),
position,
})
};
let first_argument = to_register.saturating_sub(argument_count);
let last_argument = to_register.saturating_sub(1);
for argument_index in first_argument..=last_argument {
if argument_index != first_argument {
stdout.write(b" ").map_err(map_err)?;
}
let argument_string = vm.open_register(argument_index, position)?.to_string();
stdout
.write_all(argument_string.as_bytes())
.map_err(map_err)?;
}
None
}
NativeFunction::WriteLine => {
let mut stdout = stdout();
let map_err = |io_error: io::Error| {
VmError::NativeFunction(NativeFunctionError::Io {
error: io_error.kind(),
position,
})
};
let first_index = to_register.saturating_sub(argument_count);
let arguments = vm.open_nonempty_registers(first_index..to_register, position)?;
for (index, argument) in arguments.into_iter().enumerate() {
if index != 0 {
stdout.write(b" ").map_err(map_err)?;
}
if let Value::Concrete(ConcreteValue::String(string)) = argument {
let bytes = string.as_bytes();
stdout.write_all(bytes).map_err(map_err)?;
} else {
let bytes = argument.to_string().into_bytes();
stdout.write_all(&bytes).map_err(map_err)?;
}
}
stdout.write(b"\n").map_err(map_err)?;
None
}
_ => todo!(),
};
Ok(return_value)
}
}
impl Display for NativeFunction {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum NativeFunctionError {
ExpectedArgumentCount {
expected: usize,
found: usize,
position: Span,
},
Panic {
message: Option<String>,
position: Span,
},
Parse {
error: string::ParseError,
position: Span,
},
Io {
error: io::ErrorKind,
position: Span,
},
}
impl AnnotatedError for NativeFunctionError {
fn title() -> &'static str {
"Native Function Error"
}
fn description(&self) -> &'static str {
match self {
NativeFunctionError::ExpectedArgumentCount { .. } => {
"Expected a different number of arguments"
}
NativeFunctionError::Panic { .. } => "Explicit panic",
NativeFunctionError::Parse { .. } => "Failed to parse value",
NativeFunctionError::Io { .. } => "I/O error",
}
}
fn details(&self) -> Option<String> {
match self {
NativeFunctionError::ExpectedArgumentCount {
expected, found, ..
} => Some(format!("Expected {} arguments, found {}", expected, found)),
NativeFunctionError::Panic { message, .. } => message.clone(),
NativeFunctionError::Parse { error, .. } => Some(format!("{}", error)),
NativeFunctionError::Io { error, .. } => Some(format!("{}", error)),
}
}
fn position(&self) -> Span {
match self {
NativeFunctionError::ExpectedArgumentCount { position, .. } => *position,
NativeFunctionError::Panic { position, .. } => *position,
NativeFunctionError::Parse { position, .. } => *position,
NativeFunctionError::Io { position, .. } => *position,
}
}
}

View File

@ -1,110 +0,0 @@
//! Part of an [Instruction][crate::Instruction], which can be executed by the Dust virtual machine.
//!
//! !!! Warning !!!
//! The byte values of the operations matter. The seventh and eighth bits must be zero so that the
//! [Instruction][crate::Instruction] type can use them as flags.
use std::fmt::{self, Display, Formatter};
macro_rules! define_operation {
($(($name:ident, $byte:literal, $str:expr, $type:expr)),*) => {
/// Part of an [Instruction][crate::Instruction], which can be executed by the Dust virtual machine.)
///
/// See the [module-level documentation](index.html) for more information.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Operation {
$(
$name = $byte as isize,
)*
}
impl From<u8> for Operation {
fn from(byte: u8) -> Self {
match byte {
$(
$byte => Operation::$name,
)*
_ => {
if cfg!(test) {
panic!("Invalid operation byte: {}", byte)
} else {
Operation::Return
}
}
}
}
}
impl From<Operation> for u8 {
fn from(operation: Operation) -> Self {
match operation {
$(
Operation::$name => $byte,
)*
}
}
}
impl Display for Operation {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
$(
Operation::$name => write!(f, "{}", $str),
)*
}
}
}
}
}
define_operation! {
(Move, 0b0000_0000, "MOVE", None),
(Close, 0b000_0001, "CLOSE", None),
(LoadBoolean, 0b0000_0010, "LOAD_BOOLEAN", None),
(LoadConstant, 0b0000_0011, "LOAD_CONSTANT", None),
(LoadList, 0b0000_0100, "LOAD_LIST", None),
(LoadSelf, 0b0000_0101, "LOAD_SELF", None),
(DefineLocal, 0b0000_0110, "DEFINE_LOCAL", None),
(GetLocal, 0b0000_0111, "GET_LOCAL", None),
(SetLocal, 0b0000_1000, "SET_LOCAL", None),
(Add, 0b0000_1001, "ADD", None),
(Subtract, 0b0000_1010, "SUBTRACT", None),
(Multiply, 0b0000_1011, "MULTIPLY", None),
(Divide, 0b0000_1100, "DIVIDE", None),
(Modulo, 0b0000_1101, "MODULO", None),
(Test, 0b0000_1110, "TEST", None),
(TestSet, 0b0000_1111, "TEST_SET", None),
(Equal, 0b0001_0000, "EQUAL", None),
(Less, 0b0001_0001, "LESS", None),
(LessEqual, 0b0001_0010, "LESS_EQUAL", None),
(Negate, 0b0001_0011, "NEGATE", None),
(Not, 0b0001_0100, "NOT", None),
(Jump, 0b0001_0101, "JUMP", None),
(Call, 0b0001_0110, "CALL", None),
(CallNative, 0b0001_0111, "CALL_NATIVE", None),
(Return, 0b0001_1000, "RETURN", None)
}
impl Operation {
pub fn is_math(&self) -> bool {
matches!(
self,
Operation::Add
| Operation::Subtract
| Operation::Multiply
| Operation::Divide
| Operation::Modulo
)
}
pub fn is_comparison(&self) -> bool {
matches!(
self,
Operation::Equal | Operation::Less | Operation::LessEqual
)
}
pub fn is_test(&self) -> bool {
matches!(self, Operation::Test | Operation::TestSet)
}
}

View File

@ -1,96 +0,0 @@
//! Tools used by the compiler to optimize a chunk's bytecode.
use std::{iter::Map, slice::Iter};
use crate::{Instruction, Operation, Span};
type MapToOperation = fn(&(Instruction, Span)) -> Operation;
type OperationIter<'iter> = Map<Iter<'iter, (Instruction, Span)>, MapToOperation>;
/// Performs optimizations on a subset of instructions.
pub fn optimize(instructions: &mut [(Instruction, Span)]) -> usize {
Optimizer::new(instructions).optimize()
}
/// An instruction optimizer that mutably borrows instructions from a chunk.
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct Optimizer<'chunk> {
instructions: &'chunk mut [(Instruction, Span)],
}
impl<'chunk> Optimizer<'chunk> {
/// Creates a new optimizer with a mutable reference to some of a chunk's instructions.
pub fn new(instructions: &'chunk mut [(Instruction, Span)]) -> Self {
Self { instructions }
}
/// Potentially mutates the instructions to optimize them.
pub fn optimize(&mut self) -> usize {
let mut optimizations = 0;
if matches!(
self.get_operations(),
Some([
Operation::Equal | Operation::Less | Operation::LessEqual,
Operation::Jump,
Operation::LoadBoolean | Operation::LoadConstant,
Operation::LoadBoolean | Operation::LoadConstant,
])
) {
self.optimize_comparison();
optimizations += 1;
}
optimizations
}
/// Optimizes a comparison operation.
///
/// The instructions must be in the following order:
/// - `Operation::Equal | Operation::Less | Operation::LessEqual`
/// - `Operation::Jump`
/// - `Operation::LoadBoolean | Operation::LoadConstant`
/// - `Operation::LoadBoolean | Operation::LoadConstant`
fn optimize_comparison(&mut self) {
log::debug!("Optimizing comparison");
let first_loader_register = {
let first_loader = &mut self.instructions[2].0;
first_loader.set_c_to_boolean(true);
first_loader.a()
};
let second_loader = &mut self.instructions[3].0;
let mut second_loader_new = Instruction::with_operation(second_loader.operation());
second_loader_new.set_a(first_loader_register);
second_loader_new.set_b(second_loader.b());
second_loader_new.set_c(second_loader.c());
second_loader_new.set_b_to_boolean(second_loader.b_is_constant());
second_loader_new.set_c_to_boolean(second_loader.c_is_constant());
*second_loader = second_loader_new;
}
fn operations_iter(&self) -> OperationIter {
self.instructions
.iter()
.map(|(instruction, _)| instruction.operation())
}
fn get_operations<const COUNT: usize>(&self) -> Option<[Operation; COUNT]> {
if self.instructions.len() < COUNT {
return None;
}
let mut n_operations = [Operation::Return; COUNT];
for (nth, operation) in n_operations.iter_mut().zip(self.operations_iter()) {
*nth = operation;
}
Some(n_operations)
}
}

2309
dust-lang/src/parser.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,39 +1,16 @@
//! Token, TokenOwned and TokenKind types. //! Token and TokenOwned types.
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
macro_rules! define_tokens { /// Source code token.
($($variant:ident $(($data_type:ty))?),+ $(,)?) => { #[derive(Debug, Serialize, Deserialize, PartialEq)]
/// Source token.
///
/// This is a borrowed type, i.e. some variants contain references to the source text.
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Default, Serialize, Deserialize)]
pub enum Token<'src> { pub enum Token<'src> {
#[default] // End of file
Eof, Eof,
$(
$variant $(($data_type))?,
)*
}
#[derive(Debug, PartialEq, Clone)]
/// Data-less representation of a source token.
///
/// If a [Token] borrows from the source text, its TokenKind omits the data.
pub enum TokenKind {
Eof,
$(
$variant,
)*
}
};
}
define_tokens! {
// Hard-coded values // Hard-coded values
Boolean(&'src str), Boolean(&'src str),
Byte(&'src str),
Character(char), Character(char),
Float(&'src str), Float(&'src str),
Identifier(&'src str), Identifier(&'src str),
@ -46,20 +23,17 @@ define_tokens! {
Break, Break,
Else, Else,
FloatKeyword, FloatKeyword,
Fn,
If, If,
Int, Int,
Let, Let,
Loop, Loop,
Map, Map,
Mut, Mut,
Return,
Str, Str,
Struct, Struct,
While, While,
// Symbols // Symbols
ArrowThin,
BangEqual, BangEqual,
Bang, Bang,
Colon, Colon,
@ -80,7 +54,6 @@ define_tokens! {
Minus, Minus,
MinusEqual, MinusEqual,
Percent, Percent,
PercentEqual,
Plus, Plus,
PlusEqual, PlusEqual,
RightCurlyBrace, RightCurlyBrace,
@ -88,9 +61,7 @@ define_tokens! {
RightSquareBrace, RightSquareBrace,
Semicolon, Semicolon,
Slash, Slash,
SlashEqual,
Star, Star,
StarEqual,
} }
impl<'src> Token<'src> { impl<'src> Token<'src> {
@ -99,19 +70,16 @@ impl<'src> Token<'src> {
match self { match self {
Token::Eof => 0, Token::Eof => 0,
Token::Boolean(text) => text.len(), Token::Boolean(text) => text.len(),
Token::Byte(_) => 3,
Token::Character(_) => 3, Token::Character(_) => 3,
Token::Float(text) => text.len(), Token::Float(text) => text.len(),
Token::Identifier(text) => text.len(), Token::Identifier(text) => text.len(),
Token::Integer(text) => text.len(), Token::Integer(text) => text.len(),
Token::String(text) => text.len() + 2, Token::String(text) => text.len() + 2,
Token::Async => 5, Token::Async => 5,
Token::ArrowThin => 2,
Token::Bool => 4, Token::Bool => 4,
Token::Break => 5, Token::Break => 5,
Token::Else => 4, Token::Else => 4,
Token::FloatKeyword => 5, Token::FloatKeyword => 5,
Token::Fn => 2,
Token::If => 2, Token::If => 2,
Token::Int => 3, Token::Int => 3,
Token::Let => 3, Token::Let => 3,
@ -141,92 +109,25 @@ impl<'src> Token<'src> {
Token::Minus => 1, Token::Minus => 1,
Token::MinusEqual => 2, Token::MinusEqual => 2,
Token::Percent => 1, Token::Percent => 1,
Token::PercentEqual => 2,
Token::Plus => 1, Token::Plus => 1,
Token::PlusEqual => 2, Token::PlusEqual => 2,
Token::Return => 6,
Token::RightCurlyBrace => 1, Token::RightCurlyBrace => 1,
Token::RightParenthesis => 1, Token::RightParenthesis => 1,
Token::RightSquareBrace => 1, Token::RightSquareBrace => 1,
Token::Semicolon => 1, Token::Semicolon => 1,
Token::Slash => 1, Token::Slash => 1,
Token::SlashEqual => 2,
Token::Star => 1, Token::Star => 1,
Token::StarEqual => 2,
}
}
pub fn as_str(&self) -> &str {
match self {
Token::Eof => "",
Token::Boolean(text) => text,
Token::Byte(text) => text,
Token::Character(_) => "character token",
Token::Float(text) => text,
Token::Identifier(text) => text,
Token::Integer(text) => text,
Token::String(text) => text,
Token::Async => "async",
Token::ArrowThin => "->",
Token::Bool => "bool",
Token::Break => "break",
Token::Else => "else",
Token::FloatKeyword => "float",
Token::Fn => "fn",
Token::If => "if",
Token::Int => "int",
Token::Let => "let",
Token::Loop => "loop",
Token::Map => "map",
Token::Mut => "mut",
Token::Str => "str",
Token::Struct => "struct",
Token::While => "while",
Token::BangEqual => "!=",
Token::Bang => "!",
Token::Colon => ":",
Token::Comma => ",",
Token::Dot => ".",
Token::DoubleAmpersand => "&&",
Token::DoubleDot => "..",
Token::DoubleEqual => "==",
Token::DoublePipe => "||",
Token::Equal => "=",
Token::Greater => ">",
Token::GreaterEqual => ">=",
Token::LeftCurlyBrace => "{",
Token::LeftParenthesis => "(",
Token::LeftSquareBrace => "[",
Token::Less => "<",
Token::LessEqual => "<=",
Token::Minus => "-",
Token::MinusEqual => "-=",
Token::Percent => "%",
Token::PercentEqual => "%=",
Token::Plus => "+",
Token::PlusEqual => "+=",
Token::Return => "return",
Token::RightCurlyBrace => "}",
Token::RightParenthesis => ")",
Token::RightSquareBrace => "]",
Token::Semicolon => ";",
Token::Slash => "/",
Token::SlashEqual => "/=",
Token::Star => "*",
Token::StarEqual => "*=",
} }
} }
pub fn to_owned(&self) -> TokenOwned { pub fn to_owned(&self) -> TokenOwned {
match self { match self {
Token::ArrowThin => TokenOwned::ArrowThin,
Token::Async => TokenOwned::Async, Token::Async => TokenOwned::Async,
Token::BangEqual => TokenOwned::BangEqual, Token::BangEqual => TokenOwned::BangEqual,
Token::Bang => TokenOwned::Bang, Token::Bang => TokenOwned::Bang,
Token::Bool => TokenOwned::Bool, Token::Bool => TokenOwned::Bool,
Token::Boolean(boolean) => TokenOwned::Boolean(boolean.to_string()), Token::Boolean(boolean) => TokenOwned::Boolean(boolean.to_string()),
Token::Break => TokenOwned::Break, Token::Break => TokenOwned::Break,
Token::Byte(byte) => TokenOwned::Byte(byte.to_string()),
Token::Character(character) => TokenOwned::Character(*character), Token::Character(character) => TokenOwned::Character(*character),
Token::Colon => TokenOwned::Colon, Token::Colon => TokenOwned::Colon,
Token::Comma => TokenOwned::Comma, Token::Comma => TokenOwned::Comma,
@ -240,7 +141,6 @@ impl<'src> Token<'src> {
Token::Equal => TokenOwned::Equal, Token::Equal => TokenOwned::Equal,
Token::Float(float) => TokenOwned::Float(float.to_string()), Token::Float(float) => TokenOwned::Float(float.to_string()),
Token::FloatKeyword => TokenOwned::FloatKeyword, Token::FloatKeyword => TokenOwned::FloatKeyword,
Token::Fn => TokenOwned::Fn,
Token::Greater => TokenOwned::Greater, Token::Greater => TokenOwned::Greater,
Token::GreaterEqual => TokenOwned::GreaterOrEqual, Token::GreaterEqual => TokenOwned::GreaterOrEqual,
Token::Identifier(text) => TokenOwned::Identifier(text.to_string()), Token::Identifier(text) => TokenOwned::Identifier(text.to_string()),
@ -259,18 +159,14 @@ impl<'src> Token<'src> {
Token::MinusEqual => TokenOwned::MinusEqual, Token::MinusEqual => TokenOwned::MinusEqual,
Token::Mut => TokenOwned::Mut, Token::Mut => TokenOwned::Mut,
Token::Percent => TokenOwned::Percent, Token::Percent => TokenOwned::Percent,
Token::PercentEqual => TokenOwned::PercentEqual,
Token::Plus => TokenOwned::Plus, Token::Plus => TokenOwned::Plus,
Token::PlusEqual => TokenOwned::PlusEqual, Token::PlusEqual => TokenOwned::PlusEqual,
Token::Return => TokenOwned::Return,
Token::RightCurlyBrace => TokenOwned::RightCurlyBrace, Token::RightCurlyBrace => TokenOwned::RightCurlyBrace,
Token::RightParenthesis => TokenOwned::RightParenthesis, Token::RightParenthesis => TokenOwned::RightParenthesis,
Token::RightSquareBrace => TokenOwned::RightSquareBrace, Token::RightSquareBrace => TokenOwned::RightSquareBrace,
Token::Semicolon => TokenOwned::Semicolon, Token::Semicolon => TokenOwned::Semicolon,
Token::Star => TokenOwned::Star, Token::Star => TokenOwned::Star,
Token::StarEqual => TokenOwned::StarEqual,
Token::Slash => TokenOwned::Slash, Token::Slash => TokenOwned::Slash,
Token::SlashEqual => TokenOwned::SlashEqual,
Token::String(text) => TokenOwned::String(text.to_string()), Token::String(text) => TokenOwned::String(text.to_string()),
Token::Str => TokenOwned::Str, Token::Str => TokenOwned::Str,
Token::Struct => TokenOwned::Struct, Token::Struct => TokenOwned::Struct,
@ -280,14 +176,12 @@ impl<'src> Token<'src> {
pub fn kind(&self) -> TokenKind { pub fn kind(&self) -> TokenKind {
match self { match self {
Token::ArrowThin => TokenKind::ArrowThin,
Token::Async => TokenKind::Async, Token::Async => TokenKind::Async,
Token::BangEqual => TokenKind::BangEqual, Token::BangEqual => TokenKind::BangEqual,
Token::Bang => TokenKind::Bang, Token::Bang => TokenKind::Bang,
Token::Bool => TokenKind::Bool, Token::Bool => TokenKind::Bool,
Token::Boolean(_) => TokenKind::Boolean, Token::Boolean(_) => TokenKind::Boolean,
Token::Break => TokenKind::Break, Token::Break => TokenKind::Break,
Token::Byte(_) => TokenKind::Byte,
Token::Character(_) => TokenKind::Character, Token::Character(_) => TokenKind::Character,
Token::Colon => TokenKind::Colon, Token::Colon => TokenKind::Colon,
Token::Comma => TokenKind::Comma, Token::Comma => TokenKind::Comma,
@ -301,9 +195,8 @@ impl<'src> Token<'src> {
Token::Equal => TokenKind::Equal, Token::Equal => TokenKind::Equal,
Token::Float(_) => TokenKind::Float, Token::Float(_) => TokenKind::Float,
Token::FloatKeyword => TokenKind::FloatKeyword, Token::FloatKeyword => TokenKind::FloatKeyword,
Token::Fn => TokenKind::Fn,
Token::Greater => TokenKind::Greater, Token::Greater => TokenKind::Greater,
Token::GreaterEqual => TokenKind::GreaterEqual, Token::GreaterEqual => TokenKind::GreaterOrEqual,
Token::Identifier(_) => TokenKind::Identifier, Token::Identifier(_) => TokenKind::Identifier,
Token::If => TokenKind::If, Token::If => TokenKind::If,
Token::Int => TokenKind::Int, Token::Int => TokenKind::Int,
@ -313,25 +206,21 @@ impl<'src> Token<'src> {
Token::LeftSquareBrace => TokenKind::LeftSquareBrace, Token::LeftSquareBrace => TokenKind::LeftSquareBrace,
Token::Let => TokenKind::Let, Token::Let => TokenKind::Let,
Token::Less => TokenKind::Less, Token::Less => TokenKind::Less,
Token::LessEqual => TokenKind::LessEqual, Token::LessEqual => TokenKind::LessOrEqual,
Token::Loop => TokenKind::Loop, Token::Loop => TokenKind::Loop,
Token::Map => TokenKind::Map, Token::Map => TokenKind::Map,
Token::Minus => TokenKind::Minus, Token::Minus => TokenKind::Minus,
Token::MinusEqual => TokenKind::MinusEqual, Token::MinusEqual => TokenKind::MinusEqual,
Token::Mut => TokenKind::Mut, Token::Mut => TokenKind::Mut,
Token::Percent => TokenKind::Percent, Token::Percent => TokenKind::Percent,
Token::PercentEqual => TokenKind::PercentEqual,
Token::Plus => TokenKind::Plus, Token::Plus => TokenKind::Plus,
Token::PlusEqual => TokenKind::PlusEqual, Token::PlusEqual => TokenKind::PlusEqual,
Token::Return => TokenKind::Return,
Token::RightCurlyBrace => TokenKind::RightCurlyBrace, Token::RightCurlyBrace => TokenKind::RightCurlyBrace,
Token::RightParenthesis => TokenKind::RightParenthesis, Token::RightParenthesis => TokenKind::RightParenthesis,
Token::RightSquareBrace => TokenKind::RightSquareBrace, Token::RightSquareBrace => TokenKind::RightSquareBrace,
Token::Semicolon => TokenKind::Semicolon, Token::Semicolon => TokenKind::Semicolon,
Token::Star => TokenKind::Star, Token::Star => TokenKind::Star,
Token::StarEqual => TokenKind::StarEqual,
Token::Slash => TokenKind::Slash, Token::Slash => TokenKind::Slash,
Token::SlashEqual => TokenKind::SlashEqual,
Token::Str => TokenKind::Str, Token::Str => TokenKind::Str,
Token::String(_) => TokenKind::String, Token::String(_) => TokenKind::String,
Token::Struct => TokenKind::Struct, Token::Struct => TokenKind::Struct,
@ -339,45 +228,55 @@ impl<'src> Token<'src> {
} }
} }
/// Returns true if the token yields a value, begins an expression or is an expression operator. pub fn is_eof(&self) -> bool {
pub fn is_expression(&self) -> bool { matches!(self, Token::Eof)
matches!( }
self,
Token::Boolean(_) pub fn precedence(&self) -> u8 {
| Token::Byte(_) match self {
| Token::Character(_) Token::Dot => 9,
| Token::Float(_) Token::LeftParenthesis | Token::LeftSquareBrace => 8,
| Token::Identifier(_) Token::Star | Token::Slash | Token::Percent => 7,
| Token::Integer(_) Token::Minus | Token::Plus => 6,
| Token::String(_) Token::DoubleEqual
| Token::Break
| Token::If
| Token::Return
| Token::Map
| Token::Loop
| Token::Struct
| Token::BangEqual
| Token::DoubleAmpersand
| Token::DoubleEqual
| Token::DoublePipe
| Token::Equal
| Token::Greater
| Token::GreaterEqual
| Token::LeftCurlyBrace
| Token::LeftParenthesis
| Token::LeftSquareBrace
| Token::Less | Token::Less
| Token::LessEqual | Token::LessEqual
| Token::Minus | Token::Greater
| Token::MinusEqual | Token::GreaterEqual => 5,
| Token::Percent Token::DoubleAmpersand => 4,
| Token::PercentEqual Token::DoublePipe => 3,
Token::DoubleDot => 2,
Token::Equal | Token::MinusEqual | Token::PlusEqual => 1,
_ => 0,
}
}
pub fn is_left_associative(&self) -> bool {
matches!(
self,
Token::Dot
| Token::DoubleAmpersand
| Token::DoublePipe
| Token::Plus | Token::Plus
| Token::PlusEqual | Token::Minus
| Token::Slash
| Token::SlashEqual
| Token::Star | Token::Star
| Token::StarEqual | Token::Slash
| Token::Percent
)
}
pub fn is_right_associative(&self) -> bool {
matches!(self, Token::Equal | Token::MinusEqual | Token::PlusEqual)
}
pub fn is_prefix(&self) -> bool {
matches!(self, Token::Bang | Token::Minus | Token::Star)
}
pub fn is_postfix(&self) -> bool {
matches!(
self,
Token::Dot | Token::LeftCurlyBrace | Token::LeftParenthesis | Token::LeftSquareBrace
) )
} }
} }
@ -385,15 +284,13 @@ impl<'src> Token<'src> {
impl<'src> Display for Token<'src> { impl<'src> Display for Token<'src> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {
Token::ArrowThin => write!(f, "->"),
Token::Async => write!(f, "async"), Token::Async => write!(f, "async"),
Token::BangEqual => write!(f, "!="), Token::BangEqual => write!(f, "!="),
Token::Bang => write!(f, "!"), Token::Bang => write!(f, "!"),
Token::Bool => write!(f, "bool"), Token::Bool => write!(f, "bool"),
Token::Boolean(value) => write!(f, "{value}"), Token::Boolean(value) => write!(f, "{}", value),
Token::Break => write!(f, "break"), Token::Break => write!(f, "break"),
Token::Byte(value) => write!(f, "{value}"), Token::Character(value) => write!(f, "'{}'", value),
Token::Character(value) => write!(f, "{value}"),
Token::Colon => write!(f, ":"), Token::Colon => write!(f, ":"),
Token::Comma => write!(f, ","), Token::Comma => write!(f, ","),
Token::Dot => write!(f, "."), Token::Dot => write!(f, "."),
@ -404,15 +301,14 @@ impl<'src> Display for Token<'src> {
Token::Else => write!(f, "else"), Token::Else => write!(f, "else"),
Token::Eof => write!(f, "EOF"), Token::Eof => write!(f, "EOF"),
Token::Equal => write!(f, "="), Token::Equal => write!(f, "="),
Token::Float(value) => write!(f, "{value}"), Token::Float(value) => write!(f, "{}", value),
Token::FloatKeyword => write!(f, "float"), Token::FloatKeyword => write!(f, "float"),
Token::Fn => write!(f, "fn"),
Token::Greater => write!(f, ">"), Token::Greater => write!(f, ">"),
Token::GreaterEqual => write!(f, ">="), Token::GreaterEqual => write!(f, ">="),
Token::Identifier(value) => write!(f, "{value}"), Token::Identifier(value) => write!(f, "{}", value),
Token::If => write!(f, "if"), Token::If => write!(f, "if"),
Token::Int => write!(f, "int"), Token::Int => write!(f, "int"),
Token::Integer(value) => write!(f, "{value}"), Token::Integer(value) => write!(f, "{}", value),
Token::LeftCurlyBrace => write!(f, "{{"), Token::LeftCurlyBrace => write!(f, "{{"),
Token::LeftParenthesis => write!(f, "("), Token::LeftParenthesis => write!(f, "("),
Token::LeftSquareBrace => write!(f, "["), Token::LeftSquareBrace => write!(f, "["),
@ -425,30 +321,26 @@ impl<'src> Display for Token<'src> {
Token::MinusEqual => write!(f, "-="), Token::MinusEqual => write!(f, "-="),
Token::Mut => write!(f, "mut"), Token::Mut => write!(f, "mut"),
Token::Percent => write!(f, "%"), Token::Percent => write!(f, "%"),
Token::PercentEqual => write!(f, "%="),
Token::Plus => write!(f, "+"), Token::Plus => write!(f, "+"),
Token::PlusEqual => write!(f, "+="), Token::PlusEqual => write!(f, "+="),
Token::Return => write!(f, "return"),
Token::RightCurlyBrace => write!(f, "}}"), Token::RightCurlyBrace => write!(f, "}}"),
Token::RightParenthesis => write!(f, ")"), Token::RightParenthesis => write!(f, ")"),
Token::RightSquareBrace => write!(f, "]"), Token::RightSquareBrace => write!(f, "]"),
Token::Semicolon => write!(f, ";"), Token::Semicolon => write!(f, ";"),
Token::Slash => write!(f, "/"), Token::Slash => write!(f, "/"),
Token::SlashEqual => write!(f, "/="),
Token::Star => write!(f, "*"), Token::Star => write!(f, "*"),
Token::StarEqual => write!(f, "*="),
Token::Str => write!(f, "str"), Token::Str => write!(f, "str"),
Token::String(value) => write!(f, "{value}"), Token::String(value) => write!(f, "\"{}\"", value),
Token::Struct => write!(f, "struct"), Token::Struct => write!(f, "struct"),
Token::While => write!(f, "while"), Token::While => write!(f, "while"),
} }
} }
} }
/// Owned representation of a source token. /// Owned version of `Token`, which owns all the strings.
/// ///
/// If a [Token] borrows from the source text, its TokenOwned omits the data. /// This is used for errors.
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum TokenOwned { pub enum TokenOwned {
Eof, Eof,
@ -456,31 +348,27 @@ pub enum TokenOwned {
// Hard-coded values // Hard-coded values
Boolean(String), Boolean(String),
Byte(String),
Character(char), Character(char),
Float(String), Float(String),
Integer(String), Integer(String),
String(String), String(String),
// Keywords // Keywords
Async,
Bool, Bool,
Break, Break,
Else, Else,
FloatKeyword, FloatKeyword,
Fn,
If, If,
Int, Int,
Let, Let,
Loop, Loop,
Map, Map,
Mut, Mut,
Return,
Str, Str,
While, While,
// Symbols // Symbols
ArrowThin, Async,
Bang, Bang,
BangEqual, BangEqual,
Colon, Colon,
@ -501,7 +389,6 @@ pub enum TokenOwned {
Minus, Minus,
MinusEqual, MinusEqual,
Percent, Percent,
PercentEqual,
Plus, Plus,
PlusEqual, PlusEqual,
RightCurlyBrace, RightCurlyBrace,
@ -509,23 +396,19 @@ pub enum TokenOwned {
RightSquareBrace, RightSquareBrace,
Semicolon, Semicolon,
Star, Star,
StarEqual,
Struct, Struct,
Slash, Slash,
SlashEqual,
} }
impl Display for TokenOwned { impl Display for TokenOwned {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self { match self {
TokenOwned::ArrowThin => Token::ArrowThin.fmt(f),
TokenOwned::Async => Token::Async.fmt(f), TokenOwned::Async => Token::Async.fmt(f),
TokenOwned::Bang => Token::Bang.fmt(f), TokenOwned::Bang => Token::Bang.fmt(f),
TokenOwned::BangEqual => Token::BangEqual.fmt(f), TokenOwned::BangEqual => Token::BangEqual.fmt(f),
TokenOwned::Bool => Token::Bool.fmt(f), TokenOwned::Bool => Token::Bool.fmt(f),
TokenOwned::Boolean(boolean) => Token::Boolean(boolean).fmt(f), TokenOwned::Boolean(boolean) => Token::Boolean(boolean).fmt(f),
TokenOwned::Break => Token::Break.fmt(f), TokenOwned::Break => Token::Break.fmt(f),
TokenOwned::Byte(byte) => Token::Byte(byte).fmt(f),
TokenOwned::Character(character) => Token::Character(*character).fmt(f), TokenOwned::Character(character) => Token::Character(*character).fmt(f),
TokenOwned::Colon => Token::Colon.fmt(f), TokenOwned::Colon => Token::Colon.fmt(f),
TokenOwned::Comma => Token::Comma.fmt(f), TokenOwned::Comma => Token::Comma.fmt(f),
@ -539,7 +422,6 @@ impl Display for TokenOwned {
TokenOwned::Equal => Token::Equal.fmt(f), TokenOwned::Equal => Token::Equal.fmt(f),
TokenOwned::Float(float) => Token::Float(float).fmt(f), TokenOwned::Float(float) => Token::Float(float).fmt(f),
TokenOwned::FloatKeyword => Token::FloatKeyword.fmt(f), TokenOwned::FloatKeyword => Token::FloatKeyword.fmt(f),
TokenOwned::Fn => Token::Fn.fmt(f),
TokenOwned::Greater => Token::Greater.fmt(f), TokenOwned::Greater => Token::Greater.fmt(f),
TokenOwned::GreaterOrEqual => Token::GreaterEqual.fmt(f), TokenOwned::GreaterOrEqual => Token::GreaterEqual.fmt(f),
TokenOwned::Identifier(text) => Token::Identifier(text).fmt(f), TokenOwned::Identifier(text) => Token::Identifier(text).fmt(f),
@ -558,18 +440,14 @@ impl Display for TokenOwned {
TokenOwned::MinusEqual => Token::MinusEqual.fmt(f), TokenOwned::MinusEqual => Token::MinusEqual.fmt(f),
TokenOwned::Mut => Token::Mut.fmt(f), TokenOwned::Mut => Token::Mut.fmt(f),
TokenOwned::Percent => Token::Percent.fmt(f), TokenOwned::Percent => Token::Percent.fmt(f),
TokenOwned::PercentEqual => Token::PercentEqual.fmt(f),
TokenOwned::Plus => Token::Plus.fmt(f), TokenOwned::Plus => Token::Plus.fmt(f),
TokenOwned::PlusEqual => Token::PlusEqual.fmt(f), TokenOwned::PlusEqual => Token::PlusEqual.fmt(f),
TokenOwned::Return => Token::Return.fmt(f),
TokenOwned::RightCurlyBrace => Token::RightCurlyBrace.fmt(f), TokenOwned::RightCurlyBrace => Token::RightCurlyBrace.fmt(f),
TokenOwned::RightParenthesis => Token::RightParenthesis.fmt(f), TokenOwned::RightParenthesis => Token::RightParenthesis.fmt(f),
TokenOwned::RightSquareBrace => Token::RightSquareBrace.fmt(f), TokenOwned::RightSquareBrace => Token::RightSquareBrace.fmt(f),
TokenOwned::Semicolon => Token::Semicolon.fmt(f), TokenOwned::Semicolon => Token::Semicolon.fmt(f),
TokenOwned::Star => Token::Star.fmt(f), TokenOwned::Star => Token::Star.fmt(f),
TokenOwned::StarEqual => Token::StarEqual.fmt(f),
TokenOwned::Slash => Token::Slash.fmt(f), TokenOwned::Slash => Token::Slash.fmt(f),
TokenOwned::SlashEqual => Token::SlashEqual.fmt(f),
TokenOwned::Str => Token::Str.fmt(f), TokenOwned::Str => Token::Str.fmt(f),
TokenOwned::String(string) => Token::String(string).fmt(f), TokenOwned::String(string) => Token::String(string).fmt(f),
TokenOwned::Struct => Token::Struct.fmt(f), TokenOwned::Struct => Token::Struct.fmt(f),
@ -578,17 +456,76 @@ impl Display for TokenOwned {
} }
} }
/// Token representation that holds no data.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum TokenKind {
Eof,
Identifier,
// Hard-coded values
Boolean,
Character,
Float,
Integer,
String,
// Keywords
Async,
Bool,
Break,
Else,
FloatKeyword,
If,
Int,
Let,
Loop,
Map,
Str,
While,
// Symbols
BangEqual,
Bang,
Colon,
Comma,
Dot,
DoubleAmpersand,
DoubleDot,
DoubleEqual,
DoublePipe,
Equal,
Greater,
GreaterOrEqual,
LeftCurlyBrace,
LeftParenthesis,
LeftSquareBrace,
Less,
LessOrEqual,
Minus,
MinusEqual,
Mut,
Percent,
Plus,
PlusEqual,
RightCurlyBrace,
RightParenthesis,
RightSquareBrace,
Semicolon,
Star,
Struct,
Slash,
}
impl Display for TokenKind { impl Display for TokenKind {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self { match self {
TokenKind::ArrowThin => Token::ArrowThin.fmt(f),
TokenKind::Async => Token::Async.fmt(f), TokenKind::Async => Token::Async.fmt(f),
TokenKind::Bang => Token::Bang.fmt(f), TokenKind::Bang => Token::Bang.fmt(f),
TokenKind::BangEqual => Token::BangEqual.fmt(f), TokenKind::BangEqual => Token::BangEqual.fmt(f),
TokenKind::Bool => Token::Bool.fmt(f), TokenKind::Bool => Token::Bool.fmt(f),
TokenKind::Boolean => write!(f, "boolean value"), TokenKind::Boolean => write!(f, "boolean value"),
TokenKind::Break => Token::Break.fmt(f), TokenKind::Break => Token::Break.fmt(f),
TokenKind::Byte => write!(f, "byte value"),
TokenKind::Character => write!(f, "character value"), TokenKind::Character => write!(f, "character value"),
TokenKind::Colon => Token::Colon.fmt(f), TokenKind::Colon => Token::Colon.fmt(f),
TokenKind::Comma => Token::Comma.fmt(f), TokenKind::Comma => Token::Comma.fmt(f),
@ -602,9 +539,8 @@ impl Display for TokenKind {
TokenKind::Equal => Token::Equal.fmt(f), TokenKind::Equal => Token::Equal.fmt(f),
TokenKind::Float => write!(f, "float value"), TokenKind::Float => write!(f, "float value"),
TokenKind::FloatKeyword => Token::FloatKeyword.fmt(f), TokenKind::FloatKeyword => Token::FloatKeyword.fmt(f),
TokenKind::Fn => Token::Fn.fmt(f),
TokenKind::Greater => Token::Greater.fmt(f), TokenKind::Greater => Token::Greater.fmt(f),
TokenKind::GreaterEqual => Token::GreaterEqual.fmt(f), TokenKind::GreaterOrEqual => Token::GreaterEqual.fmt(f),
TokenKind::Identifier => write!(f, "identifier"), TokenKind::Identifier => write!(f, "identifier"),
TokenKind::If => Token::If.fmt(f), TokenKind::If => Token::If.fmt(f),
TokenKind::Int => Token::Int.fmt(f), TokenKind::Int => Token::Int.fmt(f),
@ -614,29 +550,102 @@ impl Display for TokenKind {
TokenKind::LeftSquareBrace => Token::LeftSquareBrace.fmt(f), TokenKind::LeftSquareBrace => Token::LeftSquareBrace.fmt(f),
TokenKind::Let => Token::Let.fmt(f), TokenKind::Let => Token::Let.fmt(f),
TokenKind::Less => Token::Less.fmt(f), TokenKind::Less => Token::Less.fmt(f),
TokenKind::LessEqual => Token::LessEqual.fmt(f), TokenKind::LessOrEqual => Token::LessEqual.fmt(f),
TokenKind::Loop => Token::Loop.fmt(f), TokenKind::Loop => Token::Loop.fmt(f),
TokenKind::Map => Token::Map.fmt(f), TokenKind::Map => Token::Map.fmt(f),
TokenKind::Minus => Token::Minus.fmt(f), TokenKind::Minus => Token::Minus.fmt(f),
TokenKind::MinusEqual => Token::MinusEqual.fmt(f), TokenKind::MinusEqual => Token::MinusEqual.fmt(f),
TokenKind::Mut => Token::Mut.fmt(f), TokenKind::Mut => Token::Mut.fmt(f),
TokenKind::Percent => Token::Percent.fmt(f), TokenKind::Percent => Token::Percent.fmt(f),
TokenKind::PercentEqual => Token::PercentEqual.fmt(f),
TokenKind::Plus => Token::Plus.fmt(f), TokenKind::Plus => Token::Plus.fmt(f),
TokenKind::PlusEqual => Token::PlusEqual.fmt(f), TokenKind::PlusEqual => Token::PlusEqual.fmt(f),
TokenKind::Return => Token::Return.fmt(f),
TokenKind::RightCurlyBrace => Token::RightCurlyBrace.fmt(f), TokenKind::RightCurlyBrace => Token::RightCurlyBrace.fmt(f),
TokenKind::RightParenthesis => Token::RightParenthesis.fmt(f), TokenKind::RightParenthesis => Token::RightParenthesis.fmt(f),
TokenKind::RightSquareBrace => Token::RightSquareBrace.fmt(f), TokenKind::RightSquareBrace => Token::RightSquareBrace.fmt(f),
TokenKind::Semicolon => Token::Semicolon.fmt(f), TokenKind::Semicolon => Token::Semicolon.fmt(f),
TokenKind::Star => Token::Star.fmt(f), TokenKind::Star => Token::Star.fmt(f),
TokenKind::StarEqual => Token::StarEqual.fmt(f),
TokenKind::Str => Token::Str.fmt(f), TokenKind::Str => Token::Str.fmt(f),
TokenKind::Slash => Token::Slash.fmt(f), TokenKind::Slash => Token::Slash.fmt(f),
TokenKind::SlashEqual => Token::SlashEqual.fmt(f),
TokenKind::String => write!(f, "string value"), TokenKind::String => write!(f, "string value"),
TokenKind::Struct => Token::Struct.fmt(f), TokenKind::Struct => Token::Struct.fmt(f),
TokenKind::While => Token::While.fmt(f), TokenKind::While => Token::While.fmt(f),
} }
} }
} }
#[cfg(test)]
pub(crate) mod tests {
use super::*;
pub fn all_tokens<'src>() -> [Token<'src>; 47] {
[
Token::Async,
Token::Bang,
Token::BangEqual,
Token::Bool,
Token::Break,
Token::Colon,
Token::Comma,
Token::Dot,
Token::DoubleAmpersand,
Token::DoubleDot,
Token::DoubleEqual,
Token::DoublePipe,
Token::Else,
Token::Eof,
Token::Equal,
Token::FloatKeyword,
Token::Greater,
Token::GreaterEqual,
Token::If,
Token::Int,
Token::LeftCurlyBrace,
Token::LeftParenthesis,
Token::LeftSquareBrace,
Token::Let,
Token::Less,
Token::LessEqual,
Token::Map,
Token::Minus,
Token::MinusEqual,
Token::Mut,
Token::Percent,
Token::Plus,
Token::PlusEqual,
Token::RightCurlyBrace,
Token::RightParenthesis,
Token::RightSquareBrace,
Token::Semicolon,
Token::Star,
Token::Str,
Token::Slash,
Token::Boolean("true"),
Token::Float("0.0"),
Token::Integer("0"),
Token::String("string"),
Token::Identifier("foobar"),
Token::Struct,
Token::While,
]
}
#[test]
fn token_displays() {
for token in all_tokens().iter() {
let display = token.to_string();
assert_eq!(display, token.to_owned().to_string());
if let Token::Boolean(_)
| Token::Float(_)
| Token::Identifier(_)
| Token::Integer(_)
| Token::String(_) = token
{
continue;
} else {
assert_eq!(display, token.kind().to_string());
}
}
}
}

View File

@ -1,4 +1,14 @@
//! Value types and conflict handling. //! Description of a kind of value.
//!
//! Most types are concrete and specific, the exceptions are the Generic and Any types.
//!
//! Generic types are temporary placeholders that describe a type that will be defined later. The
//! interpreter should use the analysis phase to enforce that all Generic types have a concrete
//! type assigned to them before the program is run.
//!
//! The Any type is used in cases where a value's type does not matter. For example, the standard
//! library's "length" function does not care about the type of item in the list, only the list
//! itself. So the input is defined as `[any]`, i.e. `Type::ListOf(Box::new(Type::Any))`.
use std::{ use std::{
cmp::Ordering, cmp::Ordering,
collections::HashMap, collections::HashMap,
@ -7,7 +17,11 @@ use std::{
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{Constructor, BuiltInFunction, Identifier};
/// Description of a kind of value. /// Description of a kind of value.
///
/// See the [module documentation](index.html) for more information.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum Type { pub enum Type {
Any, Any,
@ -18,7 +32,7 @@ pub enum Type {
Float, Float,
Function(FunctionType), Function(FunctionType),
Generic { Generic {
identifier_index: u8, identifier: Identifier,
concrete_type: Option<Box<Type>>, concrete_type: Option<Box<Type>>,
}, },
Integer, Integer,
@ -31,7 +45,7 @@ pub enum Type {
item_type: Box<Type>, item_type: Box<Type>,
}, },
Map { Map {
pairs: HashMap<u8, Type>, pairs: HashMap<Identifier, Type>,
}, },
Number, Number,
Range { Range {
@ -177,17 +191,20 @@ impl Type {
} }
( (
Type::Function(FunctionType { Type::Function(FunctionType {
name: left_name,
type_parameters: left_type_parameters, type_parameters: left_type_parameters,
value_parameters: left_value_parameters, value_parameters: left_value_parameters,
return_type: left_return, return_type: left_return,
}), }),
Type::Function(FunctionType { Type::Function(FunctionType {
name: right_name,
type_parameters: right_type_parameters, type_parameters: right_type_parameters,
value_parameters: right_value_parameters, value_parameters: right_value_parameters,
return_type: right_return, return_type: right_return,
}), }),
) => { ) => {
if left_return != right_return if left_name != right_name
|| left_return != right_return
|| left_type_parameters != right_type_parameters || left_type_parameters != right_type_parameters
|| left_value_parameters != right_value_parameters || left_value_parameters != right_value_parameters
{ {
@ -216,6 +233,48 @@ impl Type {
expected: self.clone(), expected: self.clone(),
}) })
} }
pub fn has_field(&self, field: &Identifier) -> bool {
match field.as_str() {
"to_string" => true,
"length" => {
matches!(
self,
Type::List { .. }
| Type::ListOf { .. }
| Type::ListEmpty
| Type::Map { .. }
| Type::String { .. }
)
}
"is_even" | "is_odd" => matches!(self, Type::Integer | Type::Float),
_ => match self {
Type::Struct(StructType::Fields { fields, .. }) => fields.contains_key(field),
Type::Map { pairs } => pairs.contains_key(field),
_ => false,
},
}
}
pub fn get_field_type(&self, field: &Identifier) -> Option<Type> {
match field.as_str() {
"to_string" => Some(BuiltInFunction::ToString.r#type()),
"length" => match self {
Type::List { .. } => Some(Type::Integer),
Type::ListOf { .. } => Some(Type::Integer),
Type::ListEmpty => Some(Type::Integer),
Type::Map { .. } => Some(Type::Integer),
Type::String { .. } => Some(Type::Integer),
_ => None,
},
"is_even" | "is_odd" => Some(Type::Boolean),
_ => match self {
Type::Struct(StructType::Fields { fields, .. }) => fields.get(field).cloned(),
Type::Map { pairs } => pairs.get(field).cloned(),
_ => None,
},
}
}
} }
impl Display for Type { impl Display for Type {
@ -230,10 +289,7 @@ impl Display for Type {
Type::Function(function_type) => write!(f, "{function_type}"), Type::Function(function_type) => write!(f, "{function_type}"),
Type::Generic { concrete_type, .. } => { Type::Generic { concrete_type, .. } => {
match concrete_type.clone().map(|r#box| *r#box) { match concrete_type.clone().map(|r#box| *r#box) {
Some(Type::Generic { Some(Type::Generic { identifier, .. }) => write!(f, "{identifier}"),
identifier_index: identifier,
..
}) => write!(f, "{identifier}"),
Some(concrete_type) => write!(f, "implied to be {concrete_type}"), Some(concrete_type) => write!(f, "implied to be {concrete_type}"),
None => write!(f, "unknown"), None => write!(f, "unknown"),
} }
@ -364,8 +420,9 @@ impl Ord for Type {
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct FunctionType { pub struct FunctionType {
pub type_parameters: Option<Vec<u8>>, pub name: Identifier,
pub value_parameters: Option<Vec<(u8, Type)>>, pub type_parameters: Option<Vec<Identifier>>,
pub value_parameters: Option<Vec<(Identifier, Type)>>,
pub return_type: Option<Box<Type>>, pub return_type: Option<Box<Type>>,
} }
@ -377,11 +434,11 @@ impl Display for FunctionType {
write!(f, "<")?; write!(f, "<")?;
for (index, type_parameter) in type_parameters.iter().enumerate() { for (index, type_parameter) in type_parameters.iter().enumerate() {
if index > 0 { write!(f, "{type_parameter}")?;
if index != type_parameters.len() - 1 {
write!(f, ", ")?; write!(f, ", ")?;
} }
write!(f, "{type_parameter}")?;
} }
write!(f, ">")?; write!(f, ">")?;
@ -391,11 +448,11 @@ impl Display for FunctionType {
if let Some(value_parameters) = &self.value_parameters { if let Some(value_parameters) = &self.value_parameters {
for (index, (identifier, r#type)) in value_parameters.iter().enumerate() { for (index, (identifier, r#type)) in value_parameters.iter().enumerate() {
if index > 0 { write!(f, "{identifier}: {type}")?;
if index != value_parameters.len() - 1 {
write!(f, ", ")?; write!(f, ", ")?;
} }
write!(f, "{identifier}: {type}")?;
} }
} }
@ -411,17 +468,31 @@ impl Display for FunctionType {
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum StructType { pub enum StructType {
Unit { name: u8 }, Unit {
Tuple { name: u8, fields: Vec<Type> }, name: Identifier,
Fields { name: u8, fields: HashMap<u8, Type> }, },
Tuple {
name: Identifier,
fields: Vec<Type>,
},
Fields {
name: Identifier,
fields: HashMap<Identifier, Type>,
},
} }
impl StructType { impl StructType {
pub fn name(&self) -> u8 { pub fn name(&self) -> &Identifier {
match self { match self {
StructType::Unit { name } => *name, StructType::Unit { name } => name,
StructType::Tuple { name, .. } => *name, StructType::Tuple { name, .. } => name,
StructType::Fields { name, .. } => *name, StructType::Fields { name, .. } => name,
}
}
pub fn constructor(&self) -> Constructor {
Constructor {
struct_type: self.clone(),
} }
} }
} }
@ -523,7 +594,7 @@ impl Ord for StructType {
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct EnumType { pub struct EnumType {
pub name: u8, pub name: Identifier,
pub variants: Vec<StructType>, pub variants: Vec<StructType>,
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,66 +0,0 @@
use dust_lang::*;
#[test]
fn constant() {
let source = "42";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(0, 2)),
(Instruction::r#return(true), Span(2, 2))
],
vec![Value::integer(42)],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(42))));
}
#[test]
fn empty() {
let source = "";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![(Instruction::r#return(false), Span(0, 0))],
vec![],
vec![]
))
);
assert_eq!(run(source), Ok(None));
}
#[test]
fn parentheses_precedence() {
let source = "(1 + 2) * 3";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::add(0, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(3, 4)
),
(
*Instruction::multiply(1, 0, 2).set_c_is_constant(),
Span(8, 9)
),
(Instruction::r#return(true), Span(11, 11)),
],
vec![Value::integer(1), Value::integer(2), Value::integer(3)],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(9))));
}

View File

@ -1,169 +0,0 @@
use dust_lang::*;
#[test]
fn equal() {
let source = "1 == 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 4)
),
(Instruction::jump(1, true), Span(2, 4)),
(Instruction::load_boolean(0, true, true), Span(2, 4)),
(Instruction::load_boolean(0, false, false), Span(2, 4)),
(Instruction::r#return(true), Span(6, 6)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
}
#[test]
fn greater() {
let source = "1 > 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::less_equal(false, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 3)
),
(Instruction::jump(1, true), Span(2, 3)),
(Instruction::load_boolean(0, true, true), Span(2, 3)),
(Instruction::load_boolean(0, false, false), Span(2, 3)),
(Instruction::r#return(true), Span(5, 5)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
}
#[test]
fn greater_than_or_equal() {
let source = "1 >= 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::less(false, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 4)
),
(Instruction::jump(1, true), Span(2, 4)),
(Instruction::load_boolean(0, true, true), Span(2, 4)),
(Instruction::load_boolean(0, false, false), Span(2, 4)),
(Instruction::r#return(true), Span(6, 6)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
}
#[test]
fn less_than() {
let source = "1 < 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::less(true, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 3)
),
(Instruction::jump(1, true), Span(2, 3)),
(Instruction::load_boolean(0, true, true), Span(2, 3)),
(Instruction::load_boolean(0, false, false), Span(2, 3)),
(Instruction::r#return(true), Span(5, 5)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
}
#[test]
fn less_than_or_equal() {
let source = "1 <= 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::less_equal(true, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 4)
),
(Instruction::jump(1, true), Span(2, 4)),
(Instruction::load_boolean(0, true, true), Span(2, 4)),
(Instruction::load_boolean(0, false, false), Span(2, 4)),
(Instruction::r#return(true), Span(6, 6)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
}
#[test]
fn not_equal() {
let source = "1 != 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(false, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 4)
),
(Instruction::jump(1, true), Span(2, 4)),
(Instruction::load_boolean(0, true, true), Span(2, 4)),
(Instruction::load_boolean(0, false, false), Span(2, 4)),
(Instruction::r#return(true), Span(6, 6)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
}

View File

@ -1,420 +0,0 @@
use dust_lang::*;
#[test]
fn equality_assignment_long() {
let source = "let a = if 4 == 4 { true } else { false }; a";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 0)
.set_b_is_constant()
.set_c_is_constant(),
Span(13, 15)
),
(Instruction::jump(1, true), Span(18, 19)),
(Instruction::load_boolean(0, true, true), Span(20, 24)),
(Instruction::load_boolean(0, false, false), Span(34, 39)),
(Instruction::define_local(0, 0, false), Span(4, 5)),
(Instruction::get_local(1, 0), Span(43, 44)),
(Instruction::r#return(true), Span(44, 44)),
],
vec![Value::integer(4), Value::string("a")],
vec![Local::new(
1,
None,
false,
Scope {
depth: 0,
block_index: 0
},
0
)]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
}
#[test]
fn equality_assignment_short() {
let source = "let a = 4 == 4 a";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 0)
.set_b_is_constant()
.set_c_is_constant(),
Span(10, 12)
),
(Instruction::jump(1, true), Span(10, 12)),
(Instruction::load_boolean(0, true, true), Span(10, 12)),
(Instruction::load_boolean(0, false, false), Span(10, 12)),
(Instruction::define_local(0, 0, false), Span(4, 5)),
(Instruction::get_local(1, 0), Span(15, 16)),
(Instruction::r#return(true), Span(16, 16)),
],
vec![Value::integer(4), Value::string("a")],
vec![Local::new(1, None, false, Scope::default(), 0)]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
}
#[test]
fn if_else_assigment_false() {
let source = r#"
let a = if 4 == 3 {
1; 2; 3; 4;
panic()
} else {
1; 2; 3; 4;
42
};
a"#;
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(22, 24)
),
(Instruction::jump(6, true), Span(27, 28)),
(Instruction::load_constant(0, 2, false), Span(41, 42)),
(Instruction::load_constant(1, 3, false), Span(44, 45)),
(Instruction::load_constant(2, 1, false), Span(47, 48)),
(Instruction::load_constant(3, 0, false), Span(50, 51)),
(
Instruction::call_native(4, NativeFunction::Panic, 0),
Span(65, 72)
),
(Instruction::jump(5, true), Span(138, 139)),
(Instruction::load_constant(5, 2, false), Span(102, 103)),
(Instruction::load_constant(6, 3, false), Span(105, 106)),
(Instruction::load_constant(7, 1, false), Span(108, 109)),
(Instruction::load_constant(8, 0, false), Span(111, 112)),
(Instruction::load_constant(9, 4, false), Span(126, 128)),
(Instruction::r#move(9, 4), Span(138, 139)),
(Instruction::define_local(9, 0, false), Span(13, 14)),
(Instruction::get_local(10, 0), Span(148, 149)),
(Instruction::r#return(true), Span(149, 149)),
],
vec![
Value::integer(4),
Value::integer(3),
Value::integer(1),
Value::integer(2),
Value::integer(42),
Value::string("a")
],
vec![Local::new(5, None, false, Scope::default(), 0)]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(42))));
}
#[test]
fn if_else_assigment_true() {
let source = r#"
let a = if 4 == 4 {
1; 2; 3; 4;
42
} else {
1; 2; 3; 4;
panic()
};
a"#;
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 0)
.set_b_is_constant()
.set_c_is_constant(),
Span(22, 24)
),
(Instruction::jump(6, true), Span(27, 28)),
(Instruction::load_constant(0, 1, false), Span(41, 42)),
(Instruction::load_constant(1, 2, false), Span(44, 45)),
(Instruction::load_constant(2, 3, false), Span(47, 48)),
(Instruction::load_constant(3, 0, false), Span(50, 51)),
(Instruction::load_constant(4, 4, false), Span(65, 67)),
(Instruction::jump(5, true), Span(138, 139)),
(Instruction::load_constant(5, 1, false), Span(97, 98)),
(Instruction::load_constant(6, 2, false), Span(100, 101)),
(Instruction::load_constant(7, 3, false), Span(103, 104)),
(Instruction::load_constant(8, 0, false), Span(106, 107)),
(
Instruction::call_native(9, NativeFunction::Panic, 0),
Span(121, 128)
),
(Instruction::r#move(9, 4), Span(138, 139)),
(Instruction::define_local(9, 0, false), Span(13, 14)),
(Instruction::get_local(10, 0), Span(148, 149)),
(Instruction::r#return(true), Span(149, 149)),
],
vec![
Value::integer(4),
Value::integer(1),
Value::integer(2),
Value::integer(3),
Value::integer(42),
Value::string("a")
],
vec![Local::new(5, None, false, Scope::default(), 0)]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(42))));
}
#[test]
fn if_else_complex() {
let source = "
if 1 == 1 {
1; 2; 3; 4;
} else {
1; 2; 3; 4;
}";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 0)
.set_b_is_constant()
.set_c_is_constant(),
Span(14, 16)
),
(Instruction::jump(5, true), Span(19, 20)),
(Instruction::load_constant(0, 0, false), Span(33, 34)),
(Instruction::load_constant(1, 1, false), Span(36, 37)),
(Instruction::load_constant(2, 2, false), Span(39, 40)),
(Instruction::load_constant(3, 3, false), Span(42, 43)),
(Instruction::jump(4, true), Span(95, 95)),
(Instruction::load_constant(4, 0, false), Span(74, 75)),
(Instruction::load_constant(5, 1, false), Span(77, 78)),
(Instruction::load_constant(6, 2, false), Span(80, 81)),
(Instruction::load_constant(7, 3, false), Span(83, 84)),
(Instruction::r#move(7, 3), Span(95, 95)),
(Instruction::r#return(false), Span(95, 95)),
],
vec![
Value::integer(1),
Value::integer(2),
Value::integer(3),
Value::integer(4),
],
vec![]
))
);
assert_eq!(run(source), Ok(None));
}
// #[test]
// fn if_else_nested() {
// let source = r#"
// if 0 == 1 {
// if 0 == 2 {
// 1;
// } else {
// 2;
// }
// } else {
// if 0 == 3 {
// 3;
// } else {
// 4;
// }
// }"#;
// assert_eq!(
// parse(source),
// Ok(Chunk::with_data(
// None,
// vec![
// (
// *Instruction::equal(true, 0, 1)
// .set_b_is_constant()
// .set_c_is_constant(),
// Span(14, 16)
// ),
// (Instruction::jump(7, true), Span(14, 16)),
// (
// *Instruction::equal(true, 0, 2)
// .set_b_is_constant()
// .set_c_is_constant(),
// Span(38, 41)
// ),
// (Instruction::jump(3, true), Span(38, 41)),
// (Instruction::load_constant(0, 1, false), Span(61, 62)),
// (Instruction::jump(1, true1), Span(95, 95)),
// (
// *Instruction::equal(true, 0, 3)
// .set_b_is_constant()
// .set_c_is_constant(),
// Span(77, 79)
// ),
// (Instruction::jump(3, true), Span(77, 79)),
// (Instruction::load_constant(0, 2, false), Span(94, 95)),
// (Instruction::jump(1, true1), Span(95, 95)),
// (Instruction::load_constant(0, 3, false), Span(114, 115)),
// (Instruction::jump(1, true1), Span(95, 95)),
// (Instruction::load_constant(0, 4, false), Span(134, 135)),
// (Instruction::r#return(true), Span(146, 146)),
// ],
// vec![
// Value::integer(0),
// Value::integer(1),
// Value::integer(0),
// Value::integer(2),
// Value::integer(1),
// Value::integer(0),
// Value::integer(3),
// Value::integer(3),
// Value::integer(4)
// ],
// vec![]
// ))
// );
// assert_eq!(run(source), Ok(Some(Value::integer(4))));
// }
#[test]
fn if_else_false() {
let source = "if 1 == 2 { panic() } else { 42 }";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(5, 7)
),
(Instruction::jump(1, true), Span(10, 11)),
(
Instruction::call_native(0, NativeFunction::Panic, 0),
Span(12, 19)
),
(Instruction::load_constant(1, 2, true), Span(29, 31)),
(Instruction::r#move(1, 0), Span(33, 33)),
(Instruction::r#return(true), Span(33, 33)),
],
vec![Value::integer(1), Value::integer(2), Value::integer(42)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(42))));
}
#[test]
fn if_else_true() {
let source = "if 1 == 1 { 42 } else { panic() }";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 0)
.set_b_is_constant()
.set_c_is_constant(),
Span(5, 7)
),
(Instruction::jump(1, true), Span(10, 11)),
(Instruction::load_constant(0, 1, true), Span(12, 14)),
(
Instruction::call_native(1, NativeFunction::Panic, 0),
Span(24, 31)
),
(Instruction::r#move(1, 0), Span(33, 33)),
(Instruction::r#return(true), Span(33, 33))
],
vec![Value::integer(1), Value::integer(42)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(42))));
}
#[test]
fn if_false() {
let source = "if 1 == 2 { 2 }";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(5, 7)
),
(Instruction::jump(1, true), Span(10, 11)),
(Instruction::load_constant(0, 1, false), Span(12, 13)),
(Instruction::r#return(false), Span(15, 15))
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(None));
}
#[test]
fn if_true() {
let source = "if 1 == 1 { 2 }";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 0)
.set_b_is_constant()
.set_c_is_constant(),
Span(5, 7)
),
(Instruction::jump(1, true), Span(10, 11)),
(Instruction::load_constant(0, 1, false), Span(12, 13)),
(Instruction::r#return(false), Span(15, 15))
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(None));
}

View File

@ -1,126 +0,0 @@
use dust_lang::*;
#[test]
fn function() {
let source = "fn(a: int, b: int) -> int { a + b }";
assert_eq!(
run(source),
Ok(Some(Value::function(
Chunk::with_data(
None,
vec![
(Instruction::add(2, 0, 1), Span(30, 31)),
(Instruction::r#return(true), Span(35, 35)),
],
vec![Value::string("a"), Value::string("b"),],
vec![
Local::new(0, Some(Type::Integer), false, Scope::default(), 0),
Local::new(1, Some(Type::Integer), false, Scope::default(), 1)
]
),
FunctionType {
type_parameters: None,
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
return_type: Some(Box::new(Type::Integer)),
}
)))
);
}
#[test]
fn function_call() {
let source = "fn(a: int, b: int) -> int { a + b }(1, 2)";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(0, 36)),
(Instruction::load_constant(1, 1, false), Span(36, 37)),
(Instruction::load_constant(2, 2, false), Span(39, 40)),
(Instruction::call(3, 0, 2), Span(35, 41)),
(Instruction::r#return(true), Span(41, 41)),
],
vec![
Value::function(
Chunk::with_data(
None,
vec![
(Instruction::add(2, 0, 1), Span(30, 31)),
(Instruction::r#return(true), Span(35, 36)),
],
vec![Value::string("a"), Value::string("b"),],
vec![
Local::new(0, Some(Type::Integer), false, Scope::default(), 0),
Local::new(1, Some(Type::Integer), false, Scope::default(), 1)
]
),
FunctionType {
type_parameters: None,
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
return_type: Some(Box::new(Type::Integer)),
}
),
Value::integer(1),
Value::integer(2)
],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(3))));
}
#[test]
fn function_declaration() {
let source = "fn add (a: int, b: int) -> int { a + b }";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 1, false), Span(0, 40)),
(Instruction::define_local(0, 0, false), Span(3, 6)),
(Instruction::r#return(false), Span(40, 40))
],
vec![
Value::string("add"),
Value::function(
Chunk::with_data(
None,
vec![
(Instruction::add(2, 0, 1), Span(35, 36)),
(Instruction::r#return(true), Span(40, 40)),
],
vec![Value::string("a"), Value::string("b")],
vec![
Local::new(0, Some(Type::Integer), false, Scope::default(), 0),
Local::new(1, Some(Type::Integer), false, Scope::default(), 1)
]
),
FunctionType {
type_parameters: None,
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
return_type: Some(Box::new(Type::Integer)),
},
)
],
vec![Local::new(
0,
Some(Type::Function(FunctionType {
type_parameters: None,
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
return_type: Some(Box::new(Type::Integer)),
})),
false,
Scope::default(),
0
),],
)),
);
assert_eq!(run(source), Ok(None));
}

View File

@ -1,127 +0,0 @@
use dust_lang::*;
#[test]
fn empty_list() {
let source = "[]";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_list(0, 0), Span(0, 2)),
(Instruction::r#return(true), Span(2, 2)),
],
vec![],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::abstract_list(0, 0, Type::Any))));
}
#[test]
fn list() {
let source = "[1, 2, 3]";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(1, 2)),
(Instruction::load_constant(1, 1, false), Span(4, 5)),
(Instruction::load_constant(2, 2, false), Span(7, 8)),
(Instruction::load_list(3, 0), Span(0, 9)),
(Instruction::r#return(true), Span(9, 9)),
],
vec![Value::integer(1), Value::integer(2), Value::integer(3)],
vec![]
)),
);
assert_eq!(
run(source),
Ok(Some(Value::abstract_list(0, 3, Type::Integer)))
);
}
#[test]
fn list_with_complex_expression() {
let source = "[1, 2 + 3 - 4 * 5]";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(1, 2)),
(
*Instruction::add(1, 1, 2)
.set_b_is_constant()
.set_c_is_constant(),
Span(6, 7)
),
(
*Instruction::multiply(2, 3, 4)
.set_b_is_constant()
.set_c_is_constant(),
Span(14, 15)
),
(Instruction::subtract(3, 1, 2), Span(10, 11)),
(Instruction::close(1, 3), Span(17, 18)),
(Instruction::load_list(4, 0), Span(0, 18)),
(Instruction::r#return(true), Span(18, 18)),
],
vec![
Value::integer(1),
Value::integer(2),
Value::integer(3),
Value::integer(4),
Value::integer(5)
],
vec![]
)),
);
assert_eq!(
run(source),
Ok(Some(Value::abstract_list(0, 4, Type::Integer)))
);
}
#[test]
fn list_with_simple_expression() {
let source = "[1, 2 + 3, 4]";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(1, 2)),
(
*Instruction::add(1, 1, 2)
.set_b_is_constant()
.set_c_is_constant(),
Span(6, 7)
),
(Instruction::load_constant(2, 3, false), Span(11, 12)),
(Instruction::load_list(3, 0), Span(0, 13)),
(Instruction::r#return(true), Span(13, 13)),
],
vec![
Value::integer(1),
Value::integer(2),
Value::integer(3),
Value::integer(4),
],
vec![]
)),
);
assert_eq!(
run(source),
Ok(Some(Value::abstract_list(0, 3, Type::Integer)))
);
}

View File

@ -1,77 +0,0 @@
use dust_lang::*;
#[test]
fn and() {
let source = "true && false";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_boolean(0, true, false), Span(0, 4)),
(Instruction::test(0, false), Span(5, 7)),
(Instruction::jump(1, true), Span(5, 7)),
(Instruction::load_boolean(1, false, false), Span(8, 13)),
(Instruction::r#return(true), Span(13, 13)),
],
vec![],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
}
#[test]
fn or() {
let source = "true || false";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_boolean(0, true, false), Span(0, 4)),
(Instruction::test(0, true), Span(5, 7)),
(Instruction::jump(1, true), Span(5, 7)),
(Instruction::load_boolean(1, false, false), Span(8, 13)),
(Instruction::r#return(true), Span(13, 13)),
],
vec![],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
}
#[test]
fn variable_and() {
let source = "let a = true; let b = false; a && b";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_boolean(0, true, false), Span(8, 12)),
(Instruction::define_local(0, 0, false), Span(4, 5)),
(Instruction::load_boolean(1, false, false), Span(22, 27)),
(Instruction::define_local(1, 1, false), Span(18, 19)),
(Instruction::get_local(2, 0), Span(29, 30)),
(Instruction::test(2, false), Span(31, 33)),
(Instruction::jump(1, true), Span(31, 33)),
(Instruction::get_local(3, 1), Span(34, 35)),
(Instruction::r#return(true), Span(35, 35)),
],
vec![Value::string("a"), Value::string("b"),],
vec![
Local::new(0, None, false, Scope::default(), 0),
Local::new(1, None, false, Scope::default(), 1),
]
))
);
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
}

View File

@ -1,35 +0,0 @@
use dust_lang::*;
#[test]
fn r#while() {
let source = "let mut x = 0; while x < 5 { x = x + 1 } x";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(
*Instruction::less(true, 0, 2).set_c_is_constant(),
Span(23, 24)
),
(Instruction::jump(2, true), Span(41, 42)),
(*Instruction::add(0, 0, 3).set_c_is_constant(), Span(39, 40)),
(Instruction::jump(3, false), Span(41, 42)),
(Instruction::get_local(1, 0), Span(41, 42)),
(Instruction::r#return(true), Span(42, 42)),
],
vec![
Value::integer(0),
Value::string("x"),
Value::integer(5),
Value::integer(1),
],
vec![Local::new(1, None, true, Scope::default(), 0),]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(5))));
}

View File

@ -1,324 +0,0 @@
use dust_lang::*;
#[test]
fn add() {
let source = "1 + 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::add(0, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 3)
),
(Instruction::r#return(true), Span(5, 5))
],
vec![Value::integer(1), Value::integer(2)],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(3))));
}
#[test]
fn add_assign() {
let source = "let mut a = 1; a += 2; a";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(*Instruction::add(0, 0, 2).set_c_is_constant(), Span(17, 19)),
(Instruction::get_local(1, 0), Span(23, 24)),
(Instruction::r#return(true), Span(24, 24))
],
vec![Value::integer(1), Value::string("a"), Value::integer(2)],
vec![Local::new(1, None, true, Scope::default(), 0)]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(3))));
}
#[test]
fn add_assign_expects_mutable_variable() {
let source = "1 += 2";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::ExpectedMutableVariable {
found: Token::Integer("1").to_owned(),
position: Span(0, 1)
},
source
})
);
}
// #[test]
// fn add_expects_integer_float_or_string() {
// let source = "true + false";
// assert_eq!(
// parse(source),
// Err(DustError::Parse {
// error: ParseError::ExpectedIntegerFloatOrString {
// found: Token::True,
// position: Span(0, 3)
// },
// source
// })
// );
// }
#[test]
fn divide() {
let source = "2 / 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::divide(0, 0, 0)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 3)
),
(Instruction::r#return(true), Span(5, 5))
],
vec![Value::integer(2)],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(1))));
}
#[test]
fn divide_assign() {
let source = "let mut a = 2; a /= 2; a";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(
*Instruction::divide(0, 0, 0).set_c_is_constant(),
Span(17, 19)
),
(Instruction::get_local(1, 0), Span(23, 24)),
(Instruction::r#return(true), Span(24, 24))
],
vec![Value::integer(2), Value::string("a")],
vec![Local::new(1, None, true, Scope::default(), 0)]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(1))));
}
#[test]
fn divide_assign_expects_mutable_variable() {
let source = "1 -= 2";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::ExpectedMutableVariable {
found: Token::Integer("1").to_owned(),
position: Span(0, 1)
},
source
})
);
}
#[test]
fn math_operator_precedence() {
let source = "1 + 2 - 3 * 4 / 5";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::add(0, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 3)
),
(
*Instruction::multiply(1, 2, 3)
.set_b_is_constant()
.set_c_is_constant(),
Span(10, 11)
),
(
*Instruction::divide(2, 1, 4).set_c_is_constant(),
Span(14, 15)
),
(Instruction::subtract(3, 0, 2), Span(6, 7)),
(Instruction::r#return(true), Span(17, 17)),
],
vec![
Value::integer(1),
Value::integer(2),
Value::integer(3),
Value::integer(4),
Value::integer(5),
],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(1))));
}
#[test]
fn multiply() {
let source = "1 * 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::multiply(0, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 3)
),
(Instruction::r#return(true), Span(5, 5)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(2))));
}
#[test]
fn multiply_assign() {
let source = "let mut a = 2; a *= 3 a";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(
*Instruction::multiply(0, 0, 2).set_c_is_constant(),
Span(17, 19)
),
(Instruction::get_local(1, 0), Span(22, 23)),
(Instruction::r#return(true), Span(23, 23))
],
vec![Value::integer(2), Value::string("a"), Value::integer(3)],
vec![Local::new(1, None, true, Scope::default(), 0),]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(6))));
}
#[test]
fn multiply_assign_expects_mutable_variable() {
let source = "1 *= 2";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::ExpectedMutableVariable {
found: Token::Integer("1").to_owned(),
position: Span(0, 1)
},
source
})
);
}
#[test]
fn subtract() {
let source = "1 - 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::subtract(0, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 3)
),
(Instruction::r#return(true), Span(5, 5)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(-1))));
}
#[test]
fn subtract_assign() {
let source = "let mut x = 42; x -= 2; x";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(12, 14)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(
*Instruction::subtract(0, 0, 2).set_c_is_constant(),
Span(18, 20)
),
(Instruction::get_local(1, 0), Span(24, 25)),
(Instruction::r#return(true), Span(25, 25)),
],
vec![Value::integer(42), Value::string("x"), Value::integer(2)],
vec![Local::new(1, None, true, Scope::default(), 0)]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(40))));
}
#[test]
fn subtract_assign_expects_mutable_variable() {
let source = "1 -= 2";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::ExpectedMutableVariable {
found: Token::Integer("1").to_owned(),
position: Span(0, 1)
},
source
})
);
}

View File

@ -1,59 +0,0 @@
use dust_lang::*;
#[test]
fn panic() {
let source = "panic(\"Goodbye world!\", 42)";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(6, 22)),
(Instruction::load_constant(1, 1, false), Span(24, 26)),
(
Instruction::call_native(2, NativeFunction::Panic, 2),
Span(0, 27)
),
(Instruction::r#return(true), Span(27, 27))
],
vec![Value::string("Goodbye world!"), Value::integer(42)],
vec![]
)),
);
assert_eq!(
run(source),
Err(DustError::Runtime {
error: VmError::NativeFunction(NativeFunctionError::Panic {
message: Some("Goodbye world! 42".to_string()),
position: Span(0, 27)
}),
source
})
)
}
#[test]
fn to_string() {
let source = "to_string(42)";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(10, 12)),
(
Instruction::call_native(1, NativeFunction::ToString, 1),
Span(0, 13)
),
(Instruction::r#return(true), Span(13, 13))
],
vec![Value::integer(42)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::string("42"))))
}

View File

@ -1,244 +1,47 @@
use dust_lang::*; use dust_lang::*;
#[test] #[test]
fn allow_access_to_parent_scope() { fn block_scope_captures_parent() {
let source = r#" let source = "let x = 42; { x }";
let x = 1;
{
x
}
"#;
assert_eq!(run(source), Ok(Some(Value::integer(1)))); assert_eq!(run(source), Ok(Some(Value::integer(42))));
} }
#[test] #[test]
fn block_scope() { fn block_scope_does_not_capture_child() {
let source = " env_logger::builder().is_test(true).try_init().unwrap();
let a = 0;
{
let b = 42;
{
let c = 1;
}
let d = 2;
}
let e = 1;
";
assert_eq!( let source = "{ let x = 42; } x";
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(17, 18)),
(Instruction::define_local(0, 0, false), Span(13, 14)),
(Instruction::load_constant(1, 2, false), Span(50, 52)),
(Instruction::define_local(1, 1, false), Span(46, 47)),
(Instruction::load_constant(2, 4, false), Span(92, 93)),
(Instruction::define_local(2, 2, false), Span(88, 89)),
(Instruction::load_constant(3, 6, false), Span(129, 130)),
(Instruction::define_local(3, 3, false), Span(125, 126)),
(Instruction::load_constant(4, 4, false), Span(158, 159)),
(Instruction::define_local(4, 4, false), Span(154, 155)),
(Instruction::r#return(false), Span(165, 165))
],
vec![
Value::integer(0),
Value::string("a"),
Value::integer(42),
Value::string("b"),
Value::integer(1),
Value::string("c"),
Value::integer(2),
Value::string("d"),
Value::string("e"),
],
vec![
Local::new(1, None, false, Scope::new(0, 0), 0),
Local::new(3, None, false, Scope::new(1, 1), 1),
Local::new(5, None, false, Scope::new(2, 2), 2),
Local::new(7, None, false, Scope::new(1, 1), 3),
Local::new(8, None, false, Scope::new(0, 0), 4),
]
)),
);
assert_eq!(run(source), Ok(None));
}
#[test]
fn multiple_block_scopes() {
let source = "
let a = 0;
{
let b = 42;
{
let c = 1;
}
let d = 2;
}
let q = 42;
{
let b = 42;
{
let c = 1;
}
let d = 2;
}
let e = 1;
";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(17, 18)),
(Instruction::define_local(0, 0, false), Span(13, 14)),
(Instruction::load_constant(1, 2, false), Span(50, 52)),
(Instruction::define_local(1, 1, false), Span(46, 47)),
(Instruction::load_constant(2, 4, false), Span(92, 93)),
(Instruction::define_local(2, 2, false), Span(88, 89)),
(Instruction::load_constant(3, 6, false), Span(129, 130)),
(Instruction::define_local(3, 3, false), Span(125, 126)),
(Instruction::load_constant(4, 2, false), Span(158, 160)),
(Instruction::define_local(4, 4, false), Span(154, 155)),
(Instruction::load_constant(5, 2, false), Span(192, 194)),
(Instruction::define_local(5, 5, false), Span(188, 189)),
(Instruction::load_constant(6, 4, false), Span(234, 235)),
(Instruction::define_local(6, 6, false), Span(230, 231)),
(Instruction::load_constant(7, 6, false), Span(271, 272)),
(Instruction::define_local(7, 7, false), Span(267, 268)),
(Instruction::load_constant(8, 4, false), Span(300, 301)),
(Instruction::define_local(8, 8, false), Span(296, 297)),
(Instruction::r#return(false), Span(307, 307))
],
vec![
Value::integer(0),
Value::string("a"),
Value::integer(42),
Value::string("b"),
Value::integer(1),
Value::string("c"),
Value::integer(2),
Value::string("d"),
Value::string("q"),
Value::string("e"),
],
vec![
Local::new(1, None, false, Scope::new(0, 0), 0),
Local::new(3, None, false, Scope::new(1, 1), 1),
Local::new(5, None, false, Scope::new(2, 2), 2),
Local::new(7, None, false, Scope::new(1, 1), 3),
Local::new(8, None, false, Scope::new(0, 0), 4),
Local::new(3, None, false, Scope::new(1, 3), 5),
Local::new(5, None, false, Scope::new(2, 4), 6),
Local::new(7, None, false, Scope::new(1, 3), 7),
Local::new(9, None, false, Scope::new(0, 0), 8),
]
)),
);
assert_eq!(run(source), Ok(None));
}
#[test]
fn disallow_access_to_child_scope() {
let source = r#"
{
let x = 1;
}
x
"#;
assert_eq!( assert_eq!(
run(source), run(source),
Err(DustError::Compile { Err(DustError::analysis(
error: CompileError::VariableOutOfScope { [AnalysisError::UndefinedVariable {
identifier: "x".to_string(), identifier: Node::new(Identifier::new("x"), (16, 17))
position: Span(52, 53), }],
variable_scope: Scope::new(1, 1),
access_scope: Scope::new(0, 0),
},
source source
}) ))
); );
} }
#[test] #[test]
fn disallow_access_to_child_scope_nested() { fn block_scope_does_not_capture_sibling() {
let source = r#" let source = "{ let x = 42; } { x }";
{
{
let x = 1;
}
x
}
"#;
assert_eq!( assert_eq!(
run(source), run(source),
Err(DustError::Compile { Err(DustError::analysis(
error: CompileError::VariableOutOfScope { [AnalysisError::UndefinedVariable {
identifier: "x".to_string(), identifier: Node::new(Identifier::new("x"), (18, 19))
position: Span(78, 79), }],
variable_scope: Scope::new(2, 2),
access_scope: Scope::new(1, 1),
},
source source
}) ))
); );
} }
#[test] #[test]
fn disallow_access_to_sibling_scope() { fn block_scope_does_not_pollute_parent() {
let source = r#" let source = "let x = 42; { let x = \"foo\"; let x = \"bar\"; } x";
{
let x = 1;
}
{
x
}
"#;
assert_eq!( assert_eq!(run(source), Ok(Some(Value::integer(42))));
run(source),
Err(DustError::Compile {
error: CompileError::VariableOutOfScope {
identifier: "x".to_string(),
variable_scope: Scope::new(1, 1),
access_scope: Scope::new(1, 2),
position: Span(66, 67),
},
source
})
);
}
#[test]
fn disallow_access_to_sibling_scope_nested() {
let source = r#"
{
{
let x = 1;
}
{
x
}
}
"#;
assert_eq!(
run(source),
Err(DustError::Compile {
error: CompileError::VariableOutOfScope {
identifier: "x".to_string(),
variable_scope: Scope::new(2, 2),
access_scope: Scope::new(2, 3),
position: Span(96, 97),
},
source
})
);
} }

View File

@ -1,42 +0,0 @@
use dust_lang::*;
#[test]
fn negate() {
let source = "-(42)";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(*Instruction::negate(0, 0).set_b_is_constant(), Span(0, 1)),
(Instruction::r#return(true), Span(5, 5)),
],
vec![Value::integer(42)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(-42))));
}
#[test]
fn not() {
let source = "!true";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_boolean(0, true, false), Span(1, 5)),
(Instruction::not(1, 0), Span(0, 1)),
(Instruction::r#return(true), Span(5, 5)),
],
vec![],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
}

View File

@ -1,63 +0,0 @@
use dust_lang::*;
#[test]
fn define_local() {
let source = "let x = 42;";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(8, 10)),
(Instruction::define_local(0, 0, false), Span(4, 5)),
(Instruction::r#return(false), Span(11, 11))
],
vec![Value::integer(42), Value::string("x")],
vec![Local::new(1, None, false, Scope::default(), 0)]
)),
);
assert_eq!(run(source), Ok(None));
}
#[test]
fn let_statement_expects_identifier() {
let source = "let 1 = 2";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::ExpectedToken {
expected: TokenKind::Identifier,
found: Token::Integer("1").to_owned(),
position: Span(4, 5)
},
source
})
);
}
#[test]
fn set_local() {
let source = "let mut x = 41; x = 42; x";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(12, 14)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(Instruction::load_constant(1, 2, false), Span(20, 22)),
(Instruction::set_local(1, 0), Span(16, 17)),
(Instruction::get_local(2, 0), Span(24, 25)),
(Instruction::r#return(true), Span(25, 25)),
],
vec![Value::integer(41), Value::string("x"), Value::integer(42)],
vec![Local::new(1, None, true, Scope::default(), 0)]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(42))));
}

View File

@ -9,7 +9,5 @@ repository.workspace = true
[dependencies] [dependencies]
clap = { version = "4.5.14", features = ["derive"] } clap = { version = "4.5.14", features = ["derive"] }
colored = "2.1.0"
dust-lang = { path = "../dust-lang" } dust-lang = { path = "../dust-lang" }
env_logger = "0.11.5" env_logger = "0.11.5"
log = "0.4.22"

View File

@ -1,138 +1,55 @@
use std::{fs::read_to_string, io::Write}; use std::fs::read_to_string;
use clap::Parser; use clap::Parser;
use colored::Colorize; use dust_lang::run;
use dust_lang::{compile, format, run};
use log::{Level, LevelFilter};
#[derive(Parser)] #[derive(Parser)]
struct Cli { struct Cli {
/// Source code sent via command line
#[arg(short, long)] #[arg(short, long)]
command: Option<String>, command: Option<String>,
/// Whether to output formatted source code
#[arg(short, long)]
format: bool,
/// Whether to output line numbers in formatted source code
#[arg(long)]
format_line_numbers: Option<bool>,
/// Whether to output colors in formatted source code
#[arg(long)]
format_colored: Option<bool>,
/// Whether to output the disassembled chunk
#[arg(short, long)] #[arg(short, long)]
parse: bool, parse: bool,
/// Whether to style the disassembled chunk
#[arg(long)]
style_disassembly: Option<bool>,
/// Log level
#[arg(short, long)]
log: Option<LevelFilter>,
/// Path to a source code file
path: Option<String>, path: Option<String>,
} }
fn main() { fn main() {
env_logger::init();
let args = Cli::parse(); let args = Cli::parse();
let mut logger = env_logger::builder();
logger.format(|buf, record| { if let Some(command) = &args.command {
let level_display = match record.level() { if args.parse {
Level::Info => "INFO".bold().white(), parse_and_display_errors(command);
Level::Debug => "DEBUG".bold().blue(),
Level::Warn => "WARN".bold().yellow(),
Level::Error => "ERROR".bold().red(),
Level::Trace => "TRACE".bold().purple(),
};
let module = record
.module_path()
.map(|path| path.split("::").last().unwrap_or(path))
.unwrap_or("unknown")
.dimmed();
let display = format!("{level_display:5} {module:^6} {args}", args = record.args());
writeln!(buf, "{display}")
});
if let Some(level) = args.log {
logger.filter_level(level).init();
} else { } else {
logger.parse_env("DUST_LOG").init(); run_and_display_errors(command);
}
let source = if let Some(path) = &args.path {
&read_to_string(path).expect("Failed to read file")
} else if let Some(command) = &args.command {
command
} else {
eprintln!("No input provided");
return;
};
if args.format {
let line_numbers = args.format_line_numbers.unwrap_or(true);
let colored = args.format_colored.unwrap_or(true);
log::info!("Formatting source");
match format(source, line_numbers, colored) {
Ok(formatted) => println!("{}", formatted),
Err(error) => {
eprintln!("{}", error.report());
}
}
} }
} else if let Some(path) = &args.path {
let source = read_to_string(path).expect("Failed to read file");
if args.parse { if args.parse {
let styled = args.style_disassembly.unwrap_or(true); parse_and_display_errors(&source);
} else {
log::info!("Parsing source"); run_and_display_errors(&source);
match compile(source) {
Ok(chunk) => {
let disassembly = chunk
.disassembler()
.source(source)
.styled(styled)
.disassemble();
println!("{}", disassembly);
}
Err(error) => {
eprintln!("{}", error.report());
} }
} }
} }
if args.format || args.parse { fn parse_and_display_errors(source: &str) {
return; match dust_lang::parse(source) {
Ok(ast) => println!("{:#?}", ast),
Err(error) => eprintln!("{}", error.report()),
}
} }
fn run_and_display_errors(source: &str) {
match run(source) { match run(source) {
Ok(Some(value)) => println!("{}", value), Ok(return_value) => {
Ok(None) => {} if let Some(value) = return_value {
Err(error) => { println!("{}", value);
eprintln!("{}", error.report());
} }
} }
} Err(error) => eprintln!("{}", error.report()),
#[cfg(test)]
mod tests {
use clap::CommandFactory;
use super::*;
#[test]
fn verify_cli() {
Cli::command().debug_assert();
} }
} }

View File

@ -1,5 +0,0 @@
var i = 0;
while (i < 10000) {
i++;
}

View File

@ -1,11 +0,0 @@
function fib(n) {
if (n <= 0) {
return 0;
} else if (n === 1) {
return 1;
} else {
return fib(n - 1) + fib(n - 2);
}
}
console.log(fib(25));

View File

@ -1,5 +0,0 @@
let mut i = 0;
while i < 10000 {
i += 1;
}

View File

@ -1,8 +0,0 @@
fn fib (n: int) -> int {
if n <= 0 { return 0 }
if n == 1 { return 1 }
fib(n - 1) + fib(n - 2)
}
write_line(fib(25))

View File

@ -11,7 +11,7 @@ while count <= 15 {
} else if divides_by_5 { } else if divides_by_5 {
"buzz" "buzz"
} else { } else {
to_string(count) count.to_string()
} }
write_line(output) write_line(output)