Compare commits
205 Commits
Author | SHA1 | Date | |
---|---|---|---|
bb345a7938 | |||
04a1c81a2a | |||
f54d2d0d22 | |||
3a6c05a79c | |||
ea88ffc451 | |||
a997665d1a | |||
dc9b451b37 | |||
e99a7b5e1e | |||
74bb8a429a | |||
d587d87ed7 | |||
87f597624a | |||
78840cf3e7 | |||
c0b998c0d8 | |||
e2df823566 | |||
f08c7c6f1f | |||
a16f7795de | |||
314913dbf5 | |||
9294b8f7ed | |||
ea46bfe5da | |||
9650183c73 | |||
3330939128 | |||
dd13d2efee | |||
b0af1609f0 | |||
80b6380255 | |||
5ff5568e95 | |||
cfb4fa66b5 | |||
8c72e921dc | |||
0dcfcd5375 | |||
a2fa5afcd4 | |||
c77e1c89a9 | |||
34dca01d85 | |||
0e7aae79f9 | |||
a2e7a4e73e | |||
a9e675cb4f | |||
60535e20d6 | |||
e85297bcbb | |||
1947d66be5 | |||
ae6d3d7a82 | |||
7e4f6654a4 | |||
32ff52d9b3 | |||
febd7bb054 | |||
604962c535 | |||
1f9adfab2f | |||
10abd91c85 | |||
bd23853657 | |||
382d43ef77 | |||
4d6412006a | |||
d7be203bfc | |||
c264aaeb13 | |||
af4e43fc9f | |||
caf1c22af0 | |||
e304195661 | |||
004cf73959 | |||
c0f74f1e09 | |||
648bbdbc4d | |||
1da61f0873 | |||
8db37bcdfd | |||
6caae6c952 | |||
f15cf84c4d | |||
13b4cfffcc | |||
9d5c9d9fd0 | |||
65e513488f | |||
1c8e8b35b9 | |||
3f8b554a86 | |||
8c79157fa7 | |||
6bcc5b1555 | |||
b7153df9be | |||
bfade78a0d | |||
8b70a1dcc4 | |||
589e59b8c4 | |||
86e055a562 | |||
19f2d19134 | |||
95e5b3062d | |||
44659ec34a | |||
0c758c9768 | |||
12d34d6354 | |||
3609fddaea | |||
5d62d897f4 | |||
565d3c54f1 | |||
02ee7d126c | |||
c7bba88875 | |||
5eb901f468 | |||
055f0a4100 | |||
d5fc68e466 | |||
743679371d | |||
2864bee057 | |||
2527cc2de7 | |||
ea0be43199 | |||
5bbda1a24e | |||
f1034534ed | |||
02877d06d3 | |||
30b2801a38 | |||
9c612317dc | |||
6e1ef77192 | |||
c1e372d7cf | |||
61d633392c | |||
b6c3b1e5ba | |||
259721f6cb | |||
bdc34cb10e | |||
dddbf77fac | |||
79cc59c952 | |||
35f73d60f0 | |||
437a6bf164 | |||
d857f42434 | |||
9d0aa13e8a | |||
9b1dc6c55c | |||
5015cf4cc4 | |||
5411a1db27 | |||
ba904fdcd8 | |||
9d1996c9ec | |||
88684f49b6 | |||
d0d80cf407 | |||
47d6ea417d | |||
daca836db1 | |||
60df8b4d64 | |||
95e22aa97f | |||
7afde989f9 | |||
85241c04b9 | |||
c1eccf049f | |||
855782c370 | |||
a6da34a79c | |||
3df42f6a47 | |||
1d03876b89 | |||
5441938725 | |||
c31991cc24 | |||
9fe3e440ac | |||
182ef66f23 | |||
d68c789ea8 | |||
1d0bafa4a3 | |||
2c485cf046 | |||
c5c2fe95ae | |||
23f733d8b2 | |||
57edf48e36 | |||
573e5ae470 | |||
31bb7eaffc | |||
d4a8a65096 | |||
dee09d3583 | |||
be77d64c39 | |||
03113fdf5e | |||
bf4f319302 | |||
a1dd7e3bb9 | |||
5b3232c723 | |||
413cb70731 | |||
915340fbdb | |||
85b95a56aa | |||
2cb03297c5 | |||
56f1222cfc | |||
89573e81b9 | |||
fa4c2d48a3 | |||
0a16c5e0ca | |||
2f8c46f0a5 | |||
37dc2e05c5 | |||
f02c3d1fb5 | |||
6ca96bc1dc | |||
00555785e3 | |||
6c76006ad9 | |||
0d55bb7244 | |||
fd4ffeec7c | |||
71a4f863e3 | |||
9cb6873618 | |||
b66710e3eb | |||
4653a3f506 | |||
97bde437e8 | |||
d1bdabed56 | |||
ba80774e7b | |||
aa8b1215a8 | |||
9418cd5b70 | |||
8534f18c9a | |||
2ad3440097 | |||
3b7987c218 | |||
a0439675b7 | |||
82a2b8f6b7 | |||
5c54a5b9bd | |||
9144257524 | |||
caf59894b6 | |||
8b33df3d4a | |||
78c9ed97e2 | |||
6ff25a22ec | |||
d4d58e793b | |||
67e5de6664 | |||
7b055d79b5 | |||
974310ffab | |||
86f8e47b0c | |||
e4204c1b0d | |||
8f58bf30a4 | |||
c3790e90bf | |||
4ba3a47ae5 | |||
f936c30b4f | |||
0ed2733991 | |||
8798efc0af | |||
8f20e53880 | |||
fcfcb4a429 | |||
85f5f44946 | |||
5b8ec74d05 | |||
c406039c99 | |||
32347ec512 | |||
e9ec838b25 | |||
b8957190e0 | |||
3ac15fe70b | |||
03d44434e2 | |||
616f890028 | |||
812d930488 | |||
406edda573 | |||
3c2e3699ab | |||
1ecaac0819 |
427
Cargo.lock
generated
427
Cargo.lock
generated
@ -4,9 +4,9 @@ version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@ -23,52 +23,65 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.13"
|
||||
version = "0.6.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
|
||||
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.8"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
|
||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.3"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
|
||||
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.0.2"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
|
||||
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.2"
|
||||
version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
|
||||
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
@ -77,9 +90,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.14"
|
||||
version = "4.5.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c937d4061031a6d0c8da4b9a4f98a172fc2976dfb1c19213a9cf7d0d3c837e36"
|
||||
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@ -87,9 +100,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.14"
|
||||
version = "4.5.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85379ba512b21a328adf887e85f7742d12e96eb31f3ef077df4ffc26b506ffed"
|
||||
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@ -99,9 +112,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.13"
|
||||
version = "4.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
|
||||
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@ -117,44 +130,30 @@ checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.0"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.5"
|
||||
name = "colored"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
|
||||
checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
"lazy_static",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "dust-lang"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"annotate-snippets",
|
||||
"colored",
|
||||
"env_logger",
|
||||
"getrandom",
|
||||
"log",
|
||||
"rand",
|
||||
"rayon",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
@ -164,21 +163,17 @@ name = "dust-shell"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"colored",
|
||||
"dust-lang",
|
||||
"env_logger",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "0.1.0"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
|
||||
checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab"
|
||||
dependencies = [
|
||||
"log",
|
||||
"regex",
|
||||
@ -199,13 +194,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.12"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
|
||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -221,16 +218,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.10"
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[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]]
|
||||
name = "libc"
|
||||
version = "0.2.153"
|
||||
version = "0.2.161"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
@ -240,30 +258,39 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.1"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.79"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
|
||||
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@ -298,31 +325,11 @@ dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@ -332,9 +339,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.6"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
|
||||
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@ -343,30 +350,30 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.2"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.17"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
|
||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.203"
|
||||
version = "1.0.214"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
|
||||
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.203"
|
||||
version = "1.0.214"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
|
||||
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -375,11 +382,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.117"
|
||||
version = "1.0.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
|
||||
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
@ -392,9 +400,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.53"
|
||||
version = "2.0.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032"
|
||||
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -403,21 +411,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.13"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
@ -426,67 +434,216 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"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]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.4"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.4"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.4"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.4"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.4"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.4"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.4"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.4"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[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
125
README.md
@ -1,109 +1,50 @@
|
||||
# Dust
|
||||
|
||||
High-level programming language with effortless concurrency, automatic memory management and type
|
||||
safety.
|
||||
Dust is a high-level interpreted programming language with static types that focuses on ease of use,
|
||||
performance and correctness.
|
||||
|
||||
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.
|
||||
## Implementation
|
||||
|
||||
## Usage
|
||||
Dust is implemented in Rust and is divided into several parts, primarily the lexer, compiler, and
|
||||
virtual machine. All of Dust's components are designed with performance in mind and the codebase
|
||||
uses as few dependencies as possible.
|
||||
|
||||
The Dust command line tool can be used to run Dust programs. It is not yet available outside of
|
||||
this repository.
|
||||
### Lexer
|
||||
|
||||
```sh
|
||||
cargo run --package dust-shell -- examples/hello_world.ds
|
||||
```
|
||||
The lexer emits tokens from the source code. Dust makes extensive use of Rust's zero-copy
|
||||
capabilities to avoid unnecessary allocations when creating tokens. A token, depending on its type,
|
||||
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.
|
||||
|
||||
```sh
|
||||
cargo run --package dust-shell -- -c '"Hello my name is " + read_line() + "!"'
|
||||
```
|
||||
### Compiler
|
||||
|
||||
Dust is easily embedded in another program. You can run a dust program of any size or complexity
|
||||
with a single function.
|
||||
The compiler creates a chunk, which contains all of the data needed by the virtual machine to run a
|
||||
Dust program. It does so by emitting bytecode instructions, constants and locals while parsing the
|
||||
tokens, which are generated one at a time by the lexer.
|
||||
|
||||
```rust
|
||||
use dust_lang::{run, Value};
|
||||
#### Parsing
|
||||
|
||||
fn main() {
|
||||
let code = "
|
||||
let x = 'Dust'
|
||||
let y = ' is awesome!'
|
||||
Dust's compiler uses a custom Pratt parser, a kind of recursive descent parser, to translate a
|
||||
sequence of tokens into a chunk.
|
||||
|
||||
write_line(x + y)
|
||||
#### Optimizing
|
||||
|
||||
42
|
||||
";
|
||||
When generating instructions for a register-based virtual machine, there are opportunities to
|
||||
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.
|
||||
|
||||
let result = run(code);
|
||||
### Instructions
|
||||
|
||||
assert_eq!(result, Ok(Some(Value::integer(42))));
|
||||
}
|
||||
```
|
||||
### Virtual Machine
|
||||
|
||||
## Concepts
|
||||
## Previous Implementations
|
||||
|
||||
### Effortless Concurrency
|
||||
## Inspiration
|
||||
|
||||
Dust makes concurrency as effortless as possible. Dust is organized into **statements**, and any
|
||||
sequence of statements can be run concurrently by simply adding the `async` keyword before the block
|
||||
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.
|
||||
- [The Implementation of Lua 5.0](https://www.lua.org/doc/jucs05.pdf)
|
||||
- [A No-Frills Introduction to Lua 5.1 VM Instructions](https://www.mcours.net/cours/pdf/hasclic3/hasssclic818.pdf)
|
||||
- [Crafting Interpreters](https://craftinginterpreters.com/)
|
||||
|
@ -10,12 +10,14 @@ repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
annotate-snippets = "0.11.4"
|
||||
env_logger = "0.11.3"
|
||||
colored = "2.1.0"
|
||||
log = "0.4.22"
|
||||
rand = "0.8.5"
|
||||
rayon = "1.9.0"
|
||||
serde = { version = "1.0.203", features = ["derive"] }
|
||||
serde_json = "1.0.117"
|
||||
getrandom = { version = "0.2", features = [
|
||||
"js",
|
||||
] } # Indirect dependency, for wasm builds
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.11.5"
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,192 +0,0 @@
|
||||
//! 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,187 +0,0 @@
|
||||
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, "}}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,208 +0,0 @@
|
||||
//! 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
402
dust-lang/src/chunk.rs
Normal file
402
dust-lang/src/chunk.rs
Normal file
@ -0,0 +1,402 @@
|
||||
//! 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"),
|
||||
}
|
||||
}
|
||||
}
|
1977
dust-lang/src/compiler.rs
Normal file
1977
dust-lang/src/compiler.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,107 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,651 +0,0 @@
|
||||
//! 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);
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
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,
|
||||
),
|
||||
),
|
||||
]))
|
||||
})
|
||||
}
|
366
dust-lang/src/disassembler.rs
Normal file
366
dust-lang/src/disassembler.rs
Normal file
@ -0,0 +1,366 @@
|
||||
//! 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(§ion_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(§ion_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
|
||||
}
|
||||
}
|
@ -1,167 +1,53 @@
|
||||
//! Top-level error handling for the Dust language.
|
||||
//! Top-level Dust errors with source code annotations.
|
||||
use annotate_snippets::{Level, Renderer, Snippet};
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::{AnalysisError, ContextError, LexError, ParseError, RuntimeError};
|
||||
use crate::{vm::VmError, CompileError, Span};
|
||||
|
||||
/// An error that occurred during the execution of the Dust language and its
|
||||
/// corresponding source code.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
/// A top-level error that can occur during the execution of Dust code.
|
||||
///
|
||||
/// This error can display nicely formatted messages with source code annotations.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum DustError<'src> {
|
||||
ContextError(ContextError),
|
||||
Compile {
|
||||
error: CompileError,
|
||||
source: &'src str,
|
||||
},
|
||||
Runtime {
|
||||
runtime_error: RuntimeError,
|
||||
error: VmError,
|
||||
source: &'src str,
|
||||
},
|
||||
Analysis {
|
||||
analysis_errors: Vec<AnalysisError>,
|
||||
source: &'src str,
|
||||
},
|
||||
Parse {
|
||||
parse_error: ParseError,
|
||||
source: &'src str,
|
||||
},
|
||||
Lex {
|
||||
lex_error: LexError,
|
||||
source: &'src str,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'src> From<ContextError> for DustError<'src> {
|
||||
fn from(error: ContextError) -> Self {
|
||||
Self::ContextError(error)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
let mut report = String::new();
|
||||
let renderer = Renderer::styled();
|
||||
|
||||
match self {
|
||||
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();
|
||||
DustError::Runtime { error, source } => {
|
||||
let position = error.position();
|
||||
let label = error.to_string();
|
||||
let message = Level::Error
|
||||
.title("Runtime error")
|
||||
.snippet(
|
||||
let label = format!("{}: {}", VmError::title(), error.description());
|
||||
let details = error
|
||||
.details()
|
||||
.unwrap_or_else(|| "While running this code".to_string());
|
||||
let message = Level::Error.title(&label).snippet(
|
||||
Snippet::source(source)
|
||||
.fold(true)
|
||||
.annotation(Level::Error.span(position.0..position.1).label(&label)),
|
||||
)
|
||||
.footer(
|
||||
Level::Error
|
||||
.title("This error occured during the execution of the Dust program."),
|
||||
.fold(false)
|
||||
.annotation(Level::Error.span(position.0..position.1).label(&details)),
|
||||
);
|
||||
|
||||
report.push_str(&renderer.render(message).to_string());
|
||||
report.push_str("\n\n");
|
||||
}
|
||||
DustError::Analysis {
|
||||
analysis_errors,
|
||||
source,
|
||||
} => {
|
||||
for error in analysis_errors {
|
||||
DustError::Compile { error, source } => {
|
||||
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(
|
||||
let label = format!("{}: {}", CompileError::title(), error.description());
|
||||
let details = error
|
||||
.details()
|
||||
.unwrap_or_else(|| "While parsing this code".to_string());
|
||||
let message = Level::Error.title(&label).snippet(
|
||||
Snippet::source(source)
|
||||
.fold(true)
|
||||
.annotation(Level::Error.span(position.0..position.1).label(&label)),
|
||||
);
|
||||
|
||||
report.push_str(&renderer.render(message).to_string());
|
||||
}
|
||||
DustError::Lex { lex_error, source } => {
|
||||
let position = lex_error.position();
|
||||
let label = lex_error.to_string();
|
||||
let message = Level::Error.title("Lex error").snippet(
|
||||
Snippet::source(source)
|
||||
.fold(true)
|
||||
.annotation(Level::Error.span(position.0..position.1).label(&label)),
|
||||
.fold(false)
|
||||
.annotation(Level::Error.span(position.0..position.1).label(&details)),
|
||||
);
|
||||
|
||||
report.push_str(&renderer.render(message).to_string());
|
||||
@ -172,22 +58,9 @@ impl<'src> DustError<'src> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for DustError<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
DustError::ContextError(context_error) => write!(f, "{context_error}"),
|
||||
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}"),
|
||||
}
|
||||
}
|
||||
pub trait AnnotatedError {
|
||||
fn title() -> &'static str;
|
||||
fn description(&self) -> &'static str;
|
||||
fn details(&self) -> Option<String>;
|
||||
fn position(&self) -> Span;
|
||||
}
|
||||
|
@ -1,41 +0,0 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
224
dust-lang/src/formatter.rs
Normal file
224
dust-lang/src/formatter.rs
Normal file
@ -0,0 +1,224 @@
|
||||
//! 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,
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
//! 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)
|
||||
}
|
||||
}
|
902
dust-lang/src/instruction.rs
Normal file
902
dust-lang/src/instruction.rs
Normal file
@ -0,0 +1,902 @@
|
||||
//! 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
@ -1,48 +1,44 @@
|
||||
//! The Dust programming language.
|
||||
//!
|
||||
//! To get started, you can use the `run` function to run a Dust program.
|
||||
//!
|
||||
//! ```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;
|
||||
//! The Dust programming language library.
|
||||
|
||||
pub mod chunk;
|
||||
pub mod compiler;
|
||||
pub mod disassembler;
|
||||
pub mod dust_error;
|
||||
pub mod evaluation;
|
||||
pub mod identifier;
|
||||
pub mod formatter;
|
||||
pub mod instruction;
|
||||
pub mod lexer;
|
||||
pub mod parser;
|
||||
pub mod native_function;
|
||||
pub mod operation;
|
||||
pub mod optimizer;
|
||||
pub mod token;
|
||||
pub mod r#type;
|
||||
pub mod value;
|
||||
pub mod vm;
|
||||
|
||||
pub use analyzer::{analyze, AnalysisError, Analyzer};
|
||||
pub use ast::{AbstractSyntaxTree, AstError, Expression, Node, Span, Statement};
|
||||
pub use built_in_function::{BuiltInFunction, BuiltInFunctionError};
|
||||
pub use constructor::{ConstructError, Constructor};
|
||||
pub use context::{Context, ContextData, ContextError};
|
||||
pub use core_library::core_library;
|
||||
pub use dust_error::DustError;
|
||||
pub use evaluation::{Evaluation, TypeEvaluation};
|
||||
pub use identifier::Identifier;
|
||||
pub use lexer::{lex, LexError, Lexer};
|
||||
pub use parser::{parse, ParseError, Parser};
|
||||
pub use r#type::*;
|
||||
pub use token::{Token, TokenKind, TokenOwned};
|
||||
pub use value::*;
|
||||
pub use vm::{run, RuntimeError, Vm};
|
||||
pub use crate::chunk::{Chunk, ChunkError, Local, Scope};
|
||||
pub use crate::compiler::{compile, CompileError, Compiler};
|
||||
pub use crate::disassembler::Disassembler;
|
||||
pub use crate::dust_error::{AnnotatedError, DustError};
|
||||
pub use crate::formatter::{format, Formatter};
|
||||
pub use crate::instruction::Instruction;
|
||||
pub use crate::lexer::{lex, LexError, Lexer};
|
||||
pub use crate::native_function::{NativeFunction, NativeFunctionError};
|
||||
pub use crate::operation::Operation;
|
||||
pub use crate::optimizer::{optimize, Optimizer};
|
||||
pub use crate::r#type::{EnumType, FunctionType, RangeableType, StructType, Type, TypeConflict};
|
||||
pub use crate::token::{Token, TokenKind, TokenOwned};
|
||||
pub use crate::value::{ConcreteValue, Function, Value, ValueError};
|
||||
pub use crate::vm::{run, Vm, VmError};
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
418
dust-lang/src/native_function.rs
Normal file
418
dust-lang/src/native_function.rs
Normal file
@ -0,0 +1,418 @@
|
||||
//! 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,
|
||||
}
|
||||
}
|
||||
}
|
110
dust-lang/src/operation.rs
Normal file
110
dust-lang/src/operation.rs
Normal file
@ -0,0 +1,110 @@
|
||||
//! 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)
|
||||
}
|
||||
}
|
96
dust-lang/src/optimizer.rs
Normal file
96
dust-lang/src/optimizer.rs
Normal file
@ -0,0 +1,96 @@
|
||||
//! 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)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,16 +1,39 @@
|
||||
//! Token and TokenOwned types.
|
||||
//! Token, TokenOwned and TokenKind types.
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Source code token.
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum Token<'src> {
|
||||
// End of file
|
||||
macro_rules! define_tokens {
|
||||
($($variant:ident $(($data_type:ty))?),+ $(,)?) => {
|
||||
/// 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> {
|
||||
#[default]
|
||||
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
|
||||
Boolean(&'src str),
|
||||
Byte(&'src str),
|
||||
Character(char),
|
||||
Float(&'src str),
|
||||
Identifier(&'src str),
|
||||
@ -23,17 +46,20 @@ pub enum Token<'src> {
|
||||
Break,
|
||||
Else,
|
||||
FloatKeyword,
|
||||
Fn,
|
||||
If,
|
||||
Int,
|
||||
Let,
|
||||
Loop,
|
||||
Map,
|
||||
Mut,
|
||||
Return,
|
||||
Str,
|
||||
Struct,
|
||||
While,
|
||||
|
||||
// Symbols
|
||||
ArrowThin,
|
||||
BangEqual,
|
||||
Bang,
|
||||
Colon,
|
||||
@ -54,6 +80,7 @@ pub enum Token<'src> {
|
||||
Minus,
|
||||
MinusEqual,
|
||||
Percent,
|
||||
PercentEqual,
|
||||
Plus,
|
||||
PlusEqual,
|
||||
RightCurlyBrace,
|
||||
@ -61,7 +88,9 @@ pub enum Token<'src> {
|
||||
RightSquareBrace,
|
||||
Semicolon,
|
||||
Slash,
|
||||
SlashEqual,
|
||||
Star,
|
||||
StarEqual,
|
||||
}
|
||||
|
||||
impl<'src> Token<'src> {
|
||||
@ -70,16 +99,19 @@ impl<'src> Token<'src> {
|
||||
match self {
|
||||
Token::Eof => 0,
|
||||
Token::Boolean(text) => text.len(),
|
||||
Token::Byte(_) => 3,
|
||||
Token::Character(_) => 3,
|
||||
Token::Float(text) => text.len(),
|
||||
Token::Identifier(text) => text.len(),
|
||||
Token::Integer(text) => text.len(),
|
||||
Token::String(text) => text.len() + 2,
|
||||
Token::Async => 5,
|
||||
Token::ArrowThin => 2,
|
||||
Token::Bool => 4,
|
||||
Token::Break => 5,
|
||||
Token::Else => 4,
|
||||
Token::FloatKeyword => 5,
|
||||
Token::Fn => 2,
|
||||
Token::If => 2,
|
||||
Token::Int => 3,
|
||||
Token::Let => 3,
|
||||
@ -109,25 +141,92 @@ impl<'src> Token<'src> {
|
||||
Token::Minus => 1,
|
||||
Token::MinusEqual => 2,
|
||||
Token::Percent => 1,
|
||||
Token::PercentEqual => 2,
|
||||
Token::Plus => 1,
|
||||
Token::PlusEqual => 2,
|
||||
Token::Return => 6,
|
||||
Token::RightCurlyBrace => 1,
|
||||
Token::RightParenthesis => 1,
|
||||
Token::RightSquareBrace => 1,
|
||||
Token::Semicolon => 1,
|
||||
Token::Slash => 1,
|
||||
Token::SlashEqual => 2,
|
||||
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 {
|
||||
match self {
|
||||
Token::ArrowThin => TokenOwned::ArrowThin,
|
||||
Token::Async => TokenOwned::Async,
|
||||
Token::BangEqual => TokenOwned::BangEqual,
|
||||
Token::Bang => TokenOwned::Bang,
|
||||
Token::Bool => TokenOwned::Bool,
|
||||
Token::Boolean(boolean) => TokenOwned::Boolean(boolean.to_string()),
|
||||
Token::Break => TokenOwned::Break,
|
||||
Token::Byte(byte) => TokenOwned::Byte(byte.to_string()),
|
||||
Token::Character(character) => TokenOwned::Character(*character),
|
||||
Token::Colon => TokenOwned::Colon,
|
||||
Token::Comma => TokenOwned::Comma,
|
||||
@ -141,6 +240,7 @@ impl<'src> Token<'src> {
|
||||
Token::Equal => TokenOwned::Equal,
|
||||
Token::Float(float) => TokenOwned::Float(float.to_string()),
|
||||
Token::FloatKeyword => TokenOwned::FloatKeyword,
|
||||
Token::Fn => TokenOwned::Fn,
|
||||
Token::Greater => TokenOwned::Greater,
|
||||
Token::GreaterEqual => TokenOwned::GreaterOrEqual,
|
||||
Token::Identifier(text) => TokenOwned::Identifier(text.to_string()),
|
||||
@ -159,14 +259,18 @@ impl<'src> Token<'src> {
|
||||
Token::MinusEqual => TokenOwned::MinusEqual,
|
||||
Token::Mut => TokenOwned::Mut,
|
||||
Token::Percent => TokenOwned::Percent,
|
||||
Token::PercentEqual => TokenOwned::PercentEqual,
|
||||
Token::Plus => TokenOwned::Plus,
|
||||
Token::PlusEqual => TokenOwned::PlusEqual,
|
||||
Token::Return => TokenOwned::Return,
|
||||
Token::RightCurlyBrace => TokenOwned::RightCurlyBrace,
|
||||
Token::RightParenthesis => TokenOwned::RightParenthesis,
|
||||
Token::RightSquareBrace => TokenOwned::RightSquareBrace,
|
||||
Token::Semicolon => TokenOwned::Semicolon,
|
||||
Token::Star => TokenOwned::Star,
|
||||
Token::StarEqual => TokenOwned::StarEqual,
|
||||
Token::Slash => TokenOwned::Slash,
|
||||
Token::SlashEqual => TokenOwned::SlashEqual,
|
||||
Token::String(text) => TokenOwned::String(text.to_string()),
|
||||
Token::Str => TokenOwned::Str,
|
||||
Token::Struct => TokenOwned::Struct,
|
||||
@ -176,12 +280,14 @@ impl<'src> Token<'src> {
|
||||
|
||||
pub fn kind(&self) -> TokenKind {
|
||||
match self {
|
||||
Token::ArrowThin => TokenKind::ArrowThin,
|
||||
Token::Async => TokenKind::Async,
|
||||
Token::BangEqual => TokenKind::BangEqual,
|
||||
Token::Bang => TokenKind::Bang,
|
||||
Token::Bool => TokenKind::Bool,
|
||||
Token::Boolean(_) => TokenKind::Boolean,
|
||||
Token::Break => TokenKind::Break,
|
||||
Token::Byte(_) => TokenKind::Byte,
|
||||
Token::Character(_) => TokenKind::Character,
|
||||
Token::Colon => TokenKind::Colon,
|
||||
Token::Comma => TokenKind::Comma,
|
||||
@ -195,8 +301,9 @@ impl<'src> Token<'src> {
|
||||
Token::Equal => TokenKind::Equal,
|
||||
Token::Float(_) => TokenKind::Float,
|
||||
Token::FloatKeyword => TokenKind::FloatKeyword,
|
||||
Token::Fn => TokenKind::Fn,
|
||||
Token::Greater => TokenKind::Greater,
|
||||
Token::GreaterEqual => TokenKind::GreaterOrEqual,
|
||||
Token::GreaterEqual => TokenKind::GreaterEqual,
|
||||
Token::Identifier(_) => TokenKind::Identifier,
|
||||
Token::If => TokenKind::If,
|
||||
Token::Int => TokenKind::Int,
|
||||
@ -206,21 +313,25 @@ impl<'src> Token<'src> {
|
||||
Token::LeftSquareBrace => TokenKind::LeftSquareBrace,
|
||||
Token::Let => TokenKind::Let,
|
||||
Token::Less => TokenKind::Less,
|
||||
Token::LessEqual => TokenKind::LessOrEqual,
|
||||
Token::LessEqual => TokenKind::LessEqual,
|
||||
Token::Loop => TokenKind::Loop,
|
||||
Token::Map => TokenKind::Map,
|
||||
Token::Minus => TokenKind::Minus,
|
||||
Token::MinusEqual => TokenKind::MinusEqual,
|
||||
Token::Mut => TokenKind::Mut,
|
||||
Token::Percent => TokenKind::Percent,
|
||||
Token::PercentEqual => TokenKind::PercentEqual,
|
||||
Token::Plus => TokenKind::Plus,
|
||||
Token::PlusEqual => TokenKind::PlusEqual,
|
||||
Token::Return => TokenKind::Return,
|
||||
Token::RightCurlyBrace => TokenKind::RightCurlyBrace,
|
||||
Token::RightParenthesis => TokenKind::RightParenthesis,
|
||||
Token::RightSquareBrace => TokenKind::RightSquareBrace,
|
||||
Token::Semicolon => TokenKind::Semicolon,
|
||||
Token::Star => TokenKind::Star,
|
||||
Token::StarEqual => TokenKind::StarEqual,
|
||||
Token::Slash => TokenKind::Slash,
|
||||
Token::SlashEqual => TokenKind::SlashEqual,
|
||||
Token::Str => TokenKind::Str,
|
||||
Token::String(_) => TokenKind::String,
|
||||
Token::Struct => TokenKind::Struct,
|
||||
@ -228,55 +339,45 @@ impl<'src> Token<'src> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_eof(&self) -> bool {
|
||||
matches!(self, Token::Eof)
|
||||
}
|
||||
|
||||
pub fn precedence(&self) -> u8 {
|
||||
match self {
|
||||
Token::Dot => 9,
|
||||
Token::LeftParenthesis | Token::LeftSquareBrace => 8,
|
||||
Token::Star | Token::Slash | Token::Percent => 7,
|
||||
Token::Minus | Token::Plus => 6,
|
||||
Token::DoubleEqual
|
||||
/// Returns true if the token yields a value, begins an expression or is an expression operator.
|
||||
pub fn is_expression(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Token::Boolean(_)
|
||||
| Token::Byte(_)
|
||||
| Token::Character(_)
|
||||
| Token::Float(_)
|
||||
| Token::Identifier(_)
|
||||
| Token::Integer(_)
|
||||
| Token::String(_)
|
||||
| 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::LessEqual
|
||||
| Token::Greater
|
||||
| Token::GreaterEqual => 5,
|
||||
Token::DoubleAmpersand => 4,
|
||||
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::Minus
|
||||
| Token::Star
|
||||
| Token::Slash
|
||||
| Token::MinusEqual
|
||||
| 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
|
||||
| Token::PercentEqual
|
||||
| Token::Plus
|
||||
| Token::PlusEqual
|
||||
| Token::Slash
|
||||
| Token::SlashEqual
|
||||
| Token::Star
|
||||
| Token::StarEqual
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -284,13 +385,15 @@ impl<'src> Token<'src> {
|
||||
impl<'src> Display for Token<'src> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Token::ArrowThin => write!(f, "->"),
|
||||
Token::Async => write!(f, "async"),
|
||||
Token::BangEqual => write!(f, "!="),
|
||||
Token::Bang => write!(f, "!"),
|
||||
Token::Bool => write!(f, "bool"),
|
||||
Token::Boolean(value) => write!(f, "{}", value),
|
||||
Token::Boolean(value) => write!(f, "{value}"),
|
||||
Token::Break => write!(f, "break"),
|
||||
Token::Character(value) => write!(f, "'{}'", value),
|
||||
Token::Byte(value) => write!(f, "{value}"),
|
||||
Token::Character(value) => write!(f, "{value}"),
|
||||
Token::Colon => write!(f, ":"),
|
||||
Token::Comma => write!(f, ","),
|
||||
Token::Dot => write!(f, "."),
|
||||
@ -301,14 +404,15 @@ impl<'src> Display for Token<'src> {
|
||||
Token::Else => write!(f, "else"),
|
||||
Token::Eof => write!(f, "EOF"),
|
||||
Token::Equal => write!(f, "="),
|
||||
Token::Float(value) => write!(f, "{}", value),
|
||||
Token::Float(value) => write!(f, "{value}"),
|
||||
Token::FloatKeyword => write!(f, "float"),
|
||||
Token::Fn => write!(f, "fn"),
|
||||
Token::Greater => write!(f, ">"),
|
||||
Token::GreaterEqual => write!(f, ">="),
|
||||
Token::Identifier(value) => write!(f, "{}", value),
|
||||
Token::Identifier(value) => write!(f, "{value}"),
|
||||
Token::If => write!(f, "if"),
|
||||
Token::Int => write!(f, "int"),
|
||||
Token::Integer(value) => write!(f, "{}", value),
|
||||
Token::Integer(value) => write!(f, "{value}"),
|
||||
Token::LeftCurlyBrace => write!(f, "{{"),
|
||||
Token::LeftParenthesis => write!(f, "("),
|
||||
Token::LeftSquareBrace => write!(f, "["),
|
||||
@ -321,26 +425,30 @@ impl<'src> Display for Token<'src> {
|
||||
Token::MinusEqual => write!(f, "-="),
|
||||
Token::Mut => write!(f, "mut"),
|
||||
Token::Percent => write!(f, "%"),
|
||||
Token::PercentEqual => write!(f, "%="),
|
||||
Token::Plus => write!(f, "+"),
|
||||
Token::PlusEqual => write!(f, "+="),
|
||||
Token::Return => write!(f, "return"),
|
||||
Token::RightCurlyBrace => write!(f, "}}"),
|
||||
Token::RightParenthesis => write!(f, ")"),
|
||||
Token::RightSquareBrace => write!(f, "]"),
|
||||
Token::Semicolon => write!(f, ";"),
|
||||
Token::Slash => write!(f, "/"),
|
||||
Token::SlashEqual => write!(f, "/="),
|
||||
Token::Star => write!(f, "*"),
|
||||
Token::StarEqual => write!(f, "*="),
|
||||
Token::Str => write!(f, "str"),
|
||||
Token::String(value) => write!(f, "\"{}\"", value),
|
||||
Token::String(value) => write!(f, "{value}"),
|
||||
Token::Struct => write!(f, "struct"),
|
||||
Token::While => write!(f, "while"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Owned version of `Token`, which owns all the strings.
|
||||
/// Owned representation of a source token.
|
||||
///
|
||||
/// This is used for errors.
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
/// If a [Token] borrows from the source text, its TokenOwned omits the data.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum TokenOwned {
|
||||
Eof,
|
||||
|
||||
@ -348,27 +456,31 @@ pub enum TokenOwned {
|
||||
|
||||
// Hard-coded values
|
||||
Boolean(String),
|
||||
Byte(String),
|
||||
Character(char),
|
||||
Float(String),
|
||||
Integer(String),
|
||||
String(String),
|
||||
|
||||
// Keywords
|
||||
Async,
|
||||
Bool,
|
||||
Break,
|
||||
Else,
|
||||
FloatKeyword,
|
||||
Fn,
|
||||
If,
|
||||
Int,
|
||||
Let,
|
||||
Loop,
|
||||
Map,
|
||||
Mut,
|
||||
Return,
|
||||
Str,
|
||||
While,
|
||||
|
||||
// Symbols
|
||||
Async,
|
||||
ArrowThin,
|
||||
Bang,
|
||||
BangEqual,
|
||||
Colon,
|
||||
@ -389,6 +501,7 @@ pub enum TokenOwned {
|
||||
Minus,
|
||||
MinusEqual,
|
||||
Percent,
|
||||
PercentEqual,
|
||||
Plus,
|
||||
PlusEqual,
|
||||
RightCurlyBrace,
|
||||
@ -396,19 +509,23 @@ pub enum TokenOwned {
|
||||
RightSquareBrace,
|
||||
Semicolon,
|
||||
Star,
|
||||
StarEqual,
|
||||
Struct,
|
||||
Slash,
|
||||
SlashEqual,
|
||||
}
|
||||
|
||||
impl Display for TokenOwned {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
TokenOwned::ArrowThin => Token::ArrowThin.fmt(f),
|
||||
TokenOwned::Async => Token::Async.fmt(f),
|
||||
TokenOwned::Bang => Token::Bang.fmt(f),
|
||||
TokenOwned::BangEqual => Token::BangEqual.fmt(f),
|
||||
TokenOwned::Bool => Token::Bool.fmt(f),
|
||||
TokenOwned::Boolean(boolean) => Token::Boolean(boolean).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::Colon => Token::Colon.fmt(f),
|
||||
TokenOwned::Comma => Token::Comma.fmt(f),
|
||||
@ -422,6 +539,7 @@ impl Display for TokenOwned {
|
||||
TokenOwned::Equal => Token::Equal.fmt(f),
|
||||
TokenOwned::Float(float) => Token::Float(float).fmt(f),
|
||||
TokenOwned::FloatKeyword => Token::FloatKeyword.fmt(f),
|
||||
TokenOwned::Fn => Token::Fn.fmt(f),
|
||||
TokenOwned::Greater => Token::Greater.fmt(f),
|
||||
TokenOwned::GreaterOrEqual => Token::GreaterEqual.fmt(f),
|
||||
TokenOwned::Identifier(text) => Token::Identifier(text).fmt(f),
|
||||
@ -440,14 +558,18 @@ impl Display for TokenOwned {
|
||||
TokenOwned::MinusEqual => Token::MinusEqual.fmt(f),
|
||||
TokenOwned::Mut => Token::Mut.fmt(f),
|
||||
TokenOwned::Percent => Token::Percent.fmt(f),
|
||||
TokenOwned::PercentEqual => Token::PercentEqual.fmt(f),
|
||||
TokenOwned::Plus => Token::Plus.fmt(f),
|
||||
TokenOwned::PlusEqual => Token::PlusEqual.fmt(f),
|
||||
TokenOwned::Return => Token::Return.fmt(f),
|
||||
TokenOwned::RightCurlyBrace => Token::RightCurlyBrace.fmt(f),
|
||||
TokenOwned::RightParenthesis => Token::RightParenthesis.fmt(f),
|
||||
TokenOwned::RightSquareBrace => Token::RightSquareBrace.fmt(f),
|
||||
TokenOwned::Semicolon => Token::Semicolon.fmt(f),
|
||||
TokenOwned::Star => Token::Star.fmt(f),
|
||||
TokenOwned::StarEqual => Token::StarEqual.fmt(f),
|
||||
TokenOwned::Slash => Token::Slash.fmt(f),
|
||||
TokenOwned::SlashEqual => Token::SlashEqual.fmt(f),
|
||||
TokenOwned::Str => Token::Str.fmt(f),
|
||||
TokenOwned::String(string) => Token::String(string).fmt(f),
|
||||
TokenOwned::Struct => Token::Struct.fmt(f),
|
||||
@ -456,76 +578,17 @@ 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 {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
TokenKind::ArrowThin => Token::ArrowThin.fmt(f),
|
||||
TokenKind::Async => Token::Async.fmt(f),
|
||||
TokenKind::Bang => Token::Bang.fmt(f),
|
||||
TokenKind::BangEqual => Token::BangEqual.fmt(f),
|
||||
TokenKind::Bool => Token::Bool.fmt(f),
|
||||
TokenKind::Boolean => write!(f, "boolean value"),
|
||||
TokenKind::Break => Token::Break.fmt(f),
|
||||
TokenKind::Byte => write!(f, "byte value"),
|
||||
TokenKind::Character => write!(f, "character value"),
|
||||
TokenKind::Colon => Token::Colon.fmt(f),
|
||||
TokenKind::Comma => Token::Comma.fmt(f),
|
||||
@ -539,8 +602,9 @@ impl Display for TokenKind {
|
||||
TokenKind::Equal => Token::Equal.fmt(f),
|
||||
TokenKind::Float => write!(f, "float value"),
|
||||
TokenKind::FloatKeyword => Token::FloatKeyword.fmt(f),
|
||||
TokenKind::Fn => Token::Fn.fmt(f),
|
||||
TokenKind::Greater => Token::Greater.fmt(f),
|
||||
TokenKind::GreaterOrEqual => Token::GreaterEqual.fmt(f),
|
||||
TokenKind::GreaterEqual => Token::GreaterEqual.fmt(f),
|
||||
TokenKind::Identifier => write!(f, "identifier"),
|
||||
TokenKind::If => Token::If.fmt(f),
|
||||
TokenKind::Int => Token::Int.fmt(f),
|
||||
@ -550,102 +614,29 @@ impl Display for TokenKind {
|
||||
TokenKind::LeftSquareBrace => Token::LeftSquareBrace.fmt(f),
|
||||
TokenKind::Let => Token::Let.fmt(f),
|
||||
TokenKind::Less => Token::Less.fmt(f),
|
||||
TokenKind::LessOrEqual => Token::LessEqual.fmt(f),
|
||||
TokenKind::LessEqual => Token::LessEqual.fmt(f),
|
||||
TokenKind::Loop => Token::Loop.fmt(f),
|
||||
TokenKind::Map => Token::Map.fmt(f),
|
||||
TokenKind::Minus => Token::Minus.fmt(f),
|
||||
TokenKind::MinusEqual => Token::MinusEqual.fmt(f),
|
||||
TokenKind::Mut => Token::Mut.fmt(f),
|
||||
TokenKind::Percent => Token::Percent.fmt(f),
|
||||
TokenKind::PercentEqual => Token::PercentEqual.fmt(f),
|
||||
TokenKind::Plus => Token::Plus.fmt(f),
|
||||
TokenKind::PlusEqual => Token::PlusEqual.fmt(f),
|
||||
TokenKind::Return => Token::Return.fmt(f),
|
||||
TokenKind::RightCurlyBrace => Token::RightCurlyBrace.fmt(f),
|
||||
TokenKind::RightParenthesis => Token::RightParenthesis.fmt(f),
|
||||
TokenKind::RightSquareBrace => Token::RightSquareBrace.fmt(f),
|
||||
TokenKind::Semicolon => Token::Semicolon.fmt(f),
|
||||
TokenKind::Star => Token::Star.fmt(f),
|
||||
TokenKind::StarEqual => Token::StarEqual.fmt(f),
|
||||
TokenKind::Str => Token::Str.fmt(f),
|
||||
TokenKind::Slash => Token::Slash.fmt(f),
|
||||
TokenKind::SlashEqual => Token::SlashEqual.fmt(f),
|
||||
TokenKind::String => write!(f, "string value"),
|
||||
TokenKind::Struct => Token::Struct.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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,4 @@
|
||||
//! 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))`.
|
||||
//! Value types and conflict handling.
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::HashMap,
|
||||
@ -17,11 +7,7 @@ use std::{
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Constructor, BuiltInFunction, Identifier};
|
||||
|
||||
/// Description of a kind of value.
|
||||
///
|
||||
/// See the [module documentation](index.html) for more information.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Type {
|
||||
Any,
|
||||
@ -32,7 +18,7 @@ pub enum Type {
|
||||
Float,
|
||||
Function(FunctionType),
|
||||
Generic {
|
||||
identifier: Identifier,
|
||||
identifier_index: u8,
|
||||
concrete_type: Option<Box<Type>>,
|
||||
},
|
||||
Integer,
|
||||
@ -45,7 +31,7 @@ pub enum Type {
|
||||
item_type: Box<Type>,
|
||||
},
|
||||
Map {
|
||||
pairs: HashMap<Identifier, Type>,
|
||||
pairs: HashMap<u8, Type>,
|
||||
},
|
||||
Number,
|
||||
Range {
|
||||
@ -191,20 +177,17 @@ impl Type {
|
||||
}
|
||||
(
|
||||
Type::Function(FunctionType {
|
||||
name: left_name,
|
||||
type_parameters: left_type_parameters,
|
||||
value_parameters: left_value_parameters,
|
||||
return_type: left_return,
|
||||
}),
|
||||
Type::Function(FunctionType {
|
||||
name: right_name,
|
||||
type_parameters: right_type_parameters,
|
||||
value_parameters: right_value_parameters,
|
||||
return_type: right_return,
|
||||
}),
|
||||
) => {
|
||||
if left_name != right_name
|
||||
|| left_return != right_return
|
||||
if left_return != right_return
|
||||
|| left_type_parameters != right_type_parameters
|
||||
|| left_value_parameters != right_value_parameters
|
||||
{
|
||||
@ -233,48 +216,6 @@ impl Type {
|
||||
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 {
|
||||
@ -289,7 +230,10 @@ impl Display for Type {
|
||||
Type::Function(function_type) => write!(f, "{function_type}"),
|
||||
Type::Generic { concrete_type, .. } => {
|
||||
match concrete_type.clone().map(|r#box| *r#box) {
|
||||
Some(Type::Generic { identifier, .. }) => write!(f, "{identifier}"),
|
||||
Some(Type::Generic {
|
||||
identifier_index: identifier,
|
||||
..
|
||||
}) => write!(f, "{identifier}"),
|
||||
Some(concrete_type) => write!(f, "implied to be {concrete_type}"),
|
||||
None => write!(f, "unknown"),
|
||||
}
|
||||
@ -420,9 +364,8 @@ impl Ord for Type {
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct FunctionType {
|
||||
pub name: Identifier,
|
||||
pub type_parameters: Option<Vec<Identifier>>,
|
||||
pub value_parameters: Option<Vec<(Identifier, Type)>>,
|
||||
pub type_parameters: Option<Vec<u8>>,
|
||||
pub value_parameters: Option<Vec<(u8, Type)>>,
|
||||
pub return_type: Option<Box<Type>>,
|
||||
}
|
||||
|
||||
@ -434,11 +377,11 @@ impl Display for FunctionType {
|
||||
write!(f, "<")?;
|
||||
|
||||
for (index, type_parameter) in type_parameters.iter().enumerate() {
|
||||
write!(f, "{type_parameter}")?;
|
||||
|
||||
if index != type_parameters.len() - 1 {
|
||||
if index > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
|
||||
write!(f, "{type_parameter}")?;
|
||||
}
|
||||
|
||||
write!(f, ">")?;
|
||||
@ -448,11 +391,11 @@ impl Display for FunctionType {
|
||||
|
||||
if let Some(value_parameters) = &self.value_parameters {
|
||||
for (index, (identifier, r#type)) in value_parameters.iter().enumerate() {
|
||||
write!(f, "{identifier}: {type}")?;
|
||||
|
||||
if index != value_parameters.len() - 1 {
|
||||
if index > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
|
||||
write!(f, "{identifier}: {type}")?;
|
||||
}
|
||||
}
|
||||
|
||||
@ -468,31 +411,17 @@ impl Display for FunctionType {
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum StructType {
|
||||
Unit {
|
||||
name: Identifier,
|
||||
},
|
||||
Tuple {
|
||||
name: Identifier,
|
||||
fields: Vec<Type>,
|
||||
},
|
||||
Fields {
|
||||
name: Identifier,
|
||||
fields: HashMap<Identifier, Type>,
|
||||
},
|
||||
Unit { name: u8 },
|
||||
Tuple { name: u8, fields: Vec<Type> },
|
||||
Fields { name: u8, fields: HashMap<u8, Type> },
|
||||
}
|
||||
|
||||
impl StructType {
|
||||
pub fn name(&self) -> &Identifier {
|
||||
pub fn name(&self) -> u8 {
|
||||
match self {
|
||||
StructType::Unit { name } => name,
|
||||
StructType::Tuple { name, .. } => name,
|
||||
StructType::Fields { name, .. } => name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn constructor(&self) -> Constructor {
|
||||
Constructor {
|
||||
struct_type: self.clone(),
|
||||
StructType::Unit { name } => *name,
|
||||
StructType::Tuple { name, .. } => *name,
|
||||
StructType::Fields { name, .. } => *name,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -594,7 +523,7 @@ impl Ord for StructType {
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct EnumType {
|
||||
pub name: Identifier,
|
||||
pub name: u8,
|
||||
pub variants: Vec<StructType>,
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
2415
dust-lang/src/vm.rs
2415
dust-lang/src/vm.rs
File diff suppressed because it is too large
Load Diff
66
dust-lang/tests/basic.rs
Normal file
66
dust-lang/tests/basic.rs
Normal file
@ -0,0 +1,66 @@
|
||||
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))));
|
||||
}
|
169
dust-lang/tests/comparison.rs
Normal file
169
dust-lang/tests/comparison.rs
Normal file
@ -0,0 +1,169 @@
|
||||
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))));
|
||||
}
|
420
dust-lang/tests/control_flow.rs
Normal file
420
dust-lang/tests/control_flow.rs
Normal file
@ -0,0 +1,420 @@
|
||||
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));
|
||||
}
|
126
dust-lang/tests/functions.rs
Normal file
126
dust-lang/tests/functions.rs
Normal file
@ -0,0 +1,126 @@
|
||||
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));
|
||||
}
|
127
dust-lang/tests/lists.rs
Normal file
127
dust-lang/tests/lists.rs
Normal file
@ -0,0 +1,127 @@
|
||||
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)))
|
||||
);
|
||||
}
|
77
dust-lang/tests/logic.rs
Normal file
77
dust-lang/tests/logic.rs
Normal file
@ -0,0 +1,77 @@
|
||||
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))));
|
||||
}
|
35
dust-lang/tests/loops.rs
Normal file
35
dust-lang/tests/loops.rs
Normal file
@ -0,0 +1,35 @@
|
||||
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))));
|
||||
}
|
324
dust-lang/tests/math.rs
Normal file
324
dust-lang/tests/math.rs
Normal file
@ -0,0 +1,324 @@
|
||||
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
|
||||
})
|
||||
);
|
||||
}
|
59
dust-lang/tests/native_functions.rs
Normal file
59
dust-lang/tests/native_functions.rs
Normal file
@ -0,0 +1,59 @@
|
||||
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"))))
|
||||
}
|
@ -1,47 +1,244 @@
|
||||
use dust_lang::*;
|
||||
|
||||
#[test]
|
||||
fn block_scope_captures_parent() {
|
||||
let source = "let x = 42; { x }";
|
||||
fn allow_access_to_parent_scope() {
|
||||
let source = r#"
|
||||
let x = 1;
|
||||
{
|
||||
x
|
||||
}
|
||||
"#;
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(42))));
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(1))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_scope_does_not_capture_child() {
|
||||
env_logger::builder().is_test(true).try_init().unwrap();
|
||||
fn block_scope() {
|
||||
let source = "
|
||||
let a = 0;
|
||||
{
|
||||
let b = 42;
|
||||
{
|
||||
let c = 1;
|
||||
}
|
||||
let d = 2;
|
||||
}
|
||||
let e = 1;
|
||||
";
|
||||
|
||||
let source = "{ let x = 42; } x";
|
||||
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, 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!(
|
||||
run(source),
|
||||
Err(DustError::analysis(
|
||||
[AnalysisError::UndefinedVariable {
|
||||
identifier: Node::new(Identifier::new("x"), (16, 17))
|
||||
}],
|
||||
Err(DustError::Compile {
|
||||
error: CompileError::VariableOutOfScope {
|
||||
identifier: "x".to_string(),
|
||||
position: Span(52, 53),
|
||||
variable_scope: Scope::new(1, 1),
|
||||
access_scope: Scope::new(0, 0),
|
||||
},
|
||||
source
|
||||
))
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_scope_does_not_capture_sibling() {
|
||||
let source = "{ let x = 42; } { x }";
|
||||
fn disallow_access_to_child_scope_nested() {
|
||||
let source = r#"
|
||||
{
|
||||
{
|
||||
let x = 1;
|
||||
}
|
||||
x
|
||||
}
|
||||
"#;
|
||||
|
||||
assert_eq!(
|
||||
run(source),
|
||||
Err(DustError::analysis(
|
||||
[AnalysisError::UndefinedVariable {
|
||||
identifier: Node::new(Identifier::new("x"), (18, 19))
|
||||
}],
|
||||
Err(DustError::Compile {
|
||||
error: CompileError::VariableOutOfScope {
|
||||
identifier: "x".to_string(),
|
||||
position: Span(78, 79),
|
||||
variable_scope: Scope::new(2, 2),
|
||||
access_scope: Scope::new(1, 1),
|
||||
},
|
||||
source
|
||||
))
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_scope_does_not_pollute_parent() {
|
||||
let source = "let x = 42; { let x = \"foo\"; let x = \"bar\"; } x";
|
||||
fn disallow_access_to_sibling_scope() {
|
||||
let source = r#"
|
||||
{
|
||||
let x = 1;
|
||||
}
|
||||
{
|
||||
x
|
||||
}
|
||||
"#;
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(42))));
|
||||
assert_eq!(
|
||||
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
|
||||
})
|
||||
);
|
||||
}
|
||||
|
42
dust-lang/tests/unary_operations.rs
Normal file
42
dust-lang/tests/unary_operations.rs
Normal file
@ -0,0 +1,42 @@
|
||||
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))));
|
||||
}
|
63
dust-lang/tests/variables.rs
Normal file
63
dust-lang/tests/variables.rs
Normal file
@ -0,0 +1,63 @@
|
||||
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))));
|
||||
}
|
@ -9,5 +9,7 @@ repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.14", features = ["derive"] }
|
||||
colored = "2.1.0"
|
||||
dust-lang = { path = "../dust-lang" }
|
||||
env_logger = "0.11.5"
|
||||
log = "0.4.22"
|
||||
|
@ -1,55 +1,138 @@
|
||||
use std::fs::read_to_string;
|
||||
use std::{fs::read_to_string, io::Write};
|
||||
|
||||
use clap::Parser;
|
||||
use dust_lang::run;
|
||||
use colored::Colorize;
|
||||
use dust_lang::{compile, format, run};
|
||||
use log::{Level, LevelFilter};
|
||||
|
||||
#[derive(Parser)]
|
||||
struct Cli {
|
||||
/// Source code sent via command line
|
||||
#[arg(short, long)]
|
||||
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)]
|
||||
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>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let args = Cli::parse();
|
||||
let mut logger = env_logger::builder();
|
||||
|
||||
if let Some(command) = &args.command {
|
||||
if args.parse {
|
||||
parse_and_display_errors(command);
|
||||
logger.format(|buf, record| {
|
||||
let level_display = match record.level() {
|
||||
Level::Info => "INFO".bold().white(),
|
||||
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 {
|
||||
run_and_display_errors(command);
|
||||
logger.parse_env("DUST_LOG").init();
|
||||
}
|
||||
|
||||
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 {
|
||||
parse_and_display_errors(&source);
|
||||
} else {
|
||||
run_and_display_errors(&source);
|
||||
}
|
||||
}
|
||||
}
|
||||
let styled = args.style_disassembly.unwrap_or(true);
|
||||
|
||||
fn parse_and_display_errors(source: &str) {
|
||||
match dust_lang::parse(source) {
|
||||
Ok(ast) => println!("{:#?}", ast),
|
||||
Err(error) => eprintln!("{}", error.report()),
|
||||
}
|
||||
}
|
||||
log::info!("Parsing 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 {
|
||||
return;
|
||||
}
|
||||
|
||||
fn run_and_display_errors(source: &str) {
|
||||
match run(source) {
|
||||
Ok(return_value) => {
|
||||
if let Some(value) = return_value {
|
||||
println!("{}", value);
|
||||
Ok(Some(value)) => println!("{}", value),
|
||||
Ok(None) => {}
|
||||
Err(error) => {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
5
examples/assets/count.js
Normal file
5
examples/assets/count.js
Normal file
@ -0,0 +1,5 @@
|
||||
var i = 0;
|
||||
|
||||
while (i < 10000) {
|
||||
i++;
|
||||
}
|
11
examples/assets/fibonacci.js
Normal file
11
examples/assets/fibonacci.js
Normal file
@ -0,0 +1,11 @@
|
||||
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));
|
5
examples/count.ds
Normal file
5
examples/count.ds
Normal file
@ -0,0 +1,5 @@
|
||||
let mut i = 0;
|
||||
|
||||
while i < 10000 {
|
||||
i += 1;
|
||||
}
|
8
examples/fibonacci.ds
Normal file
8
examples/fibonacci.ds
Normal file
@ -0,0 +1,8 @@
|
||||
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))
|
@ -11,7 +11,7 @@ while count <= 15 {
|
||||
} else if divides_by_5 {
|
||||
"buzz"
|
||||
} else {
|
||||
count.to_string()
|
||||
to_string(count)
|
||||
}
|
||||
|
||||
write_line(output)
|
||||
|
Loading…
Reference in New Issue
Block a user