Merge branch 'dev'
This commit is contained in:
commit
0d39d91cc7
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1,3 @@
|
||||
flamegraph.svg
|
||||
perf.data*
|
||||
target/
|
||||
|
541
Cargo.lock
generated
541
Cargo.lock
generated
@ -1,6 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
@ -79,12 +79,35 @@ dependencies = [
|
||||
"critical-section",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.16.0"
|
||||
@ -97,12 +120,29 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.20"
|
||||
@ -123,6 +163,7 @@ dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
"terminal_size",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -149,6 +190,27 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15"
|
||||
|
||||
[[package]]
|
||||
name = "color-print"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3aa954171903797d5623e047d9ab69d91b493657917bdfb8c2c80ecaf9cdb6f4"
|
||||
dependencies = [
|
||||
"color-print-proc-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color-print-proc-macro"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "692186b5ebe54007e45a59aea47ece9eb4108e141326c304cdc91699a7118a22"
|
||||
dependencies = [
|
||||
"nom",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.3"
|
||||
@ -165,6 +227,42 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"cast",
|
||||
"clap 2.34.0",
|
||||
"criterion-plot",
|
||||
"csv",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"num-traits",
|
||||
"oorandom",
|
||||
"plotters",
|
||||
"rayon",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_cbor",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"tinytemplate",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "critical-section"
|
||||
version = "1.2.0"
|
||||
@ -172,16 +270,62 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
||||
|
||||
[[package]]
|
||||
name = "dust-cli"
|
||||
version = "0.1.0"
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"colored",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||
|
||||
[[package]]
|
||||
name = "csv"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf"
|
||||
dependencies = [
|
||||
"csv-core",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv-core"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dust-cli"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"clap 4.5.20",
|
||||
"color-print",
|
||||
"dust-lang",
|
||||
"env_logger",
|
||||
"log",
|
||||
"postcard",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -190,35 +334,29 @@ version = "0.5.0"
|
||||
dependencies = [
|
||||
"annotate-snippets",
|
||||
"colored",
|
||||
"env_logger",
|
||||
"criterion",
|
||||
"getrandom",
|
||||
"log",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smartstring",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "0.1.2"
|
||||
name = "either"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab"
|
||||
dependencies = [
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.5"
|
||||
name = "errno"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d"
|
||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"env_filter",
|
||||
"humantime",
|
||||
"log",
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -234,6 +372,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "1.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403"
|
||||
|
||||
[[package]]
|
||||
name = "hash32"
|
||||
version = "0.2.1"
|
||||
@ -264,10 +408,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
@ -275,6 +422,15 @@ version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.11"
|
||||
@ -302,6 +458,12 @@ version = "0.2.161"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.12"
|
||||
@ -324,12 +486,93 @@ version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||
dependencies = [
|
||||
"overload",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
version = "11.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9"
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"plotters-backend",
|
||||
"plotters-svg",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotters-backend"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
|
||||
|
||||
[[package]]
|
||||
name = "plotters-svg"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
|
||||
dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "postcard"
|
||||
version = "1.0.10"
|
||||
@ -398,6 +641,26 @@ dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
|
||||
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.11.1"
|
||||
@ -436,12 +699,34 @@ dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
@ -463,6 +748,16 @@ dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_cbor"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
|
||||
dependencies = [
|
||||
"half",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.214"
|
||||
@ -486,6 +781,33 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "smartstring"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
@ -501,6 +823,12 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
@ -518,6 +846,102 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinytemplate"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.13"
|
||||
@ -536,6 +960,28 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
@ -597,6 +1043,47 @@ version = "0.2.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
|
16
Cargo.toml
16
Cargo.toml
@ -5,12 +5,24 @@ resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
authors = ["Jeff Anderson"]
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
edition = "2024"
|
||||
license = "GPL-3.0"
|
||||
readme = "README.md"
|
||||
repository = "https://git.jeffa.io/jeff/dust.git"
|
||||
version = "0.5.0"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = "fat"
|
||||
opt-level = 3
|
||||
panic = "abort"
|
||||
strip = true
|
||||
|
||||
[profile.perf]
|
||||
inherits = "release"
|
||||
strip = false
|
||||
|
674
LICENSE
Normal file
674
LICENSE
Normal file
@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
384
README.md
384
README.md
@ -1,13 +1,24 @@
|
||||
# Dust
|
||||
# The Dust Programming Language
|
||||
|
||||
Dust is a high-level interpreted programming language with static types that focuses on ease of use,
|
||||
performance and correctness. The syntax, safety features and evaluation model are inspired by Rust.
|
||||
The instruction set, optimization strategies and virtual machine are inspired by Lua. Unlike Rust
|
||||
and other compiled languages, Dust has a very low time to execution. Simple programs compile in
|
||||
under a millisecond on a modern processor. Unlike Lua and most other interpreted languages, Dust is
|
||||
type-safe, with a simple yet powerful type system that enhances clarity and prevent bugs.
|
||||
A **fast**, **safe** and **easy to use** language for general-purpose programming.
|
||||
|
||||
```dust
|
||||
Dust is **statically typed** to ensure that each program is valid before it is run. Compiling is
|
||||
fast due to the purpose-built lexer and parser. Execution is fast because Dust uses a custom
|
||||
bytecode that runs in a multi-threaded VM. Dust combines compile-time safety guarantees and
|
||||
optimizations with negligible compile times and satisfying runtime speed to deliver a unique set of
|
||||
features. It offers the best qualities of two disparate categories of programming language: the
|
||||
highly optimized but slow-to-compile languages like Rust and C++ and the quick-to-start but often
|
||||
slow and error-prone languages like Python and JavaScript.
|
||||
|
||||
Dust's syntax, safety features and evaluation model are based on Rust. Its instruction set,
|
||||
optimization strategies and virtual machine are based on Lua. Unlike Rust and other languages that
|
||||
compile to machine code, Dust has a very low time to execution. Unlike Lua and most other
|
||||
interpreted languages, Dust enforces static typing to improve clarity and prevent bugs.
|
||||
|
||||
**Dust is under active development and is not yet ready for general use.**
|
||||
|
||||
```rust
|
||||
// "Hello, world" using Dust's built-in I/O functions
|
||||
write_line("Enter your name...")
|
||||
|
||||
let name = read_line()
|
||||
@ -15,165 +26,219 @@ let name = read_line()
|
||||
write_line("Hello " + name + "!")
|
||||
```
|
||||
|
||||
## Feature Progress
|
||||
```rust
|
||||
// The classic, unoptimized Fibonacci sequence
|
||||
fn fib (n: int) -> int {
|
||||
if n <= 0 { return 0 }
|
||||
if n == 1 { return 1 }
|
||||
|
||||
Dust is still in development. This list may change as the language evolves.
|
||||
fib(n - 1) + fib(n - 2)
|
||||
}
|
||||
|
||||
- [X] Lexer
|
||||
- [X] Compiler
|
||||
- [X] VM
|
||||
- [ ] Formatter
|
||||
- [X] Disassembler (for chunk debugging)
|
||||
- CLI
|
||||
- [X] Run source
|
||||
- [X] Compile to chunk and show disassembly
|
||||
- [ ] Tokenize using the lexer and show token list
|
||||
- [ ] Format using the formatter and display the output
|
||||
- [ ] Compile to and run from intermediate formats
|
||||
- [ ] JSON
|
||||
- [ ] Postcard
|
||||
- Basic Values
|
||||
- [X] No `null` or `undefined` values
|
||||
- [X] Booleans
|
||||
- [X] Bytes (unsigned 8-bit)
|
||||
- [X] Characters (Unicode scalar value)
|
||||
- [X] Floats (64-bit)
|
||||
- [X] Functions
|
||||
- [X] Integers (signed 64-bit)
|
||||
- [ ] Ranges
|
||||
- [X] Strings (UTF-8)
|
||||
- Composite Values
|
||||
- [X] Concrete lists
|
||||
- [X] Abstract lists (optimization)
|
||||
- [ ] Concrete maps
|
||||
- [ ] Abstract maps (optimization)
|
||||
- [ ] Tuples (fixed-size constant lists)
|
||||
- [ ] Structs
|
||||
- [ ] Enums
|
||||
- Types
|
||||
- [X] Basic types for each kind of basic value
|
||||
- [X] Generalized types: `num`, `any`, `none`
|
||||
- [ ] `struct` types
|
||||
- [ ] `enum` types
|
||||
- [ ] Type aliases
|
||||
- [ ] Type arguments
|
||||
- [ ] Compile-time type checking
|
||||
- [ ] Function returns
|
||||
- [X] If/Else branches
|
||||
- [ ] Instruction arguments
|
||||
- Variables
|
||||
- [X] Immutable by default
|
||||
- [X] Block scope
|
||||
- [X] Statically typed
|
||||
- [X] Copy-free identifiers are stored in the chunk as string constants
|
||||
- Functions
|
||||
- [X] First-class value
|
||||
- [X] Statically typed arguments and returns
|
||||
- [X] Pure (no "closure" of local variables, arguments are the only input)
|
||||
- [ ] Type arguments
|
||||
- Control Flow
|
||||
- [X] If/Else
|
||||
- [ ] Loops
|
||||
- [ ] `for`
|
||||
- [ ] `loop`
|
||||
- [X] `while`
|
||||
- [ ] Match
|
||||
write_line(fib(25))
|
||||
```
|
||||
|
||||
## Implementation
|
||||
## Goals
|
||||
|
||||
Dust is implemented in Rust and is divided into several parts, most importantly 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 code is tested by integration tests that compile source
|
||||
code and check the compiled chunk, then run the source and check the output of the virtual machine.
|
||||
It is important to maintain a high level of quality by writing meaningful tests and preferring to
|
||||
compile and run programs in an optimal way before adding new features.
|
||||
This project's goal is to deliver a language with features that stand out due to a combination of
|
||||
design choices and a high-quality implementation. As mentioned in the first sentence, Dust's general
|
||||
aspirations are to be **fast**, **safe** and **easy**.
|
||||
|
||||
### Lexer and Tokens
|
||||
- **Fast**
|
||||
- **Fast Compilation** Despite its compile-time abstractions, Dust should compile and start
|
||||
executing quickly. The compilation time should feel negligible to the user.
|
||||
- **Fast Execution** Dust should be competitive with highly optimized, modern, register-based VM
|
||||
languages like Lua. Dust should be bench tested during development to inform decisions about
|
||||
performance.
|
||||
- **Low Resource Usage** Memory and CPU power should be used conservatively and predictably.
|
||||
- **Safe**
|
||||
- **Static Types** Typing should prevent runtime errors and improve code quality, offering a
|
||||
superior development experience despite some additional constraints. Like any good statically
|
||||
typed language, users should feel confident in the type-consistency of their code and not want
|
||||
to go back to a dynamically typed language.
|
||||
- **Memory Safety** Dust should be free of memory bugs. Being implemented in Rust makes this easy
|
||||
but, to accommodate long-running programs, Dust still requires a memory management strategy.
|
||||
Dust's design is to use a separate thread for garbage collection, allowing the main thread to
|
||||
continue executing code while the garbage collector looks for unused memory.
|
||||
- **Easy**
|
||||
- **Simple Syntax** Dust should be easier to learn than most programming languages. Its syntax
|
||||
should be familiar to users of other C-like languages to the point that even a new user can read
|
||||
Dust code and understand what it does. Rather than being held back by a lack of features, Dust
|
||||
should be powerful and elegant in its simplicity, seeking a maximum of capability with a minimum
|
||||
of complexity.
|
||||
- **Excellent Errors** Dust should provide helpful error messages that guide the user to the
|
||||
source of the problem and suggest a solution. Errors should be a helpful learning resource for
|
||||
users rather than a source of frustration.
|
||||
- **Relevant Documentation** Users should have the resources they need to learn Dust and write
|
||||
code in it. They should know where to look for answers and how to reach out for help.
|
||||
|
||||
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. In a successfully executed program, no part of the source code is copied unless it is a
|
||||
string literal or identifier.
|
||||
## Language Overview
|
||||
|
||||
### Compiler
|
||||
This is a quick overview of Dust's syntax features. It skips over the aspects that are familiar to
|
||||
most programmers such as creating variables, using binary operators and printing to the console.
|
||||
Eventually there should be a complete reference for the syntax.
|
||||
|
||||
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.
|
||||
### Syntax and Evaluation
|
||||
|
||||
#### Parsing
|
||||
Dust belongs to the C-like family of languages[^5], with an imperative syntax that will be familiar
|
||||
to many programmers. Dust code looks a lot like Ruby, JavaScript, TypeScript and other members of
|
||||
the family but Rust is its primary point of reference for syntax. Rust was chosen as a syntax model
|
||||
because its imperative code is *obvious by design* and *widely familiar*. Those qualities are
|
||||
aligned with Dust's emphasis on usability.
|
||||
|
||||
Dust's compiler uses a custom Pratt parser, a kind of recursive descent parser, to translate a
|
||||
sequence of tokens into a chunk. Each token is given a precedence and may have a prefix and/or infix
|
||||
parser. The parsers are just functions that modify the compiler and its output. For example, when
|
||||
the compiler encounters a boolean token, its prefix parser is the `parse_boolean` function, which
|
||||
emits a `LoadBoolean` instruction. An integer token's prefix parser is `parse_integer`, which emits
|
||||
a `LoadConstant` instruction and adds the integer to the constant list. Tokens with infix parsers
|
||||
include the math operators, which emit `Add`, `Subtract`, `Multiply`, `Divide`, and `Modulo`
|
||||
instructions.
|
||||
However, some differences exist. Dust *evaluates* all the code in the file while Rust only initiates
|
||||
from a "main" function. Dust's execution model is more like one found in a scripting language. If we
|
||||
put `42 + 42 == 84` into a file and run it, it will return `true` because the outer context is, in a
|
||||
sense, the "main" function.
|
||||
|
||||
Functions are compiled into their own chunks, which are stored in the constant list. A function's
|
||||
arguments are stored in the locals list. The VM must later bind the arguments to runtime values by
|
||||
assigning each argument a register and associating the register with the local.
|
||||
So while the syntax is by no means compatible, it is superficially similar, even to the point that
|
||||
syntax highlighting for Rust code works well with Dust code. This is not a design goal but a happy
|
||||
coincidence.
|
||||
|
||||
#### Optimizing
|
||||
### Statements and Expressions
|
||||
|
||||
When generating instructions for a register-based virtual machine, there are opportunities to
|
||||
optimize the generated code by using fewer instructions or fewer registers. While it is best to
|
||||
output optimal code in the first place, it is not always possible. Dust's compiler uses simple
|
||||
functions that modify isolated sections of the instruction list through a mutable reference.
|
||||
Dust is composed of statements and expressions. If a statement ends in an expression without a
|
||||
trailing semicolon, the statement evaluates to the value produced by that expression. However, if
|
||||
the expression's value is suppressed with a semicolon, the statement does not evaluate to a value.
|
||||
This is identical to Rust's evaluation model. That means that the following code will not compile:
|
||||
|
||||
#### Type Checking
|
||||
```rust
|
||||
// !!! Compile Error !!!
|
||||
let a = { 40 + 2; }
|
||||
```
|
||||
|
||||
Dust's compiler associates each emitted instruction with a type. This allows the compiler to enforce
|
||||
compatibility when values are used in expressions. For example, the compiler will not allow a string
|
||||
to be added to an integer, but it will allow either to be added to another of the same type. Aside
|
||||
from instruction arguments, the compiler also checks the types of function arguments and the blocks
|
||||
of `if`/`else` statements.
|
||||
The `a` variable is assigned to the value produced by a block. The block contains an expression that
|
||||
is suppressed by a semicolon, so the block does not evaluate to a value. Therefore, the `a` variable
|
||||
would have to be uninitialized (which Dust does not allow) or result in a runtime error (which Dust
|
||||
avoids at all costs). We can fix this code by moving the semicolon to the end of the block. In this
|
||||
position it suppresses the value of the entire `let` statement. As we saw above, a `let` statement
|
||||
never evaluates to a value, so the semicolon has no effect on the program's behavior and could be
|
||||
omitted altogether.
|
||||
|
||||
The compiler always checks types on the fly, so there is no need for a separate type-checking pass.
|
||||
```rust
|
||||
let a = { 40 + 2 }; // This is fine
|
||||
let a = { 40 + 2 } // This is also fine
|
||||
```
|
||||
|
||||
### Instructions
|
||||
Only the final expression in a block is returned. When a `let` statement is combined with an
|
||||
`if/else` statement, the program can perform conditional side effects before assigning the variable.
|
||||
|
||||
Dust's virtual machine is register-based and uses 64-bit instructions, which encode nine pieces of
|
||||
information:
|
||||
```rust
|
||||
let random: int = random(0..100)
|
||||
let is_even = if random == 99 {
|
||||
write_line("We got a 99!")
|
||||
|
||||
Bit | Description
|
||||
----- | -----------
|
||||
0-8 | The operation code.
|
||||
9 | Boolean flag indicating whether the second argument is a constant
|
||||
10 | Boolean flag indicating whether the third argument is a constant
|
||||
11 | Boolean flag indicating whether the first argument is a local
|
||||
12 | Boolean flag indicating whether the second argument is a local
|
||||
13 | Boolean flag indicating whether the third argument is a local
|
||||
17-32 | First argument, usually the destination register or local where a value is stored
|
||||
33-48 | Second argument, a register, local, constant or boolean flag
|
||||
49-63 | Third argument, a register, local, constant or boolean flag
|
||||
false
|
||||
} else {
|
||||
random % 2 == 0
|
||||
}
|
||||
|
||||
Because the instructions are 64 bits, the maximum number of registers is 2^16, which is more than
|
||||
enough, even for programs that are very large. This also means that chunks can store up to 2^16
|
||||
constants and locals.
|
||||
is_even
|
||||
```
|
||||
|
||||
### Virtual Machine
|
||||
If the above example were passed to Dust as a complete program, it would return a boolean value and
|
||||
might print a message to the console (if the user is especially lucky). However, note that the
|
||||
program could be modified to return no value by simply adding a semicolon at the end.
|
||||
|
||||
The virtual machine is simple and efficient. It uses a stack of registers, which can hold values or
|
||||
pointers. Pointers can point to values in the constant list, locals list, or the stack itself. If it
|
||||
points to a local, the VM must consult its local definitions to find which register hold's the
|
||||
value. Those local defintions are stored as a simple list of register indexes.
|
||||
Compared to JavaScript, Dust's evaluation model is more predictable, less error-prone and will never
|
||||
trap the user into a frustating hunt for a missing semicolon. Compared to Rust, Dust's evaluation
|
||||
model is more accomodating without sacrificing expressiveness. In Rust, semicolons are *required*
|
||||
and *meaningful*, which provides excellent consistency but lacks flexibility. In JavaScript,
|
||||
semicolons are *required* and *meaningless*, which is a source of confusion for many developers.
|
||||
|
||||
While the compiler has multiple responsibilities that warrant more complexity, the VM is simple
|
||||
enough to use a very straightforward design. The VM's `run` function uses a simple `while` loop with
|
||||
a `match` statement to execute instructions. When it reaches a `Return` instruction, it breaks the
|
||||
loop and optionally returns a value.
|
||||
Dust borrowed Rust's approach to semicolons and their effect on evaluation and relaxed the rules to
|
||||
accommodate different styles of coding. Rust isn't designed for command lines or REPLs but Dust is
|
||||
well-suited to those applications. Dust needs to work in a source file or in an ad-hoc one-liner
|
||||
sent to the CLI. Thus, semicolons are optional in most cases.
|
||||
|
||||
There are two things you need to know about semicolons in Dust:
|
||||
|
||||
- Semicolons suppress the value of whatever they follow. The preceding statement or expression will
|
||||
have the type `none` and will not evaluate to a value.
|
||||
- If a semicolon does not change how the program runs, it is optional.
|
||||
|
||||
This example shows three statements with semicolons. The compiler knows that a `let` statement
|
||||
cannot produce a value and will always have the type `none`. Thanks to static typing, it also knows
|
||||
that the `write_line` function has no return value so the function call also has the type `none`.
|
||||
Therefore, these semicolons are optional.
|
||||
|
||||
```rust
|
||||
let a = 40;
|
||||
let b = 2;
|
||||
|
||||
write_line("The answer is ", a + b);
|
||||
```
|
||||
|
||||
Removing the semicolons does not alter the execution pattern or the return value.
|
||||
|
||||
```rust
|
||||
let x = 10
|
||||
let y = 3
|
||||
|
||||
write_line("The remainder is ", x % y)
|
||||
```
|
||||
|
||||
### Type System
|
||||
|
||||
All variables have a type that is established when the variable is declared. This usually does not
|
||||
require that the type be explicitly stated, Dust can infer the type from the value.
|
||||
|
||||
The next example produces a compiler error because the `if` block evaluates to and `int` but the
|
||||
`else` block evaluates to a `str`. Dust does not allow branches of the same `if/else` statement to
|
||||
have different types.
|
||||
|
||||
```rust
|
||||
// !!! Compile Error !!!
|
||||
let input = read_line()
|
||||
let reward = if input == "42" {
|
||||
write_line("You got it! Here's your reward.")
|
||||
|
||||
777 // <- This is an int
|
||||
} else {
|
||||
write_line(input, " is not the answer.")
|
||||
|
||||
"777" // <- This is a string
|
||||
}
|
||||
```
|
||||
|
||||
### Basic Values
|
||||
|
||||
Dust supports the following basic values:
|
||||
|
||||
- Boolean: `true` or `false`
|
||||
- Byte: An unsigned 8-bit integer
|
||||
- Character: A Unicode scalar value
|
||||
- Float: A 64-bit floating-point number
|
||||
- Function: An executable chunk of code
|
||||
- Integer: A signed 64-bit integer
|
||||
- String: A UTF-8 encoded byte sequence
|
||||
|
||||
Dust's "basic" values are conceptually similar because they are singular as opposed to composite.
|
||||
Most of these values are stored on the stack but some are heap-allocated. A Dust string is a
|
||||
sequence of bytes that are encoded in UTF-8. Even though it could be seen as a composite of byte
|
||||
values, strings are considered "basic" because they are parsed directly from tokens and behave as
|
||||
singular values. Shorter strings are stored on the stack while longer strings are heap-allocated.
|
||||
Dust offers built-in native functions that can manipulate strings by accessing their bytes or
|
||||
reading them as a sequence of characters.
|
||||
|
||||
There is no `null` or `undefined` value in Dust. All values and variables must be initialized to one
|
||||
of the supported value types. This eliminates a whole class of bugs that permeate many other
|
||||
languages.
|
||||
|
||||
> I call it my billion-dollar mistake. It was the invention of the null reference in 1965.
|
||||
> - Tony Hoare
|
||||
|
||||
Dust *does* have a `none` type, which should not be confused for being `null`-like. Like the `()` or
|
||||
"unit" type in Rust, `none` exists as a type but not as a value. It indicates the lack of a value
|
||||
from a function, expression or statement. A variable cannot be assigned to `none`.
|
||||
|
||||
## Previous Implementations
|
||||
|
||||
Dust has gone through several iterations, each with its own design choices. It was originally
|
||||
implemented with a syntax tree generated by an external parser, then a parser generator, and finally
|
||||
a custom parser. Eventually the language was rewritten to use bytecode instructions and a virtual
|
||||
machine. The current implementation is by far the most performant and the general design is unlikely
|
||||
to change.
|
||||
machine. The current implementation: compiling to bytecode with custom lexing and parsing for a
|
||||
register-based VM, is by far the most performant and the general design is unlikely to change.
|
||||
|
||||
Dust previously had a more complex type system with type arguments (or "generics") and a simple
|
||||
model for asynchronous execution of statements. Both of these features were removed to simplify the
|
||||
@ -182,19 +247,36 @@ reintroduced in the future.
|
||||
|
||||
## Inspiration
|
||||
|
||||
[Crafting Interpreters] by Bob Nystrom was a major inspiration for rewriting Dust to use bytecode
|
||||
instructions. It was also a great resource for writing the compiler, especially the Pratt parser.
|
||||
|
||||
[A No-Frills Introduction to Lua 5.1 VM Instructions] by Kein-Hong Man was a great resource for the
|
||||
design of Dust's instructions and operation codes. The Lua VM is simple and efficient, and Dust's VM
|
||||
attempts to be the same, though it is not as optimized for different platforms. Dust's instructions
|
||||
were originally 32-bit like Lua's, but were changed to 64-bit to allow for more complex information
|
||||
about the instruction's arguments.
|
||||
[Crafting Interpreters] by Bob Nystrom was a great resource for writing the compiler, especially the
|
||||
Pratt parser. The book is a great introduction to writing interpreters. Had it been discovered
|
||||
sooner, some early implementations of Dust would have been both simpler in design and more ambitious
|
||||
in scope.
|
||||
|
||||
[The Implementation of Lua 5.0] by Roberto Ierusalimschy, Luiz Henrique de Figueiredo, and Waldemar
|
||||
Celes was a great resource for understanding how a compiler and VM tie together. Dust's compiler's
|
||||
optimization functions are inspired by Lua optimizations covered in this paper.
|
||||
Celes was a great resource for understanding register-based virtual machines and their instructions.
|
||||
This paper was recommended by Bob Nystrom in [Crafting Interpreters].
|
||||
|
||||
[Crafting Interpreters]: https://craftinginterpreters.com/
|
||||
[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
|
||||
[A No-Frills Introduction to Lua 5.1 VM Instructions] by Kein-Hong Man has a wealth of detailed
|
||||
information on how Lua uses terse instructions to create dense chunks that execute quickly. This was
|
||||
essential in the design of Dust's instructions. Dust uses compile-time optimizations that are based
|
||||
on Lua optimizations covered in this paper.
|
||||
|
||||
[A Performance Survey on Stack-based and Register-based Virtual Machines] by Ruijie Fang and Siqi
|
||||
Liup was helpful for a quick yet efficient primer on getting stack-based and register-based virtual
|
||||
machines up and running. The included code examples show how to implement both types of VMs in C.
|
||||
The performance comparison between the two types of VMs is worth reading for anyone who is trying to
|
||||
choose between the two. Some of the benchmarks described in the paper inspired similar benchmarks
|
||||
used in this project to compare Dust to other languages.
|
||||
|
||||
## License
|
||||
|
||||
Dust is licensed under the GNU General Public License v3.0. See the `LICENSE` file for details.
|
||||
|
||||
## References
|
||||
|
||||
[^1]: [Crafting Interpreters](https://craftinginterpreters.com/)
|
||||
[^2]: [The Implementation of Lua 5.0](https://www.lua.org/doc/jucs05.pdf)
|
||||
[^3]: [A No-Frills Introduction to Lua 5.1 VM Instructions](https://www.mcours.net/cours/pdf/hasclic3/hasssclic818.pdf)
|
||||
[^4]: [A Performance Survey on Stack-based and Register-based Virtual Machines](https://arxiv.org/abs/1611.00467)
|
||||
[^5]: [List of C-family programming languages](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages)
|
||||
[^6]: [ripgrep is faster than {grep, ag, git grep, ucg, pt, sift}](https://blog.burntsushi.net/ripgrep/#mechanics)
|
||||
|
5
bench/addictive_addition/addictive_addition.ds
Normal file
5
bench/addictive_addition/addictive_addition.ds
Normal file
@ -0,0 +1,5 @@
|
||||
let mut i = 0
|
||||
|
||||
while i < 5_000_000 {
|
||||
i += 1
|
||||
}
|
10
bench/addictive_addition/addictive_addition.java
Normal file
10
bench/addictive_addition/addictive_addition.java
Normal file
@ -0,0 +1,10 @@
|
||||
class AddictiveAddition {
|
||||
|
||||
public static void main(String[] args) {
|
||||
int i = 0;
|
||||
|
||||
while (i < 5_000_000) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
5
bench/addictive_addition/addictive_addition.js
Normal file
5
bench/addictive_addition/addictive_addition.js
Normal file
@ -0,0 +1,5 @@
|
||||
var i = 0;
|
||||
|
||||
while (i < 5_000_000) {
|
||||
i++;
|
||||
}
|
5
bench/addictive_addition/addictive_addition.lua
Normal file
5
bench/addictive_addition/addictive_addition.lua
Normal file
@ -0,0 +1,5 @@
|
||||
local i = 1
|
||||
|
||||
while i < 5000000 do
|
||||
i = i + 1
|
||||
end
|
4
bench/addictive_addition/addictive_addition.py
Normal file
4
bench/addictive_addition/addictive_addition.py
Normal file
@ -0,0 +1,4 @@
|
||||
i = 1
|
||||
|
||||
while i < 5_000_000:
|
||||
i += 1
|
5
bench/addictive_addition/addictive_addition.rb
Normal file
5
bench/addictive_addition/addictive_addition.rb
Normal file
@ -0,0 +1,5 @@
|
||||
i = 0
|
||||
|
||||
while i < 5_000_000
|
||||
i += 1
|
||||
end
|
13
bench/addictive_addition/run.sh
Normal file
13
bench/addictive_addition/run.sh
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
hyperfine \
|
||||
--shell none \
|
||||
--prepare 'sync' \
|
||||
--warmup 5 \
|
||||
'../../target/release/dust addictive_addition.ds' \
|
||||
'node addictive_addition.js' \
|
||||
'deno addictive_addition.js' \
|
||||
'bun addictive_addition.js' \
|
||||
'python addictive_addition.py' \
|
||||
'lua addictive_addition.lua' \
|
||||
'ruby addictive_addition.rb' \
|
||||
'java addictive_addition.java'
|
9
bench/fibonacci/run.sh
Normal file
9
bench/fibonacci/run.sh
Normal file
@ -0,0 +1,9 @@
|
||||
hyperfine \
|
||||
--shell none \
|
||||
--prepare 'sync' \
|
||||
--warmup 5 \
|
||||
'../../target/release/dust ../../examples/fibonacci.ds' \
|
||||
'node fibonacci.js' \
|
||||
'deno fibonacci.js' \
|
||||
'bun fibonacci.js' \
|
||||
'python fibonacci.py'
|
9
bench/recursion/recursion.ds
Normal file
9
bench/recursion/recursion.ds
Normal file
@ -0,0 +1,9 @@
|
||||
fn decrement(i: int) -> str {
|
||||
if i == 0 {
|
||||
return "Done!";
|
||||
}
|
||||
|
||||
decrement(i - 1)
|
||||
}
|
||||
|
||||
write_line(decrement(10_000))
|
9
bench/recursion/recursion.js
Normal file
9
bench/recursion/recursion.js
Normal file
@ -0,0 +1,9 @@
|
||||
function decrement(i) {
|
||||
if (i == 0) {
|
||||
return "Done!";
|
||||
}
|
||||
|
||||
return decrement(i - 1);
|
||||
}
|
||||
|
||||
console.log(decrement(10_000));
|
8
bench/recursion/run.sh
Normal file
8
bench/recursion/run.sh
Normal file
@ -0,0 +1,8 @@
|
||||
hyperfine \
|
||||
--shell none \
|
||||
--prepare 'sync' \
|
||||
--warmup 5 \
|
||||
'../../target/release/dust recursion.ds' \
|
||||
'node recursion.js' \
|
||||
'deno recursion.js' \
|
||||
'bun recursion.js'
|
9
build.sh
Normal file
9
build.sh
Normal file
@ -0,0 +1,9 @@
|
||||
RUSTFLAGS="\
|
||||
-C collapse-macro-debuginfo=false \
|
||||
-C default-linker-libraries=false \
|
||||
-C embed-bitcode=true \
|
||||
-C force-frame-pointers=true \
|
||||
-C force-unwind-tables=false \
|
||||
-C passes=mem2reg \
|
||||
-C linker-plugin-lto"
|
||||
cargo build --release --package dust-cli
|
@ -1,22 +1,28 @@
|
||||
[package]
|
||||
name = "dust-cli"
|
||||
description = "The Dust Programming Language CLI"
|
||||
version = "0.1.0"
|
||||
description = "Command line interface for the Dust programming language"
|
||||
authors = ["Jeff Anderson"]
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
readme.workspace = true
|
||||
repository.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "dust"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.14", features = ["derive"] }
|
||||
colored = "2.1.0"
|
||||
clap = { version = "4.5.14", features = [
|
||||
"cargo",
|
||||
"color",
|
||||
"derive",
|
||||
"help",
|
||||
"wrap_help",
|
||||
] }
|
||||
color-print = "0.3.7"
|
||||
dust-lang = { path = "../dust-lang" }
|
||||
env_logger = "0.11.5"
|
||||
log = "0.4.22"
|
||||
postcard = "1.0.10"
|
||||
serde_json = "1.0.133"
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = "0.3.19"
|
||||
|
@ -1,192 +1,387 @@
|
||||
use std::io::{stdout, Write};
|
||||
use std::time::Instant;
|
||||
use std::{fs::read_to_string, path::PathBuf};
|
||||
use std::{
|
||||
fs::read_to_string,
|
||||
io::{self, stdout, Read},
|
||||
path::PathBuf,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use colored::Colorize;
|
||||
use dust_lang::{compile, lex, run, write_token_list};
|
||||
use log::{Level, LevelFilter};
|
||||
use clap::{
|
||||
builder::{styling::AnsiColor, Styles},
|
||||
crate_authors, crate_description, crate_version,
|
||||
error::ErrorKind,
|
||||
Args, ColorChoice, Error, Parser, Subcommand, ValueHint,
|
||||
};
|
||||
use color_print::{cformat, cstr};
|
||||
use dust_lang::{CompileError, Compiler, DustError, DustString, Lexer, Span, Token, Vm};
|
||||
use tracing::{subscriber::set_global_default, Level};
|
||||
use tracing_subscriber::FmtSubscriber;
|
||||
|
||||
const ABOUT: &str = cstr!(
|
||||
r#"
|
||||
<bright-magenta,bold>Dust CLI
|
||||
────────</>
|
||||
{about}
|
||||
|
||||
<bold>⚙️ Version:</> {version}
|
||||
<bold>🦀 Author:</> {author}
|
||||
<bold>⚖️ License:</> GPL-3.0
|
||||
<bold>🔬 Repository:</> https://git.jeffa.io/jeff/dust
|
||||
"#
|
||||
);
|
||||
|
||||
const PLAIN_ABOUT: &str = r#"
|
||||
{about}
|
||||
"#;
|
||||
|
||||
const USAGE: &str = cstr!(
|
||||
r#"
|
||||
<bright-magenta,bold>Usage:</> {usage}
|
||||
"#
|
||||
);
|
||||
|
||||
const SUBCOMMANDS: &str = cstr!(
|
||||
r#"
|
||||
<bright-magenta,bold>Modes:</>
|
||||
{subcommands}
|
||||
"#
|
||||
);
|
||||
|
||||
const OPTIONS: &str = cstr!(
|
||||
r#"
|
||||
<bright-magenta,bold>Options:</>
|
||||
{options}
|
||||
"#
|
||||
);
|
||||
|
||||
const CREATE_MAIN_HELP_TEMPLATE: fn() -> String =
|
||||
|| cformat!("{ABOUT}{USAGE}{SUBCOMMANDS}{OPTIONS}");
|
||||
|
||||
const CREATE_MODE_HELP_TEMPLATE: fn(&str) -> String = |title| {
|
||||
cformat!(
|
||||
"\
|
||||
<bright-magenta,bold>{title}\n────────</>\
|
||||
{PLAIN_ABOUT}{USAGE}{OPTIONS}
|
||||
"
|
||||
)
|
||||
};
|
||||
|
||||
const STYLES: Styles = Styles::styled()
|
||||
.literal(AnsiColor::Cyan.on_default())
|
||||
.placeholder(AnsiColor::Cyan.on_default())
|
||||
.valid(AnsiColor::BrightCyan.on_default())
|
||||
.invalid(AnsiColor::BrightYellow.on_default())
|
||||
.error(AnsiColor::BrightRed.on_default());
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(
|
||||
version = env!("CARGO_PKG_VERSION"),
|
||||
author = env!("CARGO_PKG_AUTHORS"),
|
||||
about = env!("CARGO_PKG_DESCRIPTION"),
|
||||
version = crate_version!(),
|
||||
author = crate_authors!(),
|
||||
about = crate_description!(),
|
||||
color = ColorChoice::Auto,
|
||||
help_template = CREATE_MAIN_HELP_TEMPLATE(),
|
||||
styles = STYLES,
|
||||
)]
|
||||
#[command(args_conflicts_with_subcommands = true)]
|
||||
struct Cli {
|
||||
#[command(flatten)]
|
||||
global_arguments: GlobalArguments,
|
||||
/// Overrides the DUST_LOG environment variable
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
value_parser = |input: &str| match input.to_uppercase().as_str() {
|
||||
"TRACE" => Ok(Level::TRACE),
|
||||
"DEBUG" => Ok(Level::DEBUG),
|
||||
"INFO" => Ok(Level::INFO),
|
||||
"WARN" => Ok(Level::WARN),
|
||||
"ERROR" => Ok(Level::ERROR),
|
||||
_ => Err(Error::new(ErrorKind::ValueValidation)),
|
||||
}
|
||||
)]
|
||||
log_level: Option<Level>,
|
||||
|
||||
#[command(subcommand)]
|
||||
mode: Option<CliMode>,
|
||||
mode: Option<Mode>,
|
||||
|
||||
#[command(flatten)]
|
||||
run: Run,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum CliMode {
|
||||
/// Run the source code (default)
|
||||
#[command(short_flag = 'r')]
|
||||
Run {
|
||||
#[command(flatten)]
|
||||
global_arguments: GlobalArguments,
|
||||
|
||||
/// Do not print the program's output value
|
||||
#[arg(short, long)]
|
||||
no_output: bool,
|
||||
},
|
||||
|
||||
/// Compile a chunk and show the disassembly
|
||||
#[command(short_flag = 'd')]
|
||||
Disassemble {
|
||||
#[command(flatten)]
|
||||
global_arguments: GlobalArguments,
|
||||
|
||||
/// Style the disassembly output
|
||||
#[arg(short, long)]
|
||||
style: bool,
|
||||
},
|
||||
|
||||
/// Create and display tokens from the source code
|
||||
#[command(short_flag = 't')]
|
||||
Tokenize {
|
||||
#[command(flatten)]
|
||||
global_arguments: GlobalArguments,
|
||||
|
||||
/// Style the disassembly output
|
||||
#[arg(short, long)]
|
||||
style: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
struct GlobalArguments {
|
||||
/// Log level, overrides the DUST_LOG environment variable
|
||||
///
|
||||
/// Possible values: trace, debug, info, warn, error
|
||||
#[arg(short, long, value_name = "LOG_LEVEL")]
|
||||
log: Option<LevelFilter>,
|
||||
|
||||
/// Source code
|
||||
///
|
||||
/// Conflicts with the file argument
|
||||
#[arg(short, long, value_name = "SOURCE", conflicts_with = "file")]
|
||||
#[derive(Args)]
|
||||
struct Input {
|
||||
/// Source code to run instead of a file
|
||||
#[arg(short, long, value_hint = ValueHint::Other, value_name = "INPUT")]
|
||||
command: Option<String>,
|
||||
|
||||
/// Read source code from stdin
|
||||
#[arg(long)]
|
||||
stdin: bool,
|
||||
|
||||
/// Path to a source code file
|
||||
#[arg(required_unless_present = "command")]
|
||||
#[arg(value_hint = ValueHint::FilePath)]
|
||||
file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn set_log_and_get_source(arguments: GlobalArguments, start_time: Instant) -> String {
|
||||
let GlobalArguments { command, file, log } = arguments;
|
||||
let mut logger = env_logger::builder();
|
||||
/// Compile and run the program (default)
|
||||
#[derive(Args)]
|
||||
#[command(
|
||||
short_flag = 'r',
|
||||
help_template = CREATE_MODE_HELP_TEMPLATE("Run Mode")
|
||||
)]
|
||||
struct Run {
|
||||
/// Print the time taken for compilation and execution
|
||||
#[arg(long)]
|
||||
time: bool,
|
||||
|
||||
logger.format(move |buf, record| {
|
||||
let elapsed = format!("T+{:.04}", start_time.elapsed().as_secs_f32()).dimmed();
|
||||
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 display = format!("[{elapsed}] {level_display:5} {args}", args = record.args());
|
||||
/// Do not print the program's return value
|
||||
#[arg(long)]
|
||||
no_output: bool,
|
||||
|
||||
writeln!(buf, "{display}")
|
||||
});
|
||||
/// Custom program name, overrides the file name
|
||||
#[arg(long)]
|
||||
name: Option<DustString>,
|
||||
|
||||
if let Some(level) = log {
|
||||
logger.filter_level(level).init();
|
||||
} else {
|
||||
logger.parse_env("DUST_LOG").init();
|
||||
#[command(flatten)]
|
||||
input: Input,
|
||||
}
|
||||
|
||||
if let Some(source) = command {
|
||||
source
|
||||
} else {
|
||||
let path = file.expect("Path is required when command is not provided");
|
||||
#[derive(Subcommand)]
|
||||
#[clap(subcommand_value_name = "MODE", flatten_help = true)]
|
||||
enum Mode {
|
||||
Run(Run),
|
||||
|
||||
read_to_string(path).expect("Failed to read file")
|
||||
/// Compile and print the bytecode disassembly
|
||||
#[command(
|
||||
short_flag = 'd',
|
||||
help_template = CREATE_MODE_HELP_TEMPLATE("Disassemble Mode")
|
||||
)]
|
||||
Disassemble {
|
||||
/// Style disassembly output
|
||||
#[arg(short, long, default_value = "true")]
|
||||
style: bool,
|
||||
|
||||
/// Custom program name, overrides the file name
|
||||
#[arg(long)]
|
||||
name: Option<DustString>,
|
||||
|
||||
#[command(flatten)]
|
||||
input: Input,
|
||||
},
|
||||
|
||||
/// Lex the source code and print the tokens
|
||||
#[command(
|
||||
short_flag = 't',
|
||||
help_template = CREATE_MODE_HELP_TEMPLATE("Tokenize Mode")
|
||||
)]
|
||||
Tokenize {
|
||||
/// Style token output
|
||||
#[arg(short, long, default_value = "true")]
|
||||
style: bool,
|
||||
|
||||
#[command(flatten)]
|
||||
input: Input,
|
||||
},
|
||||
}
|
||||
|
||||
fn get_source_and_file_name(input: Input) -> (String, Option<DustString>) {
|
||||
if let Some(path) = input.file {
|
||||
let source = read_to_string(&path).expect("Failed to read source file");
|
||||
let file_name = path
|
||||
.file_name()
|
||||
.and_then(|os_str| os_str.to_str())
|
||||
.map(DustString::from);
|
||||
|
||||
return (source, file_name);
|
||||
}
|
||||
|
||||
if input.stdin {
|
||||
let mut source = String::new();
|
||||
io::stdin()
|
||||
.read_to_string(&mut source)
|
||||
.expect("Failed to read from stdin");
|
||||
|
||||
return (source, None);
|
||||
}
|
||||
|
||||
let source = input.command.expect("No source code provided");
|
||||
|
||||
(source, None)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let start_time = Instant::now();
|
||||
let Cli {
|
||||
global_arguments,
|
||||
log_level,
|
||||
mode,
|
||||
run,
|
||||
} = Cli::parse();
|
||||
let mode = mode.unwrap_or(CliMode::Run {
|
||||
global_arguments,
|
||||
no_output: false,
|
||||
});
|
||||
let mode = mode.unwrap_or(Mode::Run(run));
|
||||
let subscriber = FmtSubscriber::builder()
|
||||
.with_max_level(log_level)
|
||||
.with_thread_names(true)
|
||||
.with_file(false)
|
||||
.finish();
|
||||
|
||||
if let CliMode::Run {
|
||||
global_arguments,
|
||||
set_global_default(subscriber).expect("Failed to set tracing subscriber");
|
||||
|
||||
if let Mode::Disassemble { style, name, input } = mode {
|
||||
let (source, file_name) = get_source_and_file_name(input);
|
||||
let lexer = Lexer::new(&source);
|
||||
let program_name = name.or(file_name);
|
||||
let mut compiler = match Compiler::new(lexer, program_name, true) {
|
||||
Ok(compiler) => compiler,
|
||||
Err(error) => {
|
||||
handle_compile_error(error, &source);
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match compiler.compile() {
|
||||
Ok(()) => {}
|
||||
Err(error) => {
|
||||
handle_compile_error(error, &source);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let chunk = compiler.finish();
|
||||
let mut stdout = stdout().lock();
|
||||
|
||||
chunk
|
||||
.disassembler(&mut stdout)
|
||||
.width(65)
|
||||
.style(style)
|
||||
.source(&source)
|
||||
.disassemble()
|
||||
.expect("Failed to write disassembly to stdout");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if let Mode::Tokenize { input, .. } = mode {
|
||||
let (source, _) = get_source_and_file_name(input);
|
||||
let mut lexer = Lexer::new(&source);
|
||||
let mut next_token = || -> Option<(Token, Span, bool)> {
|
||||
match lexer.next_token() {
|
||||
Ok((token, position)) => Some((token, position, lexer.is_eof())),
|
||||
Err(error) => {
|
||||
let report = DustError::compile(CompileError::Lex(error), &source).report();
|
||||
|
||||
eprintln!("{report}");
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
println!("{:^66}", "Tokens");
|
||||
|
||||
for _ in 0..66 {
|
||||
print!("-");
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("{:^21}|{:^22}|{:^22}", "Kind", "Value", "Position");
|
||||
|
||||
for _ in 0..66 {
|
||||
print!("-");
|
||||
}
|
||||
|
||||
println!();
|
||||
|
||||
while let Some((token, position, is_eof)) = next_token() {
|
||||
if is_eof {
|
||||
break;
|
||||
}
|
||||
|
||||
let token_kind = token.kind().to_string();
|
||||
let token = token.to_string();
|
||||
let position = position.to_string();
|
||||
|
||||
println!("{token_kind:^21}|{token:^22}|{position:^22}");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if let Mode::Run(Run {
|
||||
time,
|
||||
no_output,
|
||||
} = mode
|
||||
name,
|
||||
input,
|
||||
}) = mode
|
||||
{
|
||||
let source = set_log_and_get_source(global_arguments, start_time);
|
||||
let run_result = run(&source);
|
||||
let (source, file_name) = get_source_and_file_name(input);
|
||||
let lexer = Lexer::new(&source);
|
||||
let program_name = name.or(file_name);
|
||||
let mut compiler = match Compiler::new(lexer, program_name, true) {
|
||||
Ok(compiler) => compiler,
|
||||
Err(error) => {
|
||||
handle_compile_error(error, &source);
|
||||
|
||||
match run_result {
|
||||
Ok(Some(value)) => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match compiler.compile() {
|
||||
Ok(()) => {}
|
||||
Err(error) => {
|
||||
handle_compile_error(error, &source);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let chunk = compiler.finish();
|
||||
let compile_end = start_time.elapsed();
|
||||
|
||||
let vm = Vm::new(chunk);
|
||||
let return_value = vm.run();
|
||||
let run_end = start_time.elapsed();
|
||||
|
||||
if let Some(value) = return_value {
|
||||
if !no_output {
|
||||
println!("{}", value)
|
||||
}
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(error) => {
|
||||
eprintln!("{}", error.report());
|
||||
|
||||
if time {
|
||||
let run_time = run_end - compile_end;
|
||||
let total_time = compile_end + run_time;
|
||||
|
||||
print_time("Compile Time", compile_end);
|
||||
print_time("Run Time", run_time);
|
||||
print_time("Total Time", total_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
fn print_time(phase: &str, instant: Duration) {
|
||||
let seconds = instant.as_secs_f64();
|
||||
|
||||
match seconds {
|
||||
..=0.001 => {
|
||||
println!(
|
||||
"{phase:12}: {microseconds}µs",
|
||||
microseconds = (seconds * 1_000_000.0).round()
|
||||
);
|
||||
}
|
||||
..=0.199 => {
|
||||
println!(
|
||||
"{phase:12}: {milliseconds}ms",
|
||||
milliseconds = (seconds * 1000.0).round()
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
println!("{phase:12}: {seconds}s");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let CliMode::Disassemble {
|
||||
global_arguments,
|
||||
style,
|
||||
} = mode
|
||||
{
|
||||
let source = set_log_and_get_source(global_arguments, start_time);
|
||||
let chunk = match compile(&source) {
|
||||
Ok(chunk) => chunk,
|
||||
Err(error) => {
|
||||
eprintln!("{}", error.report());
|
||||
fn handle_compile_error(error: CompileError, source: &str) {
|
||||
let dust_error = DustError::compile(error, source);
|
||||
let report = dust_error.report();
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
let disassembly = chunk
|
||||
.disassembler()
|
||||
.style(style)
|
||||
.source(&source)
|
||||
.disassemble();
|
||||
|
||||
println!("{}", disassembly);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if let CliMode::Tokenize {
|
||||
global_arguments,
|
||||
style,
|
||||
} = mode
|
||||
{
|
||||
let source = set_log_and_get_source(global_arguments, start_time);
|
||||
let tokens = match lex(&source) {
|
||||
Ok(tokens) => tokens,
|
||||
Err(error) => {
|
||||
eprintln!("{}", error.report());
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mut stdout = stdout().lock();
|
||||
|
||||
write_token_list(&tokens, style, &mut stdout)
|
||||
}
|
||||
eprintln!("{report}");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,23 +1,110 @@
|
||||
[package]
|
||||
name = "dust-lang"
|
||||
description = "Interpreter library for the Dust programming language"
|
||||
version = "0.5.0"
|
||||
description = "Dust programming language library"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
readme.workspace = true
|
||||
repository.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
annotate-snippets = "0.11.4"
|
||||
colored = "2.1.0"
|
||||
log = "0.4.22"
|
||||
rand = "0.8.5"
|
||||
serde = { version = "1.0.203", features = ["derive"] }
|
||||
serde_json = "1.0.117"
|
||||
getrandom = { version = "0.2", features = [
|
||||
"js",
|
||||
] } # Indirect dependency, for wasm builds
|
||||
] } # Indirect dependency, for WASM builds
|
||||
smartstring = { version = "1.0.1", features = [
|
||||
"serde",
|
||||
], default-features = false }
|
||||
tracing = "0.1.41"
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.11.5"
|
||||
criterion = { version = "0.3.4", features = ["html_reports"] }
|
||||
|
||||
[[bench]]
|
||||
name = "addictive_addition"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "fibonacci"
|
||||
harness = false
|
||||
|
||||
[[test]]
|
||||
name = "logic_and"
|
||||
path = "tests/logic/and.rs"
|
||||
|
||||
[[test]]
|
||||
name = "logic_and_and"
|
||||
path = "tests/logic/and_and.rs"
|
||||
|
||||
[[test]]
|
||||
name = "logic_or"
|
||||
path = "tests/logic/or.rs"
|
||||
|
||||
[[test]]
|
||||
name = "logic_variables"
|
||||
path = "tests/logic/variables.rs"
|
||||
|
||||
[[test]]
|
||||
name = "math_add"
|
||||
path = "tests/math/add.rs"
|
||||
|
||||
[[test]]
|
||||
name = "math_add_assign"
|
||||
path = "tests/math/add_assign.rs"
|
||||
|
||||
[[test]]
|
||||
name = "math_add_errors"
|
||||
path = "tests/math/add_errors.rs"
|
||||
|
||||
[[test]]
|
||||
name = "math_divide"
|
||||
path = "tests/math/divide.rs"
|
||||
|
||||
[[test]]
|
||||
name = "math_divide_assign"
|
||||
path = "tests/math/divide_assign.rs"
|
||||
|
||||
[[test]]
|
||||
name = "math_divide_erros"
|
||||
path = "tests/math/divide_errors.rs"
|
||||
|
||||
[[test]]
|
||||
name = "math_modulo"
|
||||
path = "tests/math/modulo.rs"
|
||||
|
||||
[[test]]
|
||||
name = "math_modulo_assign"
|
||||
path = "tests/math/modulo_assign.rs"
|
||||
|
||||
[[test]]
|
||||
name = "math_modulo_errors"
|
||||
path = "tests/math/modulo_errors.rs"
|
||||
|
||||
[[test]]
|
||||
name = "math_multiply"
|
||||
path = "tests/math/multiply.rs"
|
||||
|
||||
[[test]]
|
||||
name = "math_multiply_assign"
|
||||
path = "tests/math/multiply_assign.rs"
|
||||
|
||||
[[test]]
|
||||
name = "math_multiply_errors"
|
||||
path = "tests/math/multiply_errors.rs"
|
||||
|
||||
[[test]]
|
||||
name = "math_subtract"
|
||||
path = "tests/math/subtract.rs"
|
||||
|
||||
[[test]]
|
||||
name = "math_subtract_assign"
|
||||
path = "tests/math/subtract_assign.rs"
|
||||
|
||||
[[test]]
|
||||
name = "math_subtract_errors"
|
||||
path = "tests/math/subtract_errors.rs"
|
||||
|
29
dust-lang/benches/addictive_addition.rs
Normal file
29
dust-lang/benches/addictive_addition.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use dust_lang::run;
|
||||
|
||||
const SOURCE: &str = r"
|
||||
let mut i = 0
|
||||
|
||||
while i < 5_000_000 {
|
||||
i += 1
|
||||
}
|
||||
";
|
||||
|
||||
fn addictive_addition(source: &str) {
|
||||
run(source).unwrap();
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("addictive_addition");
|
||||
|
||||
group.measurement_time(Duration::from_secs(15));
|
||||
group.bench_function("addictive_addition", |b| {
|
||||
b.iter(|| addictive_addition(black_box(SOURCE)))
|
||||
});
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
32
dust-lang/benches/fibonacci.rs
Normal file
32
dust-lang/benches/fibonacci.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use dust_lang::run;
|
||||
|
||||
const SOURCE: &str = r"
|
||||
fn fib (n: int) -> int {
|
||||
if n <= 0 { return 0 }
|
||||
if n == 1 { return 1 }
|
||||
|
||||
fib(n - 1) + fib(n - 2)
|
||||
}
|
||||
|
||||
fib(25)
|
||||
";
|
||||
|
||||
fn addictive_addition(source: &str) {
|
||||
run(source).unwrap();
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("fibonacci");
|
||||
|
||||
group.measurement_time(Duration::from_secs(15));
|
||||
group.bench_function("fibonacci", |b| {
|
||||
b.iter(|| addictive_addition(black_box(SOURCE)))
|
||||
});
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
@ -1,146 +0,0 @@
|
||||
//! In-memory representation of a Dust program or function.
|
||||
//!
|
||||
//! A chunk consists of a sequence of instructions and their positions, a list of constants, and a
|
||||
//! list of locals that can be executed by the Dust virtual machine. Chunks have a name when they
|
||||
//! belong to a named function.
|
||||
|
||||
use std::fmt::{self, Debug, Display, Write};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{ConcreteValue, Disassembler, FunctionType, Instruction, Scope, Span, Type};
|
||||
|
||||
/// In-memory representation of a Dust program or function.
|
||||
///
|
||||
/// See the [module-level documentation](index.html) for more information.
|
||||
#[derive(Clone, PartialOrd, Serialize, Deserialize)]
|
||||
pub struct Chunk {
|
||||
name: Option<String>,
|
||||
r#type: FunctionType,
|
||||
|
||||
instructions: Vec<(Instruction, Type, Span)>,
|
||||
constants: Vec<ConcreteValue>,
|
||||
locals: Vec<Local>,
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
pub fn new(name: Option<String>) -> Self {
|
||||
Self {
|
||||
name,
|
||||
instructions: Vec::new(),
|
||||
constants: Vec::new(),
|
||||
locals: Vec::new(),
|
||||
r#type: FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::None),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_data(
|
||||
name: Option<String>,
|
||||
r#type: FunctionType,
|
||||
instructions: Vec<(Instruction, Type, Span)>,
|
||||
constants: Vec<ConcreteValue>,
|
||||
locals: Vec<Local>,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
r#type,
|
||||
instructions,
|
||||
constants,
|
||||
locals,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Option<&String> {
|
||||
self.name.as_ref()
|
||||
}
|
||||
|
||||
pub fn r#type(&self) -> &FunctionType {
|
||||
&self.r#type
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.instructions.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.instructions.is_empty()
|
||||
}
|
||||
|
||||
pub fn constants(&self) -> &Vec<ConcreteValue> {
|
||||
&self.constants
|
||||
}
|
||||
|
||||
pub fn instructions(&self) -> &Vec<(Instruction, Type, Span)> {
|
||||
&self.instructions
|
||||
}
|
||||
|
||||
pub fn locals(&self) -> &Vec<Local> {
|
||||
&self.locals
|
||||
}
|
||||
|
||||
pub fn disassembler(&self) -> Disassembler {
|
||||
Disassembler::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Chunk {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let disassembly = self.disassembler().style(true).disassemble();
|
||||
|
||||
write!(f, "{disassembly}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Chunk {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let disassembly = self.disassembler().style(false).disassemble();
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
f.write_char('\n')?;
|
||||
}
|
||||
|
||||
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: u16,
|
||||
|
||||
/// The expected type of the local's value.
|
||||
pub r#type: Type,
|
||||
|
||||
/// Whether the local is mutable.
|
||||
pub is_mutable: bool,
|
||||
|
||||
/// Scope where the variable was declared.
|
||||
pub scope: Scope,
|
||||
}
|
||||
|
||||
impl Local {
|
||||
/// Creates a new Local instance.
|
||||
pub fn new(identifier_index: u16, r#type: Type, mutable: bool, scope: Scope) -> Self {
|
||||
Self {
|
||||
identifier_index,
|
||||
r#type,
|
||||
is_mutable: mutable,
|
||||
scope,
|
||||
}
|
||||
}
|
||||
}
|
444
dust-lang/src/chunk/disassembler.rs
Normal file
444
dust-lang/src/chunk/disassembler.rs
Normal file
@ -0,0 +1,444 @@
|
||||
//! 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.
|
||||
//!
|
||||
//! If the 'source' option is set, the disassembler will include the source code in the output.
|
||||
//!
|
||||
//! # Output
|
||||
//!
|
||||
//! The disassembler will output a human-readable representation of the chunk by writing to any type
|
||||
//! that implements the [Write][] trait.
|
||||
//!
|
||||
//! ```text
|
||||
//! ╭─────────────────────────────────────────────────────────────────╮
|
||||
//! │ dust │
|
||||
//! │ │
|
||||
//! │ write_line("Hello world!") │
|
||||
//! │ │
|
||||
//! │ 3 instructions, 1 constants, 0 locals, returns none │
|
||||
//! │ │
|
||||
//! │ Instructions │
|
||||
//! │ ╭─────┬────────────┬─────────────────┬────────────────────────╮ │
|
||||
//! │ │ i │ POSITION │ OPERATION │ INFO │ │
|
||||
//! │ ├─────┼────────────┼─────────────────┼────────────────────────┤ │
|
||||
//! │ │ 0 │ (11, 25) │ LOAD_CONSTANT │ R0 = C0 │ │
|
||||
//! │ │ 1 │ (0, 26) │ CALL_NATIVE │ write_line(R0) │ │
|
||||
//! │ │ 2 │ (26, 26) │ RETURN │ RETURN │ │
|
||||
//! │ ╰─────┴────────────┴─────────────────┴────────────────────────╯ │
|
||||
//! │ Constants │
|
||||
//! │ ╭─────┬──────────────────────────┬──────────────────────────╮ │
|
||||
//! │ │ i │ TYPE │ VALUE │ │
|
||||
//! │ ├─────┼──────────────────────────┼──────────────────────────┤ │
|
||||
//! │ │ 0 │ str │ Hello world! │ │
|
||||
//! │ ╰─────┴──────────────────────────┴──────────────────────────╯ │
|
||||
//! ╰─────────────────────────────────────────────────────────────────╯
|
||||
//! ```
|
||||
use std::io::{self, Write};
|
||||
|
||||
use colored::{ColoredString, Colorize};
|
||||
|
||||
use crate::{Chunk, Local};
|
||||
|
||||
const INSTRUCTION_COLUMNS: [(&str, usize); 4] =
|
||||
[("i", 5), ("POSITION", 12), ("OPERATION", 17), ("INFO", 24)];
|
||||
const INSTRUCTION_BORDERS: [&str; 3] = [
|
||||
"╭─────┬────────────┬─────────────────┬────────────────────────╮",
|
||||
"├─────┼────────────┼─────────────────┼────────────────────────┤",
|
||||
"╰─────┴────────────┴─────────────────┴────────────────────────╯",
|
||||
];
|
||||
|
||||
const LOCAL_COLUMNS: [(&str, usize); 5] = [
|
||||
("i", 5),
|
||||
("IDENTIFIER", 16),
|
||||
("REGISTER", 10),
|
||||
("SCOPE", 7),
|
||||
("MUTABLE", 7),
|
||||
];
|
||||
const LOCAL_BORDERS: [&str; 3] = [
|
||||
"╭─────┬────────────────┬──────────┬───────┬───────╮",
|
||||
"├─────┼────────────────┼──────────┼───────┼───────┤",
|
||||
"╰─────┴────────────────┴──────────┴───────┴───────╯",
|
||||
];
|
||||
|
||||
const CONSTANT_COLUMNS: [(&str, usize); 3] = [("i", 5), ("TYPE", 26), ("VALUE", 26)];
|
||||
const CONSTANT_BORDERS: [&str; 3] = [
|
||||
"╭─────┬──────────────────────────┬──────────────────────────╮",
|
||||
"├─────┼──────────────────────────┼──────────────────────────┤",
|
||||
"╰─────┴──────────────────────────┴──────────────────────────╯",
|
||||
];
|
||||
|
||||
const INDENTATION: &str = "│ ";
|
||||
const TOP_BORDER: [char; 3] = ['╭', '─', '╮'];
|
||||
const LEFT_BORDER: char = '│';
|
||||
const RIGHT_BORDER: char = '│';
|
||||
const BOTTOM_BORDER: [char; 3] = ['╰', '─', '╯'];
|
||||
|
||||
/// Builder that constructs a human-readable representation of a chunk.
|
||||
///
|
||||
/// See the [module-level documentation](index.html) for more information.
|
||||
pub struct Disassembler<'a, W> {
|
||||
writer: &'a mut W,
|
||||
chunk: &'a Chunk,
|
||||
source: Option<&'a str>,
|
||||
|
||||
// Options
|
||||
style: bool,
|
||||
indent: usize,
|
||||
width: usize,
|
||||
show_type: bool,
|
||||
}
|
||||
|
||||
impl<'a, W: Write> Disassembler<'a, W> {
|
||||
pub fn new(chunk: &'a Chunk, writer: &'a mut W) -> Self {
|
||||
Self {
|
||||
writer,
|
||||
chunk,
|
||||
source: None,
|
||||
style: false,
|
||||
indent: 0,
|
||||
width: Self::content_length(),
|
||||
show_type: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn source(mut self, source: &'a str) -> Self {
|
||||
self.source = Some(source);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn style(mut self, styled: bool) -> Self {
|
||||
self.style = styled;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn width(mut self, width: usize) -> Self {
|
||||
self.width = width.max(Self::content_length());
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn show_type(mut self, show_type: bool) -> Self {
|
||||
self.show_type = show_type;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn indent(mut self, indent: usize) -> Self {
|
||||
self.indent = indent;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn content_length() -> usize {
|
||||
let longest_line_length = INSTRUCTION_BORDERS[0].chars().count();
|
||||
|
||||
longest_line_length
|
||||
}
|
||||
|
||||
fn line_length(&self) -> usize {
|
||||
let indentation_length = INDENTATION.chars().count();
|
||||
|
||||
self.width + (indentation_length * self.indent) + 2 // Left and right border
|
||||
}
|
||||
|
||||
fn write_char(&mut self, c: char) -> Result<(), io::Error> {
|
||||
write!(&mut self.writer, "{}", c)
|
||||
}
|
||||
|
||||
fn write_colored(&mut self, text: &ColoredString) -> Result<(), io::Error> {
|
||||
write!(&mut self.writer, "{}", text)
|
||||
}
|
||||
|
||||
fn write_str(&mut self, text: &str) -> Result<(), io::Error> {
|
||||
write!(&mut self.writer, "{}", text)
|
||||
}
|
||||
|
||||
fn write_content(
|
||||
&mut self,
|
||||
text: &str,
|
||||
center: bool,
|
||||
style_bold: bool,
|
||||
style_dim: bool,
|
||||
add_border: bool,
|
||||
) -> Result<(), io::Error> {
|
||||
let (line_content, overflow) = {
|
||||
if text.len() > self.width {
|
||||
let split_index = text
|
||||
.char_indices()
|
||||
.nth(self.width)
|
||||
.map(|(index, _)| index)
|
||||
.unwrap_or_else(|| text.len());
|
||||
|
||||
text.split_at(split_index)
|
||||
} else {
|
||||
(text, "")
|
||||
}
|
||||
};
|
||||
let (left_pad_length, right_pad_length) = {
|
||||
let width = self.line_length();
|
||||
let line_content_length = line_content.chars().count();
|
||||
let extra_space = width.saturating_sub(line_content_length);
|
||||
let half = extra_space / 2;
|
||||
let remainder = extra_space % 2;
|
||||
|
||||
if center {
|
||||
(half, half + remainder)
|
||||
} else {
|
||||
(0, extra_space)
|
||||
}
|
||||
};
|
||||
|
||||
for _ in 0..self.indent {
|
||||
self.write_str(INDENTATION)?;
|
||||
}
|
||||
|
||||
if add_border {
|
||||
self.write_char(LEFT_BORDER)?;
|
||||
}
|
||||
|
||||
if center {
|
||||
for _ in 0..left_pad_length {
|
||||
self.write_char(' ')?;
|
||||
}
|
||||
}
|
||||
|
||||
if style_bold {
|
||||
self.write_colored(&line_content.bold())?;
|
||||
} else if style_dim {
|
||||
self.write_colored(&line_content.dimmed())?;
|
||||
} else {
|
||||
self.write_str(line_content)?;
|
||||
}
|
||||
|
||||
if center {
|
||||
for _ in 0..right_pad_length {
|
||||
self.write_char(' ')?;
|
||||
}
|
||||
}
|
||||
|
||||
if add_border {
|
||||
self.write_char(RIGHT_BORDER)?;
|
||||
}
|
||||
|
||||
self.write_char('\n')?;
|
||||
|
||||
if !overflow.is_empty() {
|
||||
self.write_content(overflow, center, style_bold, style_dim, add_border)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_center_border(&mut self, text: &str) -> Result<(), io::Error> {
|
||||
self.write_content(text, true, false, false, true)
|
||||
}
|
||||
|
||||
fn write_center_border_dim(&mut self, text: &str) -> Result<(), io::Error> {
|
||||
self.write_content(text, true, false, self.style, true)
|
||||
}
|
||||
|
||||
fn write_center_border_bold(&mut self, text: &str) -> Result<(), io::Error> {
|
||||
self.write_content(text, true, self.style, false, true)
|
||||
}
|
||||
|
||||
fn write_page_border(&mut self, border: [char; 3]) -> Result<(), io::Error> {
|
||||
for _ in 0..self.indent {
|
||||
self.write_str(INDENTATION)?;
|
||||
}
|
||||
|
||||
self.write_char(border[0])?;
|
||||
|
||||
for _ in 0..self.line_length() {
|
||||
self.write_char(border[1])?;
|
||||
}
|
||||
|
||||
self.write_char(border[2])?;
|
||||
self.write_char('\n')
|
||||
}
|
||||
|
||||
fn write_instruction_section(&mut self) -> Result<(), io::Error> {
|
||||
let mut column_name_line = String::new();
|
||||
|
||||
for (column_name, width) in INSTRUCTION_COLUMNS {
|
||||
column_name_line.push_str(&format!("│{column_name:^width$}", width = width));
|
||||
}
|
||||
|
||||
column_name_line.push('│');
|
||||
self.write_center_border_bold("Instructions")?;
|
||||
self.write_center_border(INSTRUCTION_BORDERS[0])?;
|
||||
self.write_center_border(&column_name_line)?;
|
||||
self.write_center_border(INSTRUCTION_BORDERS[1])?;
|
||||
|
||||
for (index, instruction) in self.chunk.instructions.iter().enumerate() {
|
||||
let position = self
|
||||
.chunk
|
||||
.positions
|
||||
.get(index)
|
||||
.map(|position| position.to_string())
|
||||
.unwrap_or("stripped".to_string());
|
||||
let operation = instruction.operation().to_string();
|
||||
let info = instruction.disassembly_info();
|
||||
let row = format!("│{index:^5}│{position:^12}│{operation:^17}│{info:^24}│");
|
||||
|
||||
self.write_center_border(&row)?;
|
||||
}
|
||||
|
||||
self.write_center_border(INSTRUCTION_BORDERS[2])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_local_section(&mut self) -> Result<(), io::Error> {
|
||||
let mut column_name_line = String::new();
|
||||
|
||||
for (column_name, width) in LOCAL_COLUMNS {
|
||||
column_name_line.push_str(&format!("│{:^width$}", column_name, width = width));
|
||||
}
|
||||
|
||||
column_name_line.push('│');
|
||||
self.write_center_border_bold("Locals")?;
|
||||
self.write_center_border(LOCAL_BORDERS[0])?;
|
||||
self.write_center_border(&column_name_line)?;
|
||||
self.write_center_border(LOCAL_BORDERS[1])?;
|
||||
|
||||
for (
|
||||
index,
|
||||
Local {
|
||||
identifier_index,
|
||||
register_index,
|
||||
scope,
|
||||
is_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 register_display = format!("R{register_index}");
|
||||
let scope = scope.to_string();
|
||||
let row = format!(
|
||||
"│{index:^5}│{identifier_display:^16}│{register_display:^10}│{scope:^7}│{is_mutable:^7}│"
|
||||
);
|
||||
|
||||
self.write_center_border(&row)?;
|
||||
}
|
||||
|
||||
self.write_center_border(LOCAL_BORDERS[2])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_constant_section(&mut self) -> Result<(), io::Error> {
|
||||
let mut column_name_line = String::new();
|
||||
|
||||
for (column_name, width) in CONSTANT_COLUMNS {
|
||||
column_name_line.push_str(&format!("│{:^width$}", column_name, width = width));
|
||||
}
|
||||
|
||||
column_name_line.push('│');
|
||||
self.write_center_border_bold("Constants")?;
|
||||
self.write_center_border(CONSTANT_BORDERS[0])?;
|
||||
self.write_center_border(&column_name_line)?;
|
||||
self.write_center_border(CONSTANT_BORDERS[1])?;
|
||||
|
||||
for (index, value) in self.chunk.constants.iter().enumerate() {
|
||||
let type_display = value.r#type().to_string();
|
||||
let value_display = {
|
||||
let mut value_string = value.to_string();
|
||||
|
||||
if value_string.len() > 26 {
|
||||
value_string = format!("{value_string:.23}...");
|
||||
}
|
||||
|
||||
value_string
|
||||
};
|
||||
let constant_display = format!("│{index:^5}│{type_display:^26}│{value_display:^26}│");
|
||||
|
||||
self.write_center_border(&constant_display)?;
|
||||
}
|
||||
|
||||
self.write_center_border(CONSTANT_BORDERS[2])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_prototype_section(&mut self) -> Result<(), io::Error> {
|
||||
self.write_center_border_bold("Functions")?;
|
||||
|
||||
for chunk in &self.chunk.prototypes {
|
||||
chunk
|
||||
.disassembler(self.writer)
|
||||
.indent(self.indent + 1)
|
||||
.width(self.width)
|
||||
.style(true)
|
||||
.show_type(true)
|
||||
.disassemble()?;
|
||||
|
||||
self.write_center_border("")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn disassemble(&mut self) -> Result<(), io::Error> {
|
||||
self.write_page_border(TOP_BORDER)?;
|
||||
|
||||
if let Some(name) = &self.chunk.name {
|
||||
self.write_center_border_bold(name)?;
|
||||
}
|
||||
|
||||
if self.show_type {
|
||||
let type_display = self.chunk.r#type.to_string();
|
||||
|
||||
self.write_center_border(&type_display)?;
|
||||
}
|
||||
|
||||
if let Some(source) = self.source {
|
||||
let lazily_formatted = source.split_whitespace().collect::<Vec<&str>>().join(" ");
|
||||
|
||||
self.write_center_border("")?;
|
||||
self.write_center_border(&lazily_formatted)?;
|
||||
self.write_center_border("")?;
|
||||
}
|
||||
|
||||
let info_line = format!(
|
||||
"{} instructions, {} constants, {} locals, returns {}",
|
||||
self.chunk.instructions.len(),
|
||||
self.chunk.constants.len(),
|
||||
self.chunk.locals.len(),
|
||||
self.chunk.r#type.return_type
|
||||
);
|
||||
|
||||
self.write_center_border_dim(&info_line)?;
|
||||
self.write_center_border("")?;
|
||||
|
||||
if !self.chunk.instructions.is_empty() {
|
||||
self.write_instruction_section()?;
|
||||
}
|
||||
|
||||
if !self.chunk.locals.is_empty() {
|
||||
self.write_local_section()?;
|
||||
}
|
||||
|
||||
if !self.chunk.constants.is_empty() {
|
||||
self.write_constant_section()?;
|
||||
}
|
||||
|
||||
if !self.chunk.prototypes.is_empty() {
|
||||
self.write_prototype_section()?;
|
||||
}
|
||||
|
||||
self.write_page_border(BOTTOM_BORDER)
|
||||
}
|
||||
}
|
31
dust-lang/src/chunk/local.rs
Normal file
31
dust-lang/src/chunk/local.rs
Normal file
@ -0,0 +1,31 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::Scope;
|
||||
|
||||
/// 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,
|
||||
|
||||
/// Stack index where the local's value is stored.
|
||||
pub register_index: u8,
|
||||
|
||||
/// Whether the local is mutable.
|
||||
pub is_mutable: bool,
|
||||
|
||||
/// Scope where the variable was declared.
|
||||
pub scope: Scope,
|
||||
}
|
||||
|
||||
impl Local {
|
||||
/// Creates a new Local instance.
|
||||
pub fn new(identifier_index: u8, register_index: u8, is_mutable: bool, scope: Scope) -> Self {
|
||||
Self {
|
||||
identifier_index,
|
||||
register_index,
|
||||
is_mutable,
|
||||
scope,
|
||||
}
|
||||
}
|
||||
}
|
131
dust-lang/src/chunk/mod.rs
Normal file
131
dust-lang/src/chunk/mod.rs
Normal file
@ -0,0 +1,131 @@
|
||||
//! Representation of a Dust program or function.
|
||||
//!
|
||||
//! A chunk is output by the compiler to represent all the information needed to execute a Dust
|
||||
//! program. In addition to the program itself, each function in the source is compiled into its own
|
||||
//! chunk and stored in the `prototypes` field of its parent. Thus, a chunk can also represent a
|
||||
//! function prototype.
|
||||
//!
|
||||
//! Chunks have a name when they belong to a named function. They also have a type, so the input
|
||||
//! parameters and the type of the return value are statically known. The [`Chunk::stack_size`]
|
||||
//! field can provide the necessary stack size that will be needed by the virtual machine. Chunks
|
||||
//! cannot be instantiated directly and must be created by the compiler. However, when the Rust
|
||||
//! compiler is in the "test" or "debug_assertions" configuration (used for all types of test),
|
||||
//! [`Chunk::with_data`] can be used to create a chunk for comparison to the compiler output. Do not
|
||||
//! try to run these chunks in a virtual machine. Due to their missing stack size and record index,
|
||||
//! they will cause a panic or undefined behavior.
|
||||
mod disassembler;
|
||||
mod local;
|
||||
mod scope;
|
||||
|
||||
pub use disassembler::Disassembler;
|
||||
pub use local::Local;
|
||||
pub use scope::Scope;
|
||||
|
||||
use std::fmt::{self, Debug, Display, Formatter, Write as FmtWrite};
|
||||
use std::io::Write;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{DustString, Function, FunctionType, Instruction, Span, Value};
|
||||
|
||||
/// Representation of a Dust program or function.
|
||||
///
|
||||
/// See the [module-level documentation](index.html) for more information.
|
||||
#[derive(Clone, PartialOrd, Serialize, Deserialize)]
|
||||
pub struct Chunk {
|
||||
pub(crate) name: Option<DustString>,
|
||||
pub(crate) r#type: FunctionType,
|
||||
|
||||
pub(crate) instructions: Vec<Instruction>,
|
||||
pub(crate) positions: Vec<Span>,
|
||||
pub(crate) constants: Vec<Value>,
|
||||
pub(crate) locals: Vec<Local>,
|
||||
pub(crate) prototypes: Vec<Chunk>,
|
||||
|
||||
pub(crate) register_count: usize,
|
||||
pub(crate) prototype_index: u8,
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
#[cfg(any(test, debug_assertions))]
|
||||
pub fn with_data(
|
||||
name: Option<DustString>,
|
||||
r#type: FunctionType,
|
||||
instructions: impl Into<Vec<Instruction>>,
|
||||
positions: impl Into<Vec<Span>>,
|
||||
constants: impl Into<Vec<Value>>,
|
||||
locals: impl Into<Vec<Local>>,
|
||||
prototypes: Vec<Chunk>,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
r#type,
|
||||
instructions: instructions.into(),
|
||||
positions: positions.into(),
|
||||
constants: constants.into(),
|
||||
locals: locals.into(),
|
||||
prototypes,
|
||||
register_count: 0,
|
||||
prototype_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_function(&self) -> Function {
|
||||
Function {
|
||||
name: self.name.clone(),
|
||||
r#type: self.r#type.clone(),
|
||||
prototype_index: self.prototype_index,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disassembler<'a, W: Write>(&'a self, writer: &'a mut W) -> Disassembler<'a, W> {
|
||||
Disassembler::new(self, writer)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Chunk {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
let mut output = Vec::new();
|
||||
|
||||
self.disassembler(&mut output)
|
||||
.style(true)
|
||||
.disassemble()
|
||||
.unwrap();
|
||||
|
||||
let string = String::from_utf8_lossy(&output);
|
||||
|
||||
write!(f, "{string}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Chunk {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut output = Vec::new();
|
||||
|
||||
self.disassembler(&mut output)
|
||||
.style(true)
|
||||
.disassemble()
|
||||
.unwrap();
|
||||
|
||||
let string = String::from_utf8_lossy(&output);
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
f.write_char('\n')?; // Improves readability in Cargo test output
|
||||
}
|
||||
|
||||
write!(f, "{string}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Chunk {}
|
||||
|
||||
impl PartialEq for Chunk {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.name == other.name
|
||||
&& self.r#type == other.r#type
|
||||
&& self.instructions == other.instructions
|
||||
&& self.constants == other.constants
|
||||
&& self.locals == other.locals
|
||||
&& self.prototypes == other.prototypes
|
||||
}
|
||||
}
|
@ -47,6 +47,6 @@ impl Scope {
|
||||
|
||||
impl Display for Scope {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "({}, {})", self.depth, self.block_index)
|
||||
write!(f, "{}.{}", self.depth, self.block_index)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
267
dust-lang/src/compiler/error.rs
Normal file
267
dust-lang/src/compiler/error.rs
Normal file
@ -0,0 +1,267 @@
|
||||
use std::num::{ParseFloatError, ParseIntError};
|
||||
|
||||
use crate::{AnnotatedError, LexError, Scope, Span, TokenKind, TokenOwned, Type, TypeConflict};
|
||||
|
||||
/// Compilation errors
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum CompileError {
|
||||
// Token errors
|
||||
ExpectedToken {
|
||||
expected: TokenKind,
|
||||
found: TokenOwned,
|
||||
position: Span,
|
||||
},
|
||||
ExpectedTokenMultiple {
|
||||
expected: &'static [TokenKind],
|
||||
found: TokenOwned,
|
||||
position: Span,
|
||||
},
|
||||
|
||||
// Parsing errors
|
||||
ComparisonChain {
|
||||
position: Span,
|
||||
},
|
||||
ExpectedBoolean {
|
||||
found: TokenOwned,
|
||||
position: Span,
|
||||
},
|
||||
ExpectedExpression {
|
||||
found: TokenOwned,
|
||||
position: Span,
|
||||
},
|
||||
ExpectedFunction {
|
||||
found: TokenOwned,
|
||||
actual_type: Type,
|
||||
position: Span,
|
||||
},
|
||||
ExpectedFunctionType {
|
||||
found: Type,
|
||||
position: Span,
|
||||
},
|
||||
InvalidAssignmentTarget {
|
||||
found: TokenOwned,
|
||||
position: Span,
|
||||
},
|
||||
UnexpectedReturn {
|
||||
position: Span,
|
||||
},
|
||||
|
||||
// Variable errors
|
||||
CannotMutateImmutableVariable {
|
||||
identifier: String,
|
||||
position: Span,
|
||||
},
|
||||
ExpectedMutableVariable {
|
||||
found: TokenOwned,
|
||||
position: Span,
|
||||
},
|
||||
UndeclaredVariable {
|
||||
identifier: String,
|
||||
position: Span,
|
||||
},
|
||||
VariableOutOfScope {
|
||||
identifier: String,
|
||||
variable_scope: Scope,
|
||||
access_scope: Scope,
|
||||
position: Span,
|
||||
},
|
||||
|
||||
// Type errors
|
||||
CannotAddType {
|
||||
argument_type: Type,
|
||||
position: Span,
|
||||
},
|
||||
CannotAddArguments {
|
||||
left_type: Type,
|
||||
left_position: Span,
|
||||
right_type: Type,
|
||||
right_position: Span,
|
||||
},
|
||||
CannotDivideType {
|
||||
argument_type: Type,
|
||||
position: Span,
|
||||
},
|
||||
CannotDivideArguments {
|
||||
left_type: Type,
|
||||
right_type: Type,
|
||||
position: Span,
|
||||
},
|
||||
CannotModuloType {
|
||||
argument_type: Type,
|
||||
position: Span,
|
||||
},
|
||||
CannotModuloArguments {
|
||||
left_type: Type,
|
||||
right_type: Type,
|
||||
position: Span,
|
||||
},
|
||||
CannotMultiplyType {
|
||||
argument_type: Type,
|
||||
position: Span,
|
||||
},
|
||||
CannotMultiplyArguments {
|
||||
left_type: Type,
|
||||
right_type: Type,
|
||||
position: Span,
|
||||
},
|
||||
CannotSubtractType {
|
||||
argument_type: Type,
|
||||
position: Span,
|
||||
},
|
||||
CannotSubtractArguments {
|
||||
left_type: Type,
|
||||
right_type: Type,
|
||||
position: Span,
|
||||
},
|
||||
CannotResolveRegisterType {
|
||||
register_index: usize,
|
||||
position: Span,
|
||||
},
|
||||
CannotResolveVariableType {
|
||||
identifier: String,
|
||||
position: Span,
|
||||
},
|
||||
IfElseBranchMismatch {
|
||||
conflict: TypeConflict,
|
||||
position: Span,
|
||||
},
|
||||
IfMissingElse {
|
||||
position: Span,
|
||||
},
|
||||
ListItemTypeConflict {
|
||||
conflict: TypeConflict,
|
||||
position: Span,
|
||||
},
|
||||
ReturnTypeConflict {
|
||||
conflict: TypeConflict,
|
||||
position: Span,
|
||||
},
|
||||
|
||||
// Chunk errors
|
||||
ConstantIndexOutOfBounds {
|
||||
index: usize,
|
||||
position: Span,
|
||||
},
|
||||
InstructionIndexOutOfBounds {
|
||||
index: usize,
|
||||
position: Span,
|
||||
},
|
||||
LocalIndexOutOfBounds {
|
||||
index: usize,
|
||||
position: Span,
|
||||
},
|
||||
|
||||
// Wrappers around foreign errors
|
||||
Lex(LexError),
|
||||
ParseFloatError {
|
||||
error: ParseFloatError,
|
||||
position: Span,
|
||||
},
|
||||
ParseIntError {
|
||||
error: ParseIntError,
|
||||
position: Span,
|
||||
},
|
||||
}
|
||||
|
||||
impl CompileError {}
|
||||
|
||||
impl AnnotatedError for CompileError {
|
||||
fn title() -> &'static str {
|
||||
"Compilation Error"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
match self {
|
||||
Self::CannotAddArguments { .. } => "Cannot add these types",
|
||||
Self::CannotAddType { .. } => "Cannot add to this type",
|
||||
Self::ComparisonChain { .. } => "Cannot chain comparison operations",
|
||||
Self::CannotDivideArguments { .. } => "Cannot divide these types",
|
||||
Self::CannotDivideType { .. } => "Cannot divide this type",
|
||||
Self::CannotModuloArguments { .. } => "Cannot modulo these types",
|
||||
Self::CannotModuloType { .. } => "Cannot modulo this type",
|
||||
Self::CannotMutateImmutableVariable { .. } => "Cannot mutate immutable variable",
|
||||
Self::CannotMultiplyArguments { .. } => "Cannot multiply these types",
|
||||
Self::CannotMultiplyType { .. } => "Cannot multiply this type",
|
||||
Self::CannotResolveRegisterType { .. } => "Cannot resolve register type",
|
||||
Self::CannotResolveVariableType { .. } => "Cannot resolve type",
|
||||
Self::CannotSubtractType { .. } => "Cannot subtract from this type",
|
||||
Self::CannotSubtractArguments { .. } => "Cannot subtract these types",
|
||||
Self::ConstantIndexOutOfBounds { .. } => "Constant index out of bounds",
|
||||
Self::ExpectedBoolean { .. } => "Expected a boolean",
|
||||
Self::ExpectedExpression { .. } => "Expected an expression",
|
||||
Self::ExpectedFunction { .. } => "Expected a function",
|
||||
Self::ExpectedFunctionType { .. } => "Expected a function type",
|
||||
Self::ExpectedMutableVariable { .. } => "Expected a mutable variable",
|
||||
Self::ExpectedToken { .. } => "Expected a specific token",
|
||||
Self::ExpectedTokenMultiple { .. } => "Expected one of multiple tokens",
|
||||
Self::IfElseBranchMismatch { .. } => "Type mismatch in if/else branches",
|
||||
Self::IfMissingElse { .. } => "If statement missing else branch",
|
||||
Self::InstructionIndexOutOfBounds { .. } => "Instruction index out of bounds",
|
||||
Self::InvalidAssignmentTarget { .. } => "Invalid assignment target",
|
||||
Self::Lex(error) => error.description(),
|
||||
Self::ListItemTypeConflict { .. } => "List item type conflict",
|
||||
Self::LocalIndexOutOfBounds { .. } => "Local index out of bounds",
|
||||
Self::ParseFloatError { .. } => "Failed to parse float",
|
||||
Self::ParseIntError { .. } => "Failed to parse integer",
|
||||
Self::ReturnTypeConflict { .. } => "Return type conflict",
|
||||
Self::UndeclaredVariable { .. } => "Undeclared variable",
|
||||
Self::UnexpectedReturn { .. } => "Unexpected return",
|
||||
Self::VariableOutOfScope { .. } => "Variable out of scope",
|
||||
}
|
||||
}
|
||||
|
||||
fn detail_snippets(&self) -> Vec<(String, Span)> {
|
||||
match self {
|
||||
Self::CannotAddArguments {
|
||||
left_type,
|
||||
left_position,
|
||||
right_type,
|
||||
right_position,
|
||||
} => {
|
||||
vec![
|
||||
(
|
||||
format!("A value of type \"{left_type}\" was used here."),
|
||||
*left_position,
|
||||
),
|
||||
(
|
||||
format!("A value of type \"{right_type}\" was used here."),
|
||||
*right_position,
|
||||
),
|
||||
]
|
||||
}
|
||||
Self::ReturnTypeConflict { conflict, position } => {
|
||||
vec![(
|
||||
format!(
|
||||
"Expected type {} but found type {}",
|
||||
conflict.expected, conflict.actual
|
||||
),
|
||||
*position,
|
||||
)]
|
||||
}
|
||||
_ => Vec::with_capacity(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn help_snippets(&self) -> Vec<(String, Span)> {
|
||||
match self {
|
||||
Self::CannotAddArguments {
|
||||
left_type,
|
||||
left_position,
|
||||
right_type,
|
||||
right_position,
|
||||
} => {
|
||||
vec![(
|
||||
format!("Type \"{left_type}\" cannot be added to type \"{right_type}\". Try converting one of the values to the other type."),
|
||||
Span(left_position.0, right_position.1)
|
||||
)]
|
||||
}
|
||||
_ => Vec::with_capacity(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LexError> for CompileError {
|
||||
fn from(error: LexError) -> Self {
|
||||
Self::Lex(error)
|
||||
}
|
||||
}
|
1785
dust-lang/src/compiler/mod.rs
Normal file
1785
dust-lang/src/compiler/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
64
dust-lang/src/compiler/optimize.rs
Normal file
64
dust-lang/src/compiler/optimize.rs
Normal file
@ -0,0 +1,64 @@
|
||||
//! Functions used by the compiler to optimize a chunk's bytecode during compilation.
|
||||
|
||||
use tracing::debug;
|
||||
|
||||
use crate::{Compiler, Instruction, Operation};
|
||||
|
||||
/// Optimizes a control flow pattern to use fewer registers and avoid using a `POINT` instruction.
|
||||
/// Use this after parsing an if/else statement.
|
||||
///
|
||||
/// This makes the following examples compile to the same bytecode:
|
||||
///
|
||||
/// ```dust
|
||||
/// 4 == 4
|
||||
/// ```
|
||||
///
|
||||
/// ```dust
|
||||
/// if 4 == 4 { true } else { false }
|
||||
/// ```
|
||||
///
|
||||
/// When they occur in the sequence shown below, instructions can be optimized by taking advantage
|
||||
/// of the loaders' ability to skip an instruction after loading a value. If these instructions are
|
||||
/// the result of a binary expression, this will not change anything because they were already
|
||||
/// emitted optimally. Control flow patterns, however, can be optimized because the load
|
||||
/// instructions are from seperate expressions that each uses its own register. Since only one of
|
||||
/// the two branches will be executed, this is wasteful. It would also require the compiler to emit
|
||||
/// a `POINT` instruction to prevent the VM from encountering an empty register.
|
||||
///
|
||||
/// The instructions must be in the following order:
|
||||
/// - `EQUAL` | `LESS` | `LESS_EQUAL` | `TEST`
|
||||
/// - `JUMP`
|
||||
/// - `LOAD_BOOLEAN` or `LOAD_CONSTANT`
|
||||
/// - `LOAD_BOOLEAN` or `LOAD_CONSTANT`
|
||||
///
|
||||
/// This optimization was taken from `A No-Frills Introduction to Lua 5.1 VM Instructions` by
|
||||
/// Kein-Hong Man.
|
||||
pub fn control_flow_register_consolidation(compiler: &mut Compiler) {
|
||||
if !matches!(
|
||||
compiler.get_last_operations(),
|
||||
Some([
|
||||
Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL | Operation::TEST,
|
||||
Operation::JUMP,
|
||||
Operation::LOAD_BOOLEAN | Operation::LOAD_CONSTANT,
|
||||
Operation::LOAD_BOOLEAN | Operation::LOAD_CONSTANT,
|
||||
])
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
debug!("Consolidating registers for control flow optimization");
|
||||
|
||||
let first_loader_index = compiler.instructions.len() - 2;
|
||||
let (first_loader, _, _) = &mut compiler.instructions.get_mut(first_loader_index).unwrap();
|
||||
let first_loader_destination = first_loader.a_field();
|
||||
*first_loader =
|
||||
Instruction::load_boolean(first_loader.a_field(), first_loader.b_field() != 0, true);
|
||||
|
||||
let second_loader_index = compiler.instructions.len() - 1;
|
||||
let (second_loader, _, _) = &mut compiler.instructions.get_mut(second_loader_index).unwrap();
|
||||
*second_loader = Instruction::load_boolean(
|
||||
first_loader_destination,
|
||||
second_loader.b_field() != 0,
|
||||
false,
|
||||
);
|
||||
}
|
320
dust-lang/src/compiler/parse_rule.rs
Normal file
320
dust-lang/src/compiler/parse_rule.rs
Normal file
@ -0,0 +1,320 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use crate::Token;
|
||||
|
||||
use super::{CompileError, Compiler};
|
||||
|
||||
pub type Parser<'a> = fn(&mut Compiler<'a>) -> Result<(), CompileError>;
|
||||
|
||||
/// Rule that defines how to parse a token.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ParseRule<'a> {
|
||||
pub prefix: Option<Parser<'a>>,
|
||||
pub infix: Option<Parser<'a>>,
|
||||
pub precedence: Precedence,
|
||||
}
|
||||
|
||||
impl From<&Token<'_>> for ParseRule<'_> {
|
||||
fn from(token: &Token) -> Self {
|
||||
match token {
|
||||
Token::ArrowThin => ParseRule {
|
||||
prefix: Some(Compiler::expect_expression),
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::Async => todo!(),
|
||||
Token::Bang => ParseRule {
|
||||
prefix: Some(Compiler::parse_unary),
|
||||
infix: None,
|
||||
precedence: Precedence::Unary,
|
||||
},
|
||||
Token::BangEqual => ParseRule {
|
||||
prefix: None,
|
||||
infix: Some(Compiler::parse_comparison_binary),
|
||||
precedence: Precedence::Comparison,
|
||||
},
|
||||
Token::Bool => ParseRule {
|
||||
prefix: Some(Compiler::expect_expression),
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::Boolean(_) => ParseRule {
|
||||
prefix: Some(Compiler::parse_boolean),
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::Break => todo!(),
|
||||
Token::Byte(_) => ParseRule {
|
||||
prefix: Some(Compiler::parse_byte),
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::Character(_) => ParseRule {
|
||||
prefix: Some(Compiler::parse_character),
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::Colon => ParseRule {
|
||||
prefix: Some(Compiler::expect_expression),
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::Comma => ParseRule {
|
||||
prefix: None,
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::Dot => ParseRule {
|
||||
prefix: Some(Compiler::expect_expression),
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::DoubleAmpersand => ParseRule {
|
||||
prefix: None,
|
||||
infix: Some(Compiler::parse_logical_binary),
|
||||
precedence: Precedence::LogicalAnd,
|
||||
},
|
||||
Token::DoubleEqual => ParseRule {
|
||||
prefix: None,
|
||||
infix: Some(Compiler::parse_comparison_binary),
|
||||
precedence: Precedence::Comparison,
|
||||
},
|
||||
Token::DoublePipe => ParseRule {
|
||||
prefix: None,
|
||||
infix: Some(Compiler::parse_logical_binary),
|
||||
precedence: Precedence::LogicalOr,
|
||||
},
|
||||
Token::DoubleDot => ParseRule {
|
||||
prefix: Some(Compiler::expect_expression),
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::Eof => ParseRule {
|
||||
prefix: None,
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::Equal => ParseRule {
|
||||
prefix: None,
|
||||
infix: None,
|
||||
precedence: Precedence::Assignment,
|
||||
},
|
||||
Token::Else => ParseRule {
|
||||
prefix: None,
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::Float(_) => ParseRule {
|
||||
prefix: Some(Compiler::parse_float),
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::FloatKeyword => ParseRule {
|
||||
prefix: Some(Compiler::expect_expression),
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::Fn => ParseRule {
|
||||
prefix: Some(Compiler::parse_function),
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::Greater => ParseRule {
|
||||
prefix: None,
|
||||
infix: Some(Compiler::parse_comparison_binary),
|
||||
precedence: Precedence::Comparison,
|
||||
},
|
||||
Token::GreaterEqual => ParseRule {
|
||||
prefix: None,
|
||||
infix: Some(Compiler::parse_comparison_binary),
|
||||
precedence: Precedence::Comparison,
|
||||
},
|
||||
Token::Identifier(_) => ParseRule {
|
||||
prefix: Some(Compiler::parse_variable),
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::If => ParseRule {
|
||||
prefix: Some(Compiler::parse_if),
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::Int => ParseRule {
|
||||
prefix: Some(Compiler::expect_expression),
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::Integer(_) => ParseRule {
|
||||
prefix: Some(Compiler::parse_integer),
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::LeftBrace => ParseRule {
|
||||
prefix: Some(Compiler::parse_block),
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::LeftParenthesis => ParseRule {
|
||||
prefix: Some(Compiler::parse_grouped),
|
||||
infix: Some(Compiler::parse_call),
|
||||
precedence: Precedence::Call,
|
||||
},
|
||||
Token::LeftBracket => ParseRule {
|
||||
prefix: Some(Compiler::parse_list),
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::Less => ParseRule {
|
||||
prefix: None,
|
||||
infix: Some(Compiler::parse_comparison_binary),
|
||||
precedence: Precedence::Comparison,
|
||||
},
|
||||
Token::LessEqual => ParseRule {
|
||||
prefix: None,
|
||||
infix: Some(Compiler::parse_comparison_binary),
|
||||
precedence: Precedence::Comparison,
|
||||
},
|
||||
Token::Let => ParseRule {
|
||||
prefix: Some(Compiler::parse_let_statement),
|
||||
infix: None,
|
||||
precedence: Precedence::Assignment,
|
||||
},
|
||||
Token::Loop => todo!(),
|
||||
Token::Map => todo!(),
|
||||
Token::Minus => ParseRule {
|
||||
prefix: Some(Compiler::parse_unary),
|
||||
infix: Some(Compiler::parse_math_binary),
|
||||
precedence: Precedence::Term,
|
||||
},
|
||||
Token::MinusEqual => ParseRule {
|
||||
prefix: None,
|
||||
infix: Some(Compiler::parse_math_binary),
|
||||
precedence: Precedence::Assignment,
|
||||
},
|
||||
Token::Mut => ParseRule {
|
||||
prefix: None,
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::Percent => ParseRule {
|
||||
prefix: None,
|
||||
infix: Some(Compiler::parse_math_binary),
|
||||
precedence: Precedence::Factor,
|
||||
},
|
||||
Token::PercentEqual => ParseRule {
|
||||
prefix: None,
|
||||
infix: Some(Compiler::parse_math_binary),
|
||||
precedence: Precedence::Assignment,
|
||||
},
|
||||
Token::Plus => ParseRule {
|
||||
prefix: None,
|
||||
infix: Some(Compiler::parse_math_binary),
|
||||
precedence: Precedence::Term,
|
||||
},
|
||||
Token::PlusEqual => ParseRule {
|
||||
prefix: None,
|
||||
infix: Some(Compiler::parse_math_binary),
|
||||
precedence: Precedence::Assignment,
|
||||
},
|
||||
Token::Return => ParseRule {
|
||||
prefix: Some(Compiler::parse_return_statement),
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::RightBrace => ParseRule {
|
||||
prefix: None,
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::RightParenthesis => ParseRule {
|
||||
prefix: None,
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::RightBracket => ParseRule {
|
||||
prefix: None,
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::Semicolon => ParseRule {
|
||||
prefix: Some(Compiler::parse_semicolon),
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::Slash => ParseRule {
|
||||
prefix: None,
|
||||
infix: Some(Compiler::parse_math_binary),
|
||||
precedence: Precedence::Factor,
|
||||
},
|
||||
Token::SlashEqual => ParseRule {
|
||||
prefix: None,
|
||||
infix: Some(Compiler::parse_math_binary),
|
||||
precedence: Precedence::Assignment,
|
||||
},
|
||||
Token::Star => ParseRule {
|
||||
prefix: None,
|
||||
infix: Some(Compiler::parse_math_binary),
|
||||
precedence: Precedence::Factor,
|
||||
},
|
||||
Token::StarEqual => ParseRule {
|
||||
prefix: None,
|
||||
infix: Some(Compiler::parse_math_binary),
|
||||
precedence: Precedence::Assignment,
|
||||
},
|
||||
Token::Str => ParseRule {
|
||||
prefix: Some(Compiler::expect_expression),
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::String(_) => ParseRule {
|
||||
prefix: Some(Compiler::parse_string),
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
Token::Struct => todo!(),
|
||||
Token::While => ParseRule {
|
||||
prefix: Some(Compiler::parse_while),
|
||||
infix: None,
|
||||
precedence: Precedence::None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Operator precedence levels.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Precedence {
|
||||
Primary = 9,
|
||||
Call = 8,
|
||||
Unary = 7,
|
||||
Factor = 6,
|
||||
Term = 5,
|
||||
Comparison = 4,
|
||||
LogicalAnd = 3,
|
||||
LogicalOr = 2,
|
||||
Assignment = 1,
|
||||
None = 0,
|
||||
}
|
||||
|
||||
impl Precedence {
|
||||
pub fn increment(&self) -> Self {
|
||||
match self {
|
||||
Precedence::None => Precedence::Assignment,
|
||||
Precedence::Assignment => Precedence::LogicalOr,
|
||||
Precedence::LogicalOr => Precedence::LogicalAnd,
|
||||
Precedence::LogicalAnd => Precedence::Comparison,
|
||||
Precedence::Comparison => Precedence::Term,
|
||||
Precedence::Term => Precedence::Factor,
|
||||
Precedence::Factor => Precedence::Unary,
|
||||
Precedence::Unary => Precedence::Call,
|
||||
Precedence::Call => Precedence::Primary,
|
||||
Precedence::Primary => Precedence::Primary,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Precedence {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
200
dust-lang/src/compiler/type_checks.rs
Normal file
200
dust-lang/src/compiler/type_checks.rs
Normal file
@ -0,0 +1,200 @@
|
||||
use crate::{Span, Token, Type};
|
||||
|
||||
use super::CompileError;
|
||||
|
||||
pub fn check_math_type(
|
||||
r#type: &Type,
|
||||
operator: Token,
|
||||
position: &Span,
|
||||
) -> Result<(), CompileError> {
|
||||
match operator {
|
||||
Token::Plus => expect_addable_type(r#type, position),
|
||||
Token::Minus => expect_subtractable_type(r#type, position),
|
||||
Token::Star => expect_multipliable_type(r#type, position),
|
||||
Token::Slash => expect_dividable_type(r#type, position),
|
||||
Token::Percent => expect_modulable_type(r#type, position),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_math_types(
|
||||
left: &Type,
|
||||
left_position: &Span,
|
||||
operator: Token,
|
||||
right: &Type,
|
||||
right_position: &Span,
|
||||
) -> Result<(), CompileError> {
|
||||
match operator {
|
||||
Token::Plus => expect_addable_types(left, left_position, right, right_position),
|
||||
Token::Minus => expect_subtractable_types(left, left_position, right, right_position),
|
||||
Token::Star => expect_multipliable_types(left, left_position, right, right_position),
|
||||
Token::Slash => expect_dividable_types(left, left_position, right, right_position),
|
||||
Token::Percent => expect_modulable_types(left, left_position, right, right_position),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_addable_type(argument_type: &Type, position: &Span) -> Result<(), CompileError> {
|
||||
if matches!(
|
||||
argument_type,
|
||||
Type::Byte | Type::Character | Type::Float | Type::Integer | Type::String
|
||||
) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(CompileError::CannotAddType {
|
||||
argument_type: argument_type.clone(),
|
||||
position: *position,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_addable_types(
|
||||
left: &Type,
|
||||
left_position: &Span,
|
||||
right: &Type,
|
||||
right_position: &Span,
|
||||
) -> Result<(), CompileError> {
|
||||
if matches!(
|
||||
(left, right),
|
||||
(Type::Byte, Type::Byte)
|
||||
| (Type::Character, Type::String)
|
||||
| (Type::Character, Type::Character)
|
||||
| (Type::Float, Type::Float)
|
||||
| (Type::Integer, Type::Integer)
|
||||
| (Type::String, Type::Character)
|
||||
| (Type::String, Type::String),
|
||||
) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(CompileError::CannotAddArguments {
|
||||
left_type: left.clone(),
|
||||
left_position: *left_position,
|
||||
right_type: right.clone(),
|
||||
right_position: *right_position,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_dividable_type(argument_type: &Type, position: &Span) -> Result<(), CompileError> {
|
||||
if matches!(argument_type, Type::Byte | Type::Float | Type::Integer) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(CompileError::CannotDivideType {
|
||||
argument_type: argument_type.clone(),
|
||||
position: *position,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_dividable_types(
|
||||
left: &Type,
|
||||
left_position: &Span,
|
||||
right: &Type,
|
||||
right_position: &Span,
|
||||
) -> Result<(), CompileError> {
|
||||
if matches!(
|
||||
(left, right),
|
||||
(Type::Byte, Type::Byte) | (Type::Float, Type::Float) | (Type::Integer, Type::Integer)
|
||||
) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(CompileError::CannotDivideArguments {
|
||||
left_type: left.clone(),
|
||||
right_type: right.clone(),
|
||||
position: Span(left_position.0, right_position.1),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_modulable_type(argument_type: &Type, position: &Span) -> Result<(), CompileError> {
|
||||
if matches!(argument_type, Type::Byte | Type::Integer | Type::Float) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(CompileError::CannotModuloType {
|
||||
argument_type: argument_type.clone(),
|
||||
position: *position,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_modulable_types(
|
||||
left: &Type,
|
||||
left_position: &Span,
|
||||
right: &Type,
|
||||
right_position: &Span,
|
||||
) -> Result<(), CompileError> {
|
||||
if matches!(
|
||||
(left, right),
|
||||
(Type::Byte, Type::Byte) | (Type::Integer, Type::Integer) | (Type::Float, Type::Float)
|
||||
) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(CompileError::CannotModuloArguments {
|
||||
left_type: left.clone(),
|
||||
right_type: right.clone(),
|
||||
position: Span(left_position.0, right_position.1),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_multipliable_type(argument_type: &Type, position: &Span) -> Result<(), CompileError> {
|
||||
if matches!(argument_type, Type::Byte | Type::Float | Type::Integer) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(CompileError::CannotMultiplyType {
|
||||
argument_type: argument_type.clone(),
|
||||
position: *position,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_multipliable_types(
|
||||
left: &Type,
|
||||
left_position: &Span,
|
||||
right: &Type,
|
||||
right_position: &Span,
|
||||
) -> Result<(), CompileError> {
|
||||
if matches!(
|
||||
(left, right),
|
||||
(Type::Byte, Type::Byte) | (Type::Float, Type::Float) | (Type::Integer, Type::Integer)
|
||||
) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(CompileError::CannotMultiplyArguments {
|
||||
left_type: left.clone(),
|
||||
right_type: right.clone(),
|
||||
position: Span(left_position.0, right_position.1),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_subtractable_type(argument_type: &Type, position: &Span) -> Result<(), CompileError> {
|
||||
if matches!(argument_type, Type::Byte | Type::Float | Type::Integer) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(CompileError::CannotSubtractType {
|
||||
argument_type: argument_type.clone(),
|
||||
position: *position,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_subtractable_types(
|
||||
left: &Type,
|
||||
left_position: &Span,
|
||||
right: &Type,
|
||||
right_position: &Span,
|
||||
) -> Result<(), CompileError> {
|
||||
if matches!(
|
||||
(left, right),
|
||||
(Type::Byte, Type::Byte) | (Type::Float, Type::Float) | (Type::Integer, Type::Integer)
|
||||
) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(CompileError::CannotSubtractArguments {
|
||||
left_type: left.clone(),
|
||||
right_type: right.clone(),
|
||||
position: Span(left_position.0, right_position.1),
|
||||
})
|
||||
}
|
||||
}
|
@ -1,360 +0,0 @@
|
||||
//! Tool for disassembling chunks into a human-readable format.
|
||||
//!
|
||||
//! A disassembler can be created by calling [Chunk::disassembler][] or by instantiating one with
|
||||
//! [Disassembler::new][].
|
||||
//!
|
||||
//! # Options
|
||||
//!
|
||||
//! The disassembler can be customized with the 'styled' option, which will apply ANSI color codes
|
||||
//! to the output.
|
||||
//!
|
||||
//! If the 'source' option is set, the disassembler will include the source code in 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
|
||||
//! ┌──────────────────────────────────────────────────────────────────────────────┐
|
||||
//! │ dust │
|
||||
//! │ │
|
||||
//! │ write_line("hello_world") │
|
||||
//! │ │
|
||||
//! │ 3 instructions, 1 constants, 0 locals, returns none │
|
||||
//! │ │
|
||||
//! │ Instructions │
|
||||
//! │ ------------ │
|
||||
//! │ i POSITION OPERATION TYPE INFO │
|
||||
//! │ --- ---------- ------------- -------------- -------------------------------- │
|
||||
//! │ 0 (11, 24) LOAD_CONSTANT str R0 = C0 │
|
||||
//! │ 1 (0, 25) CALL_NATIVE none write_line(R0..R1) │
|
||||
//! │ 2 (25, 25) RETURN none │
|
||||
//! │┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈│
|
||||
//! │ Constants │
|
||||
//! │ --------- │
|
||||
//! │ i TYPE VALUE │
|
||||
//! │ --- ---------------- ----------------- │
|
||||
//! │ 0 str hello_world │
|
||||
//! └──────────────────────────────────────────────────────────────────────────────┘
|
||||
//! ```
|
||||
use std::env::current_exe;
|
||||
|
||||
use colored::Colorize;
|
||||
|
||||
use crate::{value::ConcreteValue, Chunk, Local};
|
||||
|
||||
const INSTRUCTION_HEADER: [&str; 4] = [
|
||||
"Instructions",
|
||||
"------------",
|
||||
" i POSITION OPERATION TYPE INFO ",
|
||||
"--- ---------- ------------- -------------- --------------------------------",
|
||||
];
|
||||
|
||||
const CONSTANT_HEADER: [&str; 4] = [
|
||||
"Constants",
|
||||
"---------",
|
||||
" i TYPE VALUE ",
|
||||
"--- ---------------- -----------------",
|
||||
];
|
||||
|
||||
const LOCAL_HEADER: [&str; 4] = [
|
||||
"Locals",
|
||||
"------",
|
||||
" i SCOPE MUTABLE TYPE IDENTIFIER ",
|
||||
"--- ------- ------- -------------------------------- ----------------",
|
||||
];
|
||||
|
||||
/// 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
|
||||
style: bool,
|
||||
indent: usize,
|
||||
}
|
||||
|
||||
impl<'a> Disassembler<'a> {
|
||||
pub fn new(chunk: &'a Chunk) -> Self {
|
||||
Self {
|
||||
output: String::new(),
|
||||
chunk,
|
||||
source: None,
|
||||
style: false,
|
||||
indent: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// The default width of the disassembly output, including borders.
|
||||
pub fn default_width() -> usize {
|
||||
let longest_line = INSTRUCTION_HEADER[3];
|
||||
|
||||
(longest_line.chars().count() + 2).max(80)
|
||||
}
|
||||
|
||||
pub fn source(mut self, source: &'a str) -> Self {
|
||||
self.source = Some(source);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn style(mut self, styled: bool) -> Self {
|
||||
self.style = styled;
|
||||
|
||||
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 mut content = line_characters.iter().collect::<String>();
|
||||
|
||||
if style_bold {
|
||||
content = content.bold().to_string();
|
||||
}
|
||||
|
||||
if style_dim {
|
||||
content = content.dimmed().to_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_source(&mut self, source: &str) {
|
||||
self.push(source, true, false, false, true);
|
||||
}
|
||||
|
||||
fn push_chunk_info(&mut self, info: &str) {
|
||||
self.push(info, true, false, true, true);
|
||||
}
|
||||
|
||||
fn push_header(&mut self, header: &str) {
|
||||
self.push(header, true, self.style, 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);
|
||||
}
|
||||
|
||||
fn push_instruction_section(&mut self) {
|
||||
for line in INSTRUCTION_HEADER {
|
||||
self.push_header(line);
|
||||
}
|
||||
|
||||
for (index, (instruction, r#type, position)) in self.chunk.instructions().iter().enumerate()
|
||||
{
|
||||
let position = position.to_string();
|
||||
let operation = instruction.operation().to_string();
|
||||
let type_display = {
|
||||
let mut type_string = r#type.to_string();
|
||||
|
||||
if type_string.len() > 14 {
|
||||
type_string = format!("{type_string:.11}...");
|
||||
}
|
||||
|
||||
type_string
|
||||
};
|
||||
let info = instruction.disassembly_info();
|
||||
let instruction_display =
|
||||
format!("{index:^3} {position:^10} {operation:13} {type_display:^14} {info:^32}");
|
||||
|
||||
self.push_details(&instruction_display);
|
||||
}
|
||||
}
|
||||
|
||||
fn push_local_section(&mut self) {
|
||||
for line in LOCAL_HEADER {
|
||||
self.push_header(line);
|
||||
}
|
||||
|
||||
for (
|
||||
index,
|
||||
Local {
|
||||
identifier_index,
|
||||
r#type,
|
||||
scope,
|
||||
is_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.to_string();
|
||||
let scope = scope.to_string();
|
||||
let local_display = format!(
|
||||
"{index:^3} {scope:^7} {is_mutable:^7} {type_display:^32} {identifier_display:^16}"
|
||||
);
|
||||
|
||||
self.push_details(&local_display);
|
||||
}
|
||||
}
|
||||
|
||||
fn push_constant_section(&mut self) {
|
||||
for line in CONSTANT_HEADER {
|
||||
self.push_header(line);
|
||||
}
|
||||
|
||||
for (index, value) in self.chunk.constants().iter().enumerate() {
|
||||
if let ConcreteValue::Function(chunk) = value {
|
||||
let mut function_disassembler = chunk.disassembler().style(self.style);
|
||||
|
||||
function_disassembler.indent = self.indent + 1;
|
||||
|
||||
let function_disassembly = function_disassembler.disassemble();
|
||||
|
||||
self.output.push_str(&function_disassembly);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let type_display = value.r#type().to_string();
|
||||
let value_display = {
|
||||
let mut value_string = value.to_string();
|
||||
|
||||
if value_string.len() > 15 {
|
||||
value_string = format!("{value_string:.12}...");
|
||||
}
|
||||
|
||||
value_string
|
||||
};
|
||||
let constant_display = format!("{index:^3} {type_display:^16} {value_display:^17}");
|
||||
|
||||
self.push_details(&constant_display);
|
||||
}
|
||||
}
|
||||
|
||||
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| {
|
||||
let path_string = path.to_string_lossy();
|
||||
let file_name = path_string
|
||||
.split('/')
|
||||
.last()
|
||||
.map(|slice| slice.to_string())
|
||||
.unwrap_or(path_string.to_string());
|
||||
|
||||
file_name
|
||||
})
|
||||
.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_source(&source.split_whitespace().collect::<Vec<&str>>().join(" "));
|
||||
self.push_empty();
|
||||
}
|
||||
|
||||
let info_line = format!(
|
||||
"{} instructions, {} constants, {} locals, returns {}",
|
||||
self.chunk.len(),
|
||||
self.chunk.constants().len(),
|
||||
self.chunk.locals().len(),
|
||||
self.chunk.r#type().return_type
|
||||
);
|
||||
|
||||
self.push_chunk_info(&info_line);
|
||||
self.push_empty();
|
||||
|
||||
if !self.chunk.is_empty() {
|
||||
self.push_instruction_section();
|
||||
}
|
||||
|
||||
if !self.chunk.locals().is_empty() {
|
||||
self.push_border(§ion_border);
|
||||
self.push_local_section();
|
||||
}
|
||||
|
||||
if !self.chunk.constants().is_empty() {
|
||||
self.push_border(§ion_border);
|
||||
self.push_constant_section();
|
||||
}
|
||||
|
||||
self.push_border(&bottom_border);
|
||||
|
||||
self.output.to_string()
|
||||
}
|
||||
}
|
@ -1,66 +1,80 @@
|
||||
//! Top-level Dust errors with source code annotations.
|
||||
//! Top-level error for the Dust language API that can create detailed reports with source code
|
||||
//! annotations.
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use annotate_snippets::{Level, Renderer, Snippet};
|
||||
|
||||
use crate::{vm::VmError, CompileError, Span};
|
||||
use crate::{CompileError, NativeFunctionError, Span};
|
||||
|
||||
/// A top-level error that can occur during the execution of Dust code.
|
||||
///
|
||||
/// This error can display nicely formatted messages with source code annotations.
|
||||
/// A top-level error that can occur during the interpretation of Dust code.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum DustError<'src> {
|
||||
Compile {
|
||||
error: CompileError,
|
||||
source: &'src str,
|
||||
},
|
||||
Runtime {
|
||||
error: VmError,
|
||||
NativeFunction {
|
||||
error: NativeFunctionError,
|
||||
source: &'src str,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'src> DustError<'src> {
|
||||
pub fn compile(error: CompileError, source: &'src str) -> Self {
|
||||
DustError::Compile { error, source }
|
||||
}
|
||||
|
||||
pub fn report(&self) -> String {
|
||||
let (title, description, detail_snippets, help_snippets) = match self {
|
||||
Self::Compile { error, .. } => (
|
||||
CompileError::title(),
|
||||
error.description(),
|
||||
error.detail_snippets(),
|
||||
error.help_snippets(),
|
||||
),
|
||||
Self::NativeFunction { error, .. } => (
|
||||
NativeFunctionError::title(),
|
||||
error.description(),
|
||||
error.detail_snippets(),
|
||||
error.help_snippets(),
|
||||
),
|
||||
};
|
||||
let label = format!("{}: {}", title, description);
|
||||
let message = Level::Error
|
||||
.title(&label)
|
||||
.snippets(detail_snippets.iter().map(|(details, position)| {
|
||||
Snippet::source(self.source())
|
||||
.annotation(Level::Info.span(position.0..position.1).label(details))
|
||||
}))
|
||||
.snippets(help_snippets.iter().map(|(help, position)| {
|
||||
Snippet::source(self.source())
|
||||
.annotation(Level::Help.span(position.0..position.1).label(help))
|
||||
}));
|
||||
let mut report = String::new();
|
||||
let renderer = Renderer::styled();
|
||||
|
||||
match self {
|
||||
DustError::Runtime { error, source } => {
|
||||
let position = error.position();
|
||||
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(false)
|
||||
.annotation(Level::Error.span(position.0..position.1).label(&details)),
|
||||
);
|
||||
|
||||
report.push_str(&renderer.render(message).to_string());
|
||||
}
|
||||
DustError::Compile { error, source } => {
|
||||
let position = error.position();
|
||||
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(false)
|
||||
.annotation(Level::Error.span(position.0..position.1).label(&details)),
|
||||
);
|
||||
|
||||
report.push_str(&renderer.render(message).to_string());
|
||||
}
|
||||
}
|
||||
|
||||
report
|
||||
}
|
||||
|
||||
fn source(&self) -> &str {
|
||||
match self {
|
||||
Self::Compile { source, .. } => source,
|
||||
Self::NativeFunction { source, .. } => source,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for DustError<'_> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.report())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AnnotatedError {
|
||||
fn title() -> &'static str;
|
||||
fn description(&self) -> &'static str;
|
||||
fn details(&self) -> Option<String>;
|
||||
fn position(&self) -> Span;
|
||||
fn detail_snippets(&self) -> Vec<(String, Span)>;
|
||||
fn help_snippets(&self) -> Vec<(String, Span)>;
|
||||
}
|
||||
|
@ -1,18 +1,14 @@
|
||||
use crate::{Argument, Destination, Instruction, Operation};
|
||||
use crate::{Argument, Instruction, Operation};
|
||||
|
||||
pub struct Add {
|
||||
pub destination: Destination,
|
||||
pub destination: u8,
|
||||
pub left: Argument,
|
||||
pub right: Argument,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for Add {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
let destination = if instruction.a_is_local() {
|
||||
Destination::Local(instruction.a())
|
||||
} else {
|
||||
Destination::Register(instruction.a())
|
||||
};
|
||||
impl From<Instruction> for Add {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let destination = instruction.a_field();
|
||||
let (left, right) = instruction.b_and_c_as_arguments();
|
||||
|
||||
Add {
|
||||
@ -25,19 +21,11 @@ impl From<&Instruction> for Add {
|
||||
|
||||
impl From<Add> for Instruction {
|
||||
fn from(add: Add) -> Self {
|
||||
let (a, a_is_local) = match add.destination {
|
||||
Destination::Local(local) => (local, true),
|
||||
Destination::Register(register) => (register, false),
|
||||
};
|
||||
let operation = Operation::ADD;
|
||||
let a = add.destination;
|
||||
let (b, b_is_constant) = add.left.as_index_and_constant_flag();
|
||||
let (c, c_is_constant) = add.right.as_index_and_constant_flag();
|
||||
|
||||
*Instruction::new(Operation::Add)
|
||||
.set_a(a)
|
||||
.set_a_is_local(a_is_local)
|
||||
.set_b(add.left.index())
|
||||
.set_b_is_constant(add.left.is_constant())
|
||||
.set_b_is_local(add.left.is_local())
|
||||
.set_c(add.right.index())
|
||||
.set_c_is_constant(add.right.is_constant())
|
||||
.set_c_is_local(add.right.is_local())
|
||||
Instruction::new(operation, a, b, c, b_is_constant, c_is_constant, false)
|
||||
}
|
||||
}
|
||||
|
@ -1,40 +1,35 @@
|
||||
use crate::{Argument, Destination, Instruction, Operation};
|
||||
use crate::{Instruction, Operation};
|
||||
|
||||
pub struct Call {
|
||||
pub destination: Destination,
|
||||
pub function: Argument,
|
||||
pub argument_count: u16,
|
||||
pub destination: u8,
|
||||
pub function_register: u8,
|
||||
pub argument_count: u8,
|
||||
pub is_recursive: bool,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for Call {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
let destination = if instruction.a_is_local() {
|
||||
Destination::Local(instruction.a())
|
||||
} else {
|
||||
Destination::Register(instruction.a())
|
||||
};
|
||||
impl From<Instruction> for Call {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let destination = instruction.a_field();
|
||||
let function_register = instruction.b_field();
|
||||
let argument_count = instruction.c_field();
|
||||
let is_recursive = instruction.d_field();
|
||||
|
||||
Call {
|
||||
destination,
|
||||
function: instruction.b_as_argument(),
|
||||
argument_count: instruction.c(),
|
||||
function_register,
|
||||
argument_count,
|
||||
is_recursive,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Call> for Instruction {
|
||||
fn from(call: Call) -> Self {
|
||||
let (a, a_is_local) = match call.destination {
|
||||
Destination::Local(local) => (local, true),
|
||||
Destination::Register(register) => (register, false),
|
||||
};
|
||||
let a = call.destination;
|
||||
let b = call.function_register;
|
||||
let c = call.argument_count;
|
||||
let d = call.is_recursive;
|
||||
|
||||
*Instruction::new(Operation::Call)
|
||||
.set_a(a)
|
||||
.set_a_is_local(a_is_local)
|
||||
.set_b(call.function.index())
|
||||
.set_b_is_constant(call.function.is_constant())
|
||||
.set_b_is_local(call.function.is_local())
|
||||
.set_c(call.argument_count)
|
||||
Instruction::new(Operation::CALL, a, b, c, false, false, d)
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +1,31 @@
|
||||
use crate::{Destination, Instruction, NativeFunction, Operation};
|
||||
use crate::{Instruction, NativeFunction, Operation};
|
||||
|
||||
pub struct CallNative {
|
||||
pub destination: Destination,
|
||||
pub destination: u8,
|
||||
pub function: NativeFunction,
|
||||
pub argument_count: u16,
|
||||
pub argument_count: u8,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for CallNative {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
let destination = if instruction.a_is_local() {
|
||||
Destination::Local(instruction.a())
|
||||
} else {
|
||||
Destination::Register(instruction.a())
|
||||
};
|
||||
impl From<Instruction> for CallNative {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let destination = instruction.a_field();
|
||||
let function = NativeFunction::from(instruction.b_field());
|
||||
|
||||
CallNative {
|
||||
destination,
|
||||
function: NativeFunction::from(instruction.b()),
|
||||
argument_count: instruction.c(),
|
||||
function,
|
||||
argument_count: instruction.c_field(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CallNative> for Instruction {
|
||||
fn from(call_native: CallNative) -> Self {
|
||||
let (a, a_is_local) = match call_native.destination {
|
||||
Destination::Local(local) => (local, true),
|
||||
Destination::Register(register) => (register, false),
|
||||
};
|
||||
let operation = Operation::CALL_NATIVE;
|
||||
let a = call_native.destination;
|
||||
let b = call_native.function as u8;
|
||||
let c = call_native.argument_count;
|
||||
|
||||
*Instruction::new(Operation::CallNative)
|
||||
.set_a(a)
|
||||
.set_a_is_local(a_is_local)
|
||||
.set_b(call_native.function as u16)
|
||||
.set_c(call_native.argument_count)
|
||||
Instruction::new(operation, a, b, c, false, false, false)
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,24 @@
|
||||
use crate::{Instruction, Operation};
|
||||
|
||||
pub struct Close {
|
||||
pub from: u16,
|
||||
pub to: u16,
|
||||
pub from: u8,
|
||||
pub to: u8,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for Close {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
impl From<Instruction> for Close {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
Close {
|
||||
from: instruction.b(),
|
||||
to: instruction.c(),
|
||||
from: instruction.b_field(),
|
||||
to: instruction.c_field(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Close> for Instruction {
|
||||
fn from(r#move: Close) -> Self {
|
||||
*Instruction::new(Operation::Close)
|
||||
.set_b(r#move.from)
|
||||
.set_c(r#move.to)
|
||||
fn from(close: Close) -> Self {
|
||||
let operation = Operation::CLOSE;
|
||||
let (a, b, c) = (0, close.from, close.to);
|
||||
|
||||
Instruction::new(operation, a, b, c, false, false, false)
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +0,0 @@
|
||||
use crate::{Instruction, Operation};
|
||||
|
||||
pub struct DefineLocal {
|
||||
pub register: u16,
|
||||
pub local_index: u16,
|
||||
pub is_mutable: bool,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for DefineLocal {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
DefineLocal {
|
||||
register: instruction.a(),
|
||||
local_index: instruction.b(),
|
||||
is_mutable: instruction.c_as_boolean(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DefineLocal> for Instruction {
|
||||
fn from(define_local: DefineLocal) -> Self {
|
||||
*Instruction::new(Operation::DefineLocal)
|
||||
.set_a(define_local.register)
|
||||
.set_b(define_local.local_index)
|
||||
.set_c_to_boolean(define_local.is_mutable)
|
||||
}
|
||||
}
|
@ -1,18 +1,14 @@
|
||||
use crate::{Argument, Destination, Instruction, Operation};
|
||||
use crate::{Argument, Instruction, Operation};
|
||||
|
||||
pub struct Divide {
|
||||
pub destination: Destination,
|
||||
pub destination: u8,
|
||||
pub left: Argument,
|
||||
pub right: Argument,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for Divide {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
let destination = if instruction.a_is_local() {
|
||||
Destination::Local(instruction.a())
|
||||
} else {
|
||||
Destination::Register(instruction.a())
|
||||
};
|
||||
impl From<Instruction> for Divide {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let destination = instruction.a_field();
|
||||
let (left, right) = instruction.b_and_c_as_arguments();
|
||||
|
||||
Divide {
|
||||
@ -25,19 +21,11 @@ impl From<&Instruction> for Divide {
|
||||
|
||||
impl From<Divide> for Instruction {
|
||||
fn from(divide: Divide) -> Self {
|
||||
let (a, a_is_local) = match divide.destination {
|
||||
Destination::Local(local) => (local, true),
|
||||
Destination::Register(register) => (register, false),
|
||||
};
|
||||
let operation = Operation::DIVIDE;
|
||||
let a = divide.destination;
|
||||
let (b, b_is_constant) = divide.left.as_index_and_constant_flag();
|
||||
let (c, c_is_constant) = divide.right.as_index_and_constant_flag();
|
||||
|
||||
*Instruction::new(Operation::Divide)
|
||||
.set_a(a)
|
||||
.set_a_is_local(a_is_local)
|
||||
.set_b(divide.left.index())
|
||||
.set_b_is_constant(divide.left.is_constant())
|
||||
.set_b_is_local(divide.left.is_local())
|
||||
.set_c(divide.right.index())
|
||||
.set_c_is_constant(divide.right.is_constant())
|
||||
.set_c_is_local(divide.right.is_local())
|
||||
Instruction::new(operation, a, b, c, b_is_constant, c_is_constant, false)
|
||||
}
|
||||
}
|
||||
|
@ -6,27 +6,22 @@ pub struct Equal {
|
||||
pub right: Argument,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for Equal {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
impl From<Instruction> for Equal {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let value = instruction.d_field();
|
||||
let (left, right) = instruction.b_and_c_as_arguments();
|
||||
|
||||
Equal {
|
||||
value: instruction.a_as_boolean(),
|
||||
left,
|
||||
right,
|
||||
}
|
||||
Equal { value, left, right }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Equal> for Instruction {
|
||||
fn from(equal: Equal) -> Self {
|
||||
*Instruction::new(Operation::Equal)
|
||||
.set_a_to_boolean(equal.value)
|
||||
.set_b(equal.left.index())
|
||||
.set_b_is_constant(equal.left.is_constant())
|
||||
.set_b_is_local(equal.left.is_local())
|
||||
.set_c(equal.right.index())
|
||||
.set_c_is_constant(equal.right.is_constant())
|
||||
.set_c_is_local(equal.right.is_local())
|
||||
let operation = Operation::EQUAL;
|
||||
let (b, b_is_constant) = equal.left.as_index_and_constant_flag();
|
||||
let (c, c_is_constant) = equal.right.as_index_and_constant_flag();
|
||||
let d = equal.value;
|
||||
|
||||
Instruction::new(operation, 0, b, c, b_is_constant, c_is_constant, d)
|
||||
}
|
||||
}
|
||||
|
@ -1,35 +1,28 @@
|
||||
use crate::{Destination, Instruction, Operation};
|
||||
use crate::{Instruction, Operation};
|
||||
|
||||
pub struct GetLocal {
|
||||
pub destination: Destination,
|
||||
pub local_index: u16,
|
||||
pub destination: u8,
|
||||
pub local_index: u8,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for GetLocal {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
let destination = if instruction.a_is_local() {
|
||||
Destination::Local(instruction.a())
|
||||
} else {
|
||||
Destination::Register(instruction.a())
|
||||
};
|
||||
impl From<Instruction> for GetLocal {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let destination = instruction.a_field();
|
||||
let local_index = instruction.b_field();
|
||||
|
||||
GetLocal {
|
||||
destination,
|
||||
local_index: instruction.b(),
|
||||
local_index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GetLocal> for Instruction {
|
||||
fn from(get_local: GetLocal) -> Self {
|
||||
let (a, a_is_local) = match get_local.destination {
|
||||
Destination::Local(local) => (local, true),
|
||||
Destination::Register(register) => (register, false),
|
||||
};
|
||||
let operation = Operation::GET_LOCAL;
|
||||
let a = get_local.destination;
|
||||
let b = get_local.local_index;
|
||||
|
||||
*Instruction::new(Operation::GetLocal)
|
||||
.set_a(a)
|
||||
.set_a_is_local(a_is_local)
|
||||
.set_b(get_local.local_index)
|
||||
Instruction::new(operation, a, b, 0, false, false, false)
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,25 @@
|
||||
use crate::{Instruction, Operation};
|
||||
|
||||
pub struct Jump {
|
||||
pub offset: u16,
|
||||
pub offset: u8,
|
||||
pub is_positive: bool,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for Jump {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
impl From<Instruction> for Jump {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
Jump {
|
||||
offset: instruction.b(),
|
||||
is_positive: instruction.c_as_boolean(),
|
||||
offset: instruction.b_field(),
|
||||
is_positive: instruction.c_field() != 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Jump> for Instruction {
|
||||
fn from(jump: Jump) -> Self {
|
||||
*Instruction::new(Operation::Jump)
|
||||
.set_b(jump.offset)
|
||||
.set_c_to_boolean(jump.is_positive)
|
||||
let operation = Operation::JUMP;
|
||||
let b = jump.offset;
|
||||
let c = jump.is_positive as u8;
|
||||
|
||||
Instruction::new(operation, 0, b, c, false, false, false)
|
||||
}
|
||||
}
|
||||
|
@ -6,27 +6,22 @@ pub struct Less {
|
||||
pub right: Argument,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for Less {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
impl From<Instruction> for Less {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let value = instruction.d_field();
|
||||
let (left, right) = instruction.b_and_c_as_arguments();
|
||||
|
||||
Less {
|
||||
value: instruction.a_as_boolean(),
|
||||
left,
|
||||
right,
|
||||
}
|
||||
Less { value, left, right }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Less> for Instruction {
|
||||
fn from(less: Less) -> Self {
|
||||
*Instruction::new(Operation::Less)
|
||||
.set_a_to_boolean(less.value)
|
||||
.set_b(less.left.index())
|
||||
.set_b_is_constant(less.left.is_constant())
|
||||
.set_b_is_local(less.left.is_local())
|
||||
.set_c(less.right.index())
|
||||
.set_c_is_constant(less.right.is_constant())
|
||||
.set_c_is_local(less.right.is_local())
|
||||
let operation = Operation::LESS;
|
||||
let (b, b_is_constant) = less.left.as_index_and_constant_flag();
|
||||
let (c, c_is_constant) = less.right.as_index_and_constant_flag();
|
||||
let d = less.value;
|
||||
|
||||
Instruction::new(operation, 0, b, c, b_is_constant, c_is_constant, d)
|
||||
}
|
||||
}
|
||||
|
@ -6,27 +6,22 @@ pub struct LessEqual {
|
||||
pub right: Argument,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for LessEqual {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
impl From<Instruction> for LessEqual {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let value = instruction.d_field();
|
||||
let (left, right) = instruction.b_and_c_as_arguments();
|
||||
|
||||
LessEqual {
|
||||
value: instruction.a_as_boolean(),
|
||||
left,
|
||||
right,
|
||||
}
|
||||
LessEqual { value, left, right }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LessEqual> for Instruction {
|
||||
fn from(less_equal: LessEqual) -> Self {
|
||||
*Instruction::new(Operation::LessEqual)
|
||||
.set_a_to_boolean(less_equal.value)
|
||||
.set_b(less_equal.left.index())
|
||||
.set_b_is_constant(less_equal.left.is_constant())
|
||||
.set_b_is_local(less_equal.left.is_local())
|
||||
.set_c(less_equal.right.index())
|
||||
.set_c_is_constant(less_equal.right.is_constant())
|
||||
.set_c_is_local(less_equal.right.is_local())
|
||||
let operation = Operation::LESS_EQUAL;
|
||||
let (b, b_options) = less_equal.left.as_index_and_constant_flag();
|
||||
let (c, c_options) = less_equal.right.as_index_and_constant_flag();
|
||||
let d = less_equal.value;
|
||||
|
||||
Instruction::new(operation, 0, b, c, b_options, c_options, d)
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +1,28 @@
|
||||
use crate::{Destination, Instruction, Operation};
|
||||
use crate::{Instruction, Operation};
|
||||
|
||||
pub struct LoadBoolean {
|
||||
pub destination: Destination,
|
||||
pub destination: u8,
|
||||
pub value: bool,
|
||||
pub jump_next: bool,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for LoadBoolean {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
let destination = if instruction.a_is_local() {
|
||||
Destination::Local(instruction.a())
|
||||
} else {
|
||||
Destination::Register(instruction.a())
|
||||
};
|
||||
|
||||
impl From<Instruction> for LoadBoolean {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
LoadBoolean {
|
||||
destination,
|
||||
value: instruction.b_as_boolean(),
|
||||
jump_next: instruction.c_as_boolean(),
|
||||
destination: instruction.a_field(),
|
||||
value: instruction.b_field() != 0,
|
||||
jump_next: instruction.c_field() != 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoadBoolean> for Instruction {
|
||||
fn from(load_boolean: LoadBoolean) -> Self {
|
||||
let (a, a_is_local) = match load_boolean.destination {
|
||||
Destination::Local(local) => (local, true),
|
||||
Destination::Register(register) => (register, false),
|
||||
};
|
||||
let operation = Operation::LOAD_BOOLEAN;
|
||||
let a = load_boolean.destination;
|
||||
let b = load_boolean.value as u8;
|
||||
let c = load_boolean.jump_next as u8;
|
||||
|
||||
*Instruction::new(Operation::LoadBoolean)
|
||||
.set_a(a)
|
||||
.set_a_is_local(a_is_local)
|
||||
.set_b_to_boolean(load_boolean.value)
|
||||
.set_c_to_boolean(load_boolean.jump_next)
|
||||
Instruction::new(operation, a, b, c, false, false, false)
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +1,52 @@
|
||||
use crate::{Destination, Instruction, Operation};
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use crate::{Instruction, Operation};
|
||||
|
||||
pub struct LoadConstant {
|
||||
pub destination: Destination,
|
||||
pub constant_index: u16,
|
||||
pub destination: u8,
|
||||
pub constant_index: u8,
|
||||
pub jump_next: bool,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for LoadConstant {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
let destination = if instruction.a_is_local() {
|
||||
Destination::Local(instruction.a())
|
||||
} else {
|
||||
Destination::Register(instruction.a())
|
||||
};
|
||||
impl From<Instruction> for LoadConstant {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let destination = instruction.a_field();
|
||||
let constant_index = instruction.b_field();
|
||||
let jump_next = instruction.c_field() != 0;
|
||||
|
||||
LoadConstant {
|
||||
destination,
|
||||
constant_index: instruction.b(),
|
||||
jump_next: instruction.c_as_boolean(),
|
||||
constant_index,
|
||||
jump_next,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoadConstant> for Instruction {
|
||||
fn from(load_constant: LoadConstant) -> Self {
|
||||
let (a, a_is_local) = match load_constant.destination {
|
||||
Destination::Local(local) => (local, true),
|
||||
Destination::Register(register) => (register, false),
|
||||
};
|
||||
let operation = Operation::LOAD_CONSTANT;
|
||||
let a = load_constant.destination;
|
||||
let b = load_constant.constant_index;
|
||||
let c = load_constant.jump_next as u8;
|
||||
|
||||
*Instruction::new(Operation::LoadConstant)
|
||||
.set_a(a)
|
||||
.set_a_is_local(a_is_local)
|
||||
.set_b(load_constant.constant_index)
|
||||
.set_c_to_boolean(load_constant.jump_next)
|
||||
Instruction::new(operation, a, b, c, false, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for LoadConstant {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
let LoadConstant {
|
||||
destination,
|
||||
constant_index,
|
||||
jump_next,
|
||||
} = self;
|
||||
|
||||
write!(f, "R{destination} = Constant {constant_index}")?;
|
||||
|
||||
if *jump_next {
|
||||
write!(f, " JUMP +1")
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
40
dust-lang/src/instruction/load_function.rs
Normal file
40
dust-lang/src/instruction/load_function.rs
Normal file
@ -0,0 +1,40 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use super::{Instruction, Operation};
|
||||
|
||||
pub struct LoadFunction {
|
||||
pub destination: u8,
|
||||
pub prototype_index: u8,
|
||||
}
|
||||
|
||||
impl From<Instruction> for LoadFunction {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let destination = instruction.a_field();
|
||||
let record_index = instruction.b_field();
|
||||
|
||||
LoadFunction {
|
||||
destination,
|
||||
prototype_index: record_index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoadFunction> for Instruction {
|
||||
fn from(load_function: LoadFunction) -> Self {
|
||||
Instruction::new(
|
||||
Operation::LOAD_FUNCTION,
|
||||
load_function.destination,
|
||||
load_function.prototype_index,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for LoadFunction {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "R{} = F{}", self.destination, self.prototype_index)
|
||||
}
|
||||
}
|
@ -1,35 +1,28 @@
|
||||
use crate::{Destination, Instruction, Operation};
|
||||
use crate::{Instruction, Operation};
|
||||
|
||||
pub struct LoadList {
|
||||
pub destination: Destination,
|
||||
pub start_register: u16,
|
||||
pub destination: u8,
|
||||
pub start_register: u8,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for LoadList {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
let destination = if instruction.a_is_local() {
|
||||
Destination::Local(instruction.a())
|
||||
} else {
|
||||
Destination::Register(instruction.a())
|
||||
};
|
||||
impl From<Instruction> for LoadList {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let destination = instruction.a_field();
|
||||
let start_register = instruction.b_field();
|
||||
|
||||
LoadList {
|
||||
destination,
|
||||
start_register: instruction.b(),
|
||||
start_register,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoadList> for Instruction {
|
||||
fn from(load_list: LoadList) -> Self {
|
||||
let (a, a_is_local) = match load_list.destination {
|
||||
Destination::Local(local) => (local, true),
|
||||
Destination::Register(register) => (register, false),
|
||||
};
|
||||
let operation = Operation::LOAD_LIST;
|
||||
let a = load_list.destination;
|
||||
let b = load_list.start_register;
|
||||
|
||||
*Instruction::new(Operation::LoadList)
|
||||
.set_a(a)
|
||||
.set_a_is_local(a_is_local)
|
||||
.set_b(load_list.start_register)
|
||||
Instruction::new(operation, a, b, 0, false, false, false)
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,12 @@
|
||||
use crate::{Destination, Instruction, Operation};
|
||||
use crate::{Instruction, Operation};
|
||||
|
||||
pub struct LoadSelf {
|
||||
pub destination: Destination,
|
||||
pub destination: u8,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for LoadSelf {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
let destination = if instruction.a_is_local() {
|
||||
Destination::Local(instruction.a())
|
||||
} else {
|
||||
Destination::Register(instruction.a())
|
||||
};
|
||||
impl From<Instruction> for LoadSelf {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let destination = instruction.a_field();
|
||||
|
||||
LoadSelf { destination }
|
||||
}
|
||||
@ -18,13 +14,9 @@ impl From<&Instruction> for LoadSelf {
|
||||
|
||||
impl From<LoadSelf> for Instruction {
|
||||
fn from(load_self: LoadSelf) -> Self {
|
||||
let (a, a_is_local) = match load_self.destination {
|
||||
Destination::Local(local) => (local, true),
|
||||
Destination::Register(register) => (register, false),
|
||||
};
|
||||
let operation = Operation::LOAD_SELF;
|
||||
let a = load_self.destination;
|
||||
|
||||
*Instruction::new(Operation::LoadSelf)
|
||||
.set_a(a)
|
||||
.set_a_is_local(a_is_local)
|
||||
Instruction::new(operation, a, 0, 0, false, false, false)
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,18 +1,14 @@
|
||||
use crate::{Argument, Destination, Instruction, Operation};
|
||||
use crate::{Argument, Instruction, Operation};
|
||||
|
||||
pub struct Modulo {
|
||||
pub destination: Destination,
|
||||
pub destination: u8,
|
||||
pub left: Argument,
|
||||
pub right: Argument,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for Modulo {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
let destination = if instruction.a_is_local() {
|
||||
Destination::Local(instruction.a())
|
||||
} else {
|
||||
Destination::Register(instruction.a())
|
||||
};
|
||||
impl From<Instruction> for Modulo {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let destination = instruction.a_field();
|
||||
let (left, right) = instruction.b_and_c_as_arguments();
|
||||
|
||||
Modulo {
|
||||
@ -25,19 +21,11 @@ impl From<&Instruction> for Modulo {
|
||||
|
||||
impl From<Modulo> for Instruction {
|
||||
fn from(modulo: Modulo) -> Self {
|
||||
let (a, a_is_local) = match modulo.destination {
|
||||
Destination::Local(local) => (local, true),
|
||||
Destination::Register(register) => (register, false),
|
||||
};
|
||||
let operation = Operation::MODULO;
|
||||
let a = modulo.destination;
|
||||
let (b, b_is_constant) = modulo.left.as_index_and_constant_flag();
|
||||
let (c, c_is_constant) = modulo.right.as_index_and_constant_flag();
|
||||
|
||||
*Instruction::new(Operation::Modulo)
|
||||
.set_a(a)
|
||||
.set_a_is_local(a_is_local)
|
||||
.set_b(modulo.left.index())
|
||||
.set_b_is_constant(modulo.left.is_constant())
|
||||
.set_b_is_local(modulo.left.is_local())
|
||||
.set_c(modulo.right.index())
|
||||
.set_c_is_constant(modulo.right.is_constant())
|
||||
.set_c_is_local(modulo.right.is_local())
|
||||
Instruction::new(operation, a, b, c, b_is_constant, c_is_constant, false)
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
use crate::{Instruction, Operation};
|
||||
|
||||
pub struct Move {
|
||||
pub from: u16,
|
||||
pub to: u16,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for Move {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
Move {
|
||||
from: instruction.b(),
|
||||
to: instruction.a(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Move> for Instruction {
|
||||
fn from(r#move: Move) -> Self {
|
||||
*Instruction::new(Operation::Move)
|
||||
.set_b(r#move.from)
|
||||
.set_c(r#move.to)
|
||||
}
|
||||
}
|
@ -1,18 +1,14 @@
|
||||
use crate::{Argument, Destination, Instruction, Operation};
|
||||
use crate::{Argument, Instruction, Operation};
|
||||
|
||||
pub struct Multiply {
|
||||
pub destination: Destination,
|
||||
pub destination: u8,
|
||||
pub left: Argument,
|
||||
pub right: Argument,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for Multiply {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
let destination = if instruction.a_is_local() {
|
||||
Destination::Local(instruction.a())
|
||||
} else {
|
||||
Destination::Register(instruction.a())
|
||||
};
|
||||
impl From<Instruction> for Multiply {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let destination = instruction.a_field();
|
||||
let (left, right) = instruction.b_and_c_as_arguments();
|
||||
|
||||
Multiply {
|
||||
@ -25,19 +21,11 @@ impl From<&Instruction> for Multiply {
|
||||
|
||||
impl From<Multiply> for Instruction {
|
||||
fn from(multiply: Multiply) -> Self {
|
||||
let (a, a_is_local) = match multiply.destination {
|
||||
Destination::Local(local) => (local, true),
|
||||
Destination::Register(register) => (register, false),
|
||||
};
|
||||
let operation = Operation::MULTIPLY;
|
||||
let a = multiply.destination;
|
||||
let (b, b_options) = multiply.left.as_index_and_constant_flag();
|
||||
let (c, c_options) = multiply.right.as_index_and_constant_flag();
|
||||
|
||||
*Instruction::new(Operation::Multiply)
|
||||
.set_a(a)
|
||||
.set_a_is_local(a_is_local)
|
||||
.set_b(multiply.left.index())
|
||||
.set_b_is_constant(multiply.left.is_constant())
|
||||
.set_b_is_local(multiply.left.is_local())
|
||||
.set_c(multiply.right.index())
|
||||
.set_c_is_constant(multiply.right.is_constant())
|
||||
.set_c_is_local(multiply.right.is_local())
|
||||
Instruction::new(operation, a, b, c, b_options, c_options, false)
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +1,29 @@
|
||||
use crate::{Argument, Destination, Instruction, Operation};
|
||||
use crate::{Argument, Instruction, Operation};
|
||||
|
||||
pub struct Negate {
|
||||
pub destination: Destination,
|
||||
pub destination: u8,
|
||||
pub argument: Argument,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for Negate {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
let destination = if instruction.a_is_local() {
|
||||
Destination::Local(instruction.a())
|
||||
} else {
|
||||
Destination::Register(instruction.a())
|
||||
};
|
||||
impl From<Instruction> for Negate {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let destination = instruction.a_field();
|
||||
let argument = instruction.b_as_argument();
|
||||
|
||||
Negate {
|
||||
destination,
|
||||
argument: instruction.b_as_argument(),
|
||||
argument,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Negate> for Instruction {
|
||||
fn from(negate: Negate) -> Self {
|
||||
let (a, a_is_local) = match negate.destination {
|
||||
Destination::Local(local) => (local, true),
|
||||
Destination::Register(register) => (register, false),
|
||||
};
|
||||
let operation = Operation::NEGATE;
|
||||
let a = negate.destination;
|
||||
let (b, b_is_constant) = negate.argument.as_index_and_constant_flag();
|
||||
let c = 0;
|
||||
|
||||
*Instruction::new(Operation::Negate)
|
||||
.set_a(a)
|
||||
.set_a_is_local(a_is_local)
|
||||
.set_b(negate.argument.index())
|
||||
.set_b_is_constant(negate.argument.is_constant())
|
||||
.set_b_is_local(negate.argument.is_local())
|
||||
Instruction::new(operation, a, b, c, b_is_constant, false, false)
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +1,28 @@
|
||||
use crate::{Argument, Destination, Instruction, Operation};
|
||||
use crate::{Argument, Instruction, Operation};
|
||||
|
||||
pub struct Not {
|
||||
pub destination: Destination,
|
||||
pub destination: u8,
|
||||
pub argument: Argument,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for Not {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
let destination = if instruction.a_is_local() {
|
||||
Destination::Local(instruction.a())
|
||||
} else {
|
||||
Destination::Register(instruction.a())
|
||||
};
|
||||
impl From<Instruction> for Not {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let destination = instruction.a_field();
|
||||
let argument = instruction.b_as_argument();
|
||||
|
||||
Not {
|
||||
destination,
|
||||
argument: instruction.b_as_argument(),
|
||||
argument,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Not> for Instruction {
|
||||
fn from(not: Not) -> Self {
|
||||
let (a, a_is_local) = match not.destination {
|
||||
Destination::Local(local) => (local, true),
|
||||
Destination::Register(register) => (register, false),
|
||||
};
|
||||
let operation = Operation::NOT;
|
||||
let a = not.destination;
|
||||
let (b, b_is_constant) = not.argument.as_index_and_constant_flag();
|
||||
|
||||
*Instruction::new(Operation::Not)
|
||||
.set_a(a)
|
||||
.set_a_is_local(a_is_local)
|
||||
.set_b(not.argument.index())
|
||||
.set_b_is_constant(not.argument.is_constant())
|
||||
.set_b_is_local(not.argument.is_local())
|
||||
Instruction::new(operation, a, b, 0, b_is_constant, false, false)
|
||||
}
|
||||
}
|
||||
|
86
dust-lang/src/instruction/operation.rs
Normal file
86
dust-lang/src/instruction/operation.rs
Normal file
@ -0,0 +1,86 @@
|
||||
//! Part of an [Instruction][crate::Instruction] that is encoded as a single byte.
|
||||
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Part of an [Instruction][crate::Instruction] that is encoded as a single byte.
|
||||
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct Operation(pub u8);
|
||||
|
||||
impl Operation {
|
||||
pub const POINT: Operation = Operation(0);
|
||||
pub const CLOSE: Operation = Operation(1);
|
||||
pub const LOAD_BOOLEAN: Operation = Operation(2);
|
||||
pub const LOAD_CONSTANT: Operation = Operation(3);
|
||||
pub const LOAD_FUNCTION: Operation = Operation(4);
|
||||
pub const LOAD_LIST: Operation = Operation(5);
|
||||
pub const LOAD_SELF: Operation = Operation(6);
|
||||
pub const GET_LOCAL: Operation = Operation(7);
|
||||
pub const SET_LOCAL: Operation = Operation(8);
|
||||
pub const ADD: Operation = Operation(9);
|
||||
pub const SUBTRACT: Operation = Operation(10);
|
||||
pub const MULTIPLY: Operation = Operation(11);
|
||||
pub const DIVIDE: Operation = Operation(12);
|
||||
pub const MODULO: Operation = Operation(13);
|
||||
pub const TEST: Operation = Operation(14);
|
||||
pub const TEST_SET: Operation = Operation(15);
|
||||
pub const EQUAL: Operation = Operation(16);
|
||||
pub const LESS: Operation = Operation(17);
|
||||
pub const LESS_EQUAL: Operation = Operation(18);
|
||||
pub const NEGATE: Operation = Operation(19);
|
||||
pub const NOT: Operation = Operation(20);
|
||||
pub const CALL: Operation = Operation(21);
|
||||
pub const CALL_NATIVE: Operation = Operation(22);
|
||||
pub const JUMP: Operation = Operation(23);
|
||||
pub const RETURN: Operation = Operation(24);
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
pub fn name(&self) -> &'static str {
|
||||
match *self {
|
||||
Self::POINT => "POINT",
|
||||
Self::CLOSE => "CLOSE",
|
||||
Self::LOAD_BOOLEAN => "LOAD_BOOLEAN",
|
||||
Self::LOAD_CONSTANT => "LOAD_CONSTANT",
|
||||
Self::LOAD_FUNCTION => "LOAD_FUNCTION",
|
||||
Self::LOAD_LIST => "LOAD_LIST",
|
||||
Self::LOAD_SELF => "LOAD_SELF",
|
||||
Self::GET_LOCAL => "GET_LOCAL",
|
||||
Self::SET_LOCAL => "SET_LOCAL",
|
||||
Self::ADD => "ADD",
|
||||
Self::SUBTRACT => "SUBTRACT",
|
||||
Self::MULTIPLY => "MULTIPLY",
|
||||
Self::DIVIDE => "DIVIDE",
|
||||
Self::MODULO => "MODULO",
|
||||
Self::TEST => "TEST",
|
||||
Self::TEST_SET => "TEST_SET",
|
||||
Self::EQUAL => "EQUAL",
|
||||
Self::LESS => "LESS",
|
||||
Self::LESS_EQUAL => "LESS_EQUAL",
|
||||
Self::NEGATE => "NEGATE",
|
||||
Self::NOT => "NOT",
|
||||
Self::CALL => "CALL",
|
||||
Self::CALL_NATIVE => "CALL_NATIVE",
|
||||
Self::JUMP => "JUMP",
|
||||
Self::RETURN => "RETURN",
|
||||
_ => Self::panic_from_unknown_code(self.0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn panic_from_unknown_code(code: u8) -> ! {
|
||||
panic!("Unknown operation code: {code}");
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Operation {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.name())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Operation {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.name())
|
||||
}
|
||||
}
|
35
dust-lang/src/instruction/point.rs
Normal file
35
dust-lang/src/instruction/point.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use crate::{Instruction, Operation};
|
||||
|
||||
pub struct Point {
|
||||
pub from: u8,
|
||||
pub to: u8,
|
||||
}
|
||||
|
||||
impl From<Instruction> for Point {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
Point {
|
||||
from: instruction.b_field(),
|
||||
to: instruction.c_field(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Point {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
let Point { from, to } = self;
|
||||
|
||||
write!(f, "{from} -> {to}")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Point> for Instruction {
|
||||
fn from(r#move: Point) -> Self {
|
||||
let operation = Operation::POINT;
|
||||
let b = r#move.from;
|
||||
let c = r#move.to;
|
||||
|
||||
Instruction::new(operation, 0, b, c, false, false, false)
|
||||
}
|
||||
}
|
@ -2,18 +2,27 @@ use crate::{Instruction, Operation};
|
||||
|
||||
pub struct Return {
|
||||
pub should_return_value: bool,
|
||||
pub return_register: u8,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for Return {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
impl From<Instruction> for Return {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let should_return_value = instruction.b_field() != 0;
|
||||
let return_register = instruction.c_field();
|
||||
|
||||
Return {
|
||||
should_return_value: instruction.b_as_boolean(),
|
||||
should_return_value,
|
||||
return_register,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Return> for Instruction {
|
||||
fn from(r#return: Return) -> Self {
|
||||
*Instruction::new(Operation::Return).set_b_to_boolean(r#return.should_return_value)
|
||||
let operation = Operation::RETURN;
|
||||
let b = r#return.should_return_value as u8;
|
||||
let c = r#return.return_register;
|
||||
|
||||
Instruction::new(operation, 0, b, c, false, false, false)
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,28 @@
|
||||
use crate::{Instruction, Operation};
|
||||
|
||||
pub struct SetLocal {
|
||||
pub register: u16,
|
||||
pub local_index: u16,
|
||||
pub register_index: u8,
|
||||
pub local_index: u8,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for SetLocal {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
impl From<Instruction> for SetLocal {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let register_index = instruction.b_field();
|
||||
let local_index = instruction.c_field();
|
||||
|
||||
SetLocal {
|
||||
register: instruction.a(),
|
||||
local_index: instruction.b(),
|
||||
register_index,
|
||||
local_index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SetLocal> for Instruction {
|
||||
fn from(set_local: SetLocal) -> Self {
|
||||
*Instruction::new(Operation::SetLocal)
|
||||
.set_a(set_local.register)
|
||||
.set_b(set_local.local_index)
|
||||
let operation = Operation::SET_LOCAL;
|
||||
let b = set_local.register_index;
|
||||
let c = set_local.local_index;
|
||||
|
||||
Instruction::new(operation, 0, b, c, false, false, false)
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,14 @@
|
||||
use crate::{Argument, Destination, Instruction};
|
||||
use crate::{Argument, Instruction, Operation};
|
||||
|
||||
pub struct Subtract {
|
||||
pub destination: Destination,
|
||||
pub destination: u8,
|
||||
pub left: Argument,
|
||||
pub right: Argument,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for Subtract {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
let destination = if instruction.a_is_local() {
|
||||
Destination::Local(instruction.a())
|
||||
} else {
|
||||
Destination::Register(instruction.a())
|
||||
};
|
||||
impl From<Instruction> for Subtract {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let destination = instruction.a_field();
|
||||
let (left, right) = instruction.b_and_c_as_arguments();
|
||||
|
||||
Subtract {
|
||||
@ -25,19 +21,11 @@ impl From<&Instruction> for Subtract {
|
||||
|
||||
impl From<Subtract> for Instruction {
|
||||
fn from(subtract: Subtract) -> Self {
|
||||
let (a, a_is_local) = match subtract.destination {
|
||||
Destination::Local(local) => (local, true),
|
||||
Destination::Register(register) => (register, false),
|
||||
};
|
||||
let operation = Operation::SUBTRACT;
|
||||
let a = subtract.destination;
|
||||
let (b, b_is_constant) = subtract.left.as_index_and_constant_flag();
|
||||
let (c, c_is_constant) = subtract.right.as_index_and_constant_flag();
|
||||
|
||||
*Instruction::new(crate::Operation::Subtract)
|
||||
.set_a(a)
|
||||
.set_a_is_local(a_is_local)
|
||||
.set_b(subtract.left.index())
|
||||
.set_b_is_constant(subtract.left.is_constant())
|
||||
.set_b_is_local(subtract.left.is_local())
|
||||
.set_c(subtract.right.index())
|
||||
.set_c_is_constant(subtract.right.is_constant())
|
||||
.set_c_is_local(subtract.right.is_local())
|
||||
Instruction::new(operation, a, b, c, b_is_constant, c_is_constant, false)
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,32 @@
|
||||
use crate::{Argument, Instruction, Operation};
|
||||
use crate::{Instruction, Operation};
|
||||
|
||||
pub struct Test {
|
||||
pub argument: Argument,
|
||||
pub operand_register: u8,
|
||||
pub test_value: bool,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for Test {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
impl From<Instruction> for Test {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let operand_register = instruction.b_field();
|
||||
let test_value = instruction.c_field() != 0;
|
||||
|
||||
Test {
|
||||
argument: instruction.b_as_argument(),
|
||||
test_value: instruction.c_as_boolean(),
|
||||
operand_register,
|
||||
test_value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Test> for Instruction {
|
||||
fn from(test: Test) -> Self {
|
||||
*Instruction::new(Operation::Test)
|
||||
.set_b(test.argument.index())
|
||||
.set_b_is_constant(test.argument.is_constant())
|
||||
.set_b_is_local(test.argument.is_local())
|
||||
.set_c_to_boolean(test.test_value)
|
||||
Instruction::new(
|
||||
Operation::TEST,
|
||||
0,
|
||||
test.operand_register,
|
||||
test.test_value as u8,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,40 +1,32 @@
|
||||
use crate::{Argument, Destination, Instruction, Operation};
|
||||
use crate::{Argument, Instruction, Operation};
|
||||
|
||||
pub struct TestSet {
|
||||
pub destination: Destination,
|
||||
pub destination: u8,
|
||||
pub argument: Argument,
|
||||
pub test_value: bool,
|
||||
}
|
||||
|
||||
impl From<&Instruction> for TestSet {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
let destination = if instruction.a_is_local() {
|
||||
Destination::Local(instruction.a())
|
||||
} else {
|
||||
Destination::Register(instruction.a())
|
||||
};
|
||||
impl From<Instruction> for TestSet {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let destination = instruction.a_field();
|
||||
let argument = instruction.b_as_argument();
|
||||
let test_value = instruction.c_field() != 0;
|
||||
|
||||
TestSet {
|
||||
destination,
|
||||
argument: instruction.b_as_argument(),
|
||||
test_value: instruction.c_as_boolean(),
|
||||
argument,
|
||||
test_value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TestSet> for Instruction {
|
||||
fn from(test_set: TestSet) -> Self {
|
||||
let (a, a_is_local) = match test_set.destination {
|
||||
Destination::Local(local) => (local, true),
|
||||
Destination::Register(register) => (register, false),
|
||||
};
|
||||
let operation = Operation::TEST;
|
||||
let a = test_set.destination;
|
||||
let (b, b_is_constant) = test_set.argument.as_index_and_constant_flag();
|
||||
let c = test_set.test_value as u8;
|
||||
|
||||
*Instruction::new(Operation::TestSet)
|
||||
.set_a(a)
|
||||
.set_a_is_local(a_is_local)
|
||||
.set_b(test_set.argument.index())
|
||||
.set_b_is_constant(test_set.argument.is_constant())
|
||||
.set_b_is_local(test_set.argument.is_local())
|
||||
.set_c_to_boolean(test_set.test_value)
|
||||
Instruction::new(operation, a, b, c, b_is_constant, false, false)
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,6 @@
|
||||
//! This module provides two lexing options:
|
||||
//! - [`lex`], which lexes the entire input and returns a vector of tokens and their positions
|
||||
//! - [`Lexer`], which lexes the input a token at a time
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{dust_error::AnnotatedError, CompileError, DustError, Span, Token};
|
||||
@ -35,10 +32,9 @@ pub fn lex(source: &str) -> Result<Vec<(Token, Span)>, DustError> {
|
||||
let mut tokens = Vec::new();
|
||||
|
||||
loop {
|
||||
let (token, span) = lexer.next_token().map_err(|error| DustError::Compile {
|
||||
error: CompileError::Lex(error),
|
||||
source,
|
||||
})?;
|
||||
let (token, span) = lexer
|
||||
.next_token()
|
||||
.map_err(|error| DustError::compile(CompileError::Lex(error), source))?;
|
||||
|
||||
tokens.push((token, span));
|
||||
|
||||
@ -50,7 +46,7 @@ pub fn lex(source: &str) -> Result<Vec<(Token, Span)>, DustError> {
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
/// Low-level tool for lexing a single token at a time.
|
||||
/// Tool for lexing a single token at a time.
|
||||
///
|
||||
/// See the [`lex`] function for an example of how to create and use a Lexer.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
@ -199,10 +195,6 @@ impl<'src> Lexer<'src> {
|
||||
self.next_char();
|
||||
|
||||
while let Some(peek_char) = self.peek_char() {
|
||||
if peek_char == ' ' {
|
||||
break;
|
||||
}
|
||||
|
||||
if let '0'..='9' = peek_char {
|
||||
self.next_char();
|
||||
|
||||
@ -211,32 +203,28 @@ impl<'src> Lexer<'src> {
|
||||
|
||||
let peek_second_char = self.peek_second_char();
|
||||
|
||||
if let ('e', Some('0'..='9')) = (peek_char, peek_second_char) {
|
||||
if let ('e' | 'E', Some('0'..='9')) = (peek_char, peek_second_char) {
|
||||
self.next_char();
|
||||
self.next_char();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if let ('e', Some('-')) = (peek_char, peek_second_char) {
|
||||
if let ('e' | 'E', Some('+' | '-')) = (peek_char, peek_second_char) {
|
||||
self.next_char();
|
||||
self.next_char();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
return Err(LexError::ExpectedCharacterMultiple {
|
||||
expected: &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'e', '-'],
|
||||
actual: peek_char,
|
||||
position: self.position,
|
||||
});
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if c.is_ascii_digit() {
|
||||
if c.is_ascii_digit() || c == '_' {
|
||||
self.next_char();
|
||||
} else {
|
||||
break;
|
||||
@ -631,7 +619,7 @@ pub struct LexRule<'src> {
|
||||
lexer: LexerFn<'src>,
|
||||
}
|
||||
|
||||
impl<'src> From<&char> for LexRule<'src> {
|
||||
impl From<&char> for LexRule<'_> {
|
||||
fn from(char: &char) -> Self {
|
||||
match char {
|
||||
'0'..='9' => LexRule {
|
||||
@ -756,65 +744,12 @@ impl AnnotatedError for LexError {
|
||||
}
|
||||
}
|
||||
|
||||
fn details(&self) -> Option<String> {
|
||||
match self {
|
||||
Self::ExpectedAsciiHexDigit { actual, .. } => Some(format!(
|
||||
"Expected ASCII hex digit (0-9 or A-F), found \"{}\"",
|
||||
actual
|
||||
.map(|character| character.to_string())
|
||||
.unwrap_or("end of input".to_string())
|
||||
)),
|
||||
Self::ExpectedCharacter {
|
||||
expected, actual, ..
|
||||
} => Some(format!(
|
||||
"Expected character \"{}\", found \"{}\"",
|
||||
expected, actual
|
||||
)),
|
||||
Self::ExpectedCharacterMultiple {
|
||||
expected, actual, ..
|
||||
} => {
|
||||
let mut details = "Expected one of the following characters ".to_string();
|
||||
|
||||
for (i, c) in expected.iter().enumerate() {
|
||||
if i == expected.len() - 1 {
|
||||
details.push_str(", or ");
|
||||
} else if i > 0 {
|
||||
details.push_str(", ");
|
||||
}
|
||||
details.push(*c);
|
||||
fn detail_snippets(&self) -> Vec<(String, Span)> {
|
||||
Vec::with_capacity(0)
|
||||
}
|
||||
|
||||
details.push_str(&format!(" but found {}", actual));
|
||||
|
||||
Some(details)
|
||||
}
|
||||
Self::UnexpectedCharacter { actual, .. } => {
|
||||
Some(format!("Unexpected character \"{}\"", actual))
|
||||
}
|
||||
Self::UnexpectedEndOfFile { .. } => Some("Unexpected end of file".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn position(&self) -> Span {
|
||||
match self {
|
||||
Self::ExpectedAsciiHexDigit { position, .. } => Span(*position, *position),
|
||||
Self::ExpectedCharacter { position, .. } => Span(*position, *position),
|
||||
Self::ExpectedCharacterMultiple { position, .. } => Span(*position, *position),
|
||||
Self::UnexpectedCharacter { position, .. } => Span(*position, *position),
|
||||
Self::UnexpectedEndOfFile { position } => Span(*position, *position),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for LexError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.description())?;
|
||||
|
||||
if let Some(details) = self.details() {
|
||||
write!(f, ": {}", details)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
fn help_snippets(&self) -> Vec<(String, Span)> {
|
||||
Vec::with_capacity(0)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,33 +30,27 @@
|
||||
|
||||
pub mod chunk;
|
||||
pub mod compiler;
|
||||
pub mod disassembler;
|
||||
pub mod dust_error;
|
||||
pub mod instruction;
|
||||
pub mod lexer;
|
||||
pub mod native_function;
|
||||
pub mod operation;
|
||||
pub mod optimize;
|
||||
pub mod scope;
|
||||
pub mod token;
|
||||
pub mod r#type;
|
||||
pub mod value;
|
||||
pub mod vm;
|
||||
|
||||
pub use crate::chunk::{Chunk, Local};
|
||||
pub use crate::chunk::{Chunk, Disassembler, Local, Scope};
|
||||
pub use crate::compiler::{compile, CompileError, Compiler};
|
||||
pub use crate::disassembler::Disassembler;
|
||||
pub use crate::dust_error::{AnnotatedError, DustError};
|
||||
pub use crate::instruction::{Argument, Destination, Instruction};
|
||||
pub use crate::instruction::{Argument, Instruction, InstructionData, Operation};
|
||||
pub use crate::lexer::{lex, LexError, Lexer};
|
||||
pub use crate::native_function::{NativeFunction, NativeFunctionError};
|
||||
pub use crate::operation::Operation;
|
||||
pub use crate::optimize::{optimize_control_flow, optimize_set_local};
|
||||
pub use crate::r#type::{EnumType, FunctionType, StructType, Type, TypeConflict};
|
||||
pub use crate::scope::Scope;
|
||||
pub use crate::token::{write_token_list, Token, TokenKind, TokenOwned};
|
||||
pub use crate::value::{AbstractValue, ConcreteValue, RangeValue, Value, ValueError, ValueRef};
|
||||
pub use crate::vm::{run, Vm, VmError};
|
||||
pub use crate::token::{Token, TokenKind, TokenOwned};
|
||||
pub use crate::value::{
|
||||
AbstractList, ConcreteValue, DustString, Function, RangeValue, Value, ValueError,
|
||||
};
|
||||
pub use crate::vm::{run, Pointer, Vm};
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
|
22
dust-lang/src/native_function/assert.rs
Normal file
22
dust-lang/src/native_function/assert.rs
Normal file
@ -0,0 +1,22 @@
|
||||
use std::{ops::Range, panic};
|
||||
|
||||
use crate::vm::ThreadData;
|
||||
|
||||
pub fn panic(data: &mut ThreadData, _: Option<u8>, argument_range: Range<u8>) -> bool {
|
||||
let position = data.current_position();
|
||||
let mut message = format!("Dust panic at {position}!");
|
||||
|
||||
for register_index in argument_range {
|
||||
let value_option = data.open_register_allow_empty_unchecked(register_index);
|
||||
let value = match value_option {
|
||||
Some(value) => value,
|
||||
None => continue,
|
||||
};
|
||||
let string = value.display(data);
|
||||
|
||||
message.push_str(&string);
|
||||
message.push('\n');
|
||||
}
|
||||
|
||||
panic!("{}", message)
|
||||
}
|
67
dust-lang/src/native_function/io.rs
Normal file
67
dust-lang/src/native_function/io.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use std::io::{stdin, stdout, Write};
|
||||
use std::ops::Range;
|
||||
|
||||
use crate::{
|
||||
vm::{get_next_action, Register, ThreadData},
|
||||
ConcreteValue, Value,
|
||||
};
|
||||
|
||||
pub fn read_line(
|
||||
data: &mut ThreadData,
|
||||
destination: Option<u8>,
|
||||
_argument_range: Range<u8>,
|
||||
) -> bool {
|
||||
let destination = destination.unwrap();
|
||||
let mut buffer = String::new();
|
||||
|
||||
if stdin().read_line(&mut buffer).is_ok() {
|
||||
let length = buffer.len();
|
||||
|
||||
buffer.truncate(length.saturating_sub(1));
|
||||
|
||||
let register = Register::Value(Value::Concrete(ConcreteValue::string(buffer)));
|
||||
|
||||
data.set_register(destination, register);
|
||||
}
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn write(data: &mut ThreadData, _destination: Option<u8>, argument_range: Range<u8>) -> bool {
|
||||
let mut stdout = stdout();
|
||||
|
||||
for register_index in argument_range {
|
||||
if let Some(value) = data.open_register_allow_empty_unchecked(register_index) {
|
||||
let string = value.display(data);
|
||||
let _ = stdout.write(string.as_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
let _ = stdout.flush();
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn write_line(
|
||||
data: &mut ThreadData,
|
||||
_destination: Option<u8>,
|
||||
argument_range: Range<u8>,
|
||||
) -> bool {
|
||||
let mut stdout = stdout().lock();
|
||||
|
||||
for register_index in argument_range {
|
||||
if let Some(value) = data.open_register_allow_empty_unchecked(register_index) {
|
||||
let string = value.display(data);
|
||||
let _ = stdout.write(string.as_bytes());
|
||||
let _ = stdout.write(b"\n");
|
||||
}
|
||||
}
|
||||
|
||||
let _ = stdout.flush();
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
@ -1,151 +0,0 @@
|
||||
use std::io::{self, stdout, Write};
|
||||
|
||||
use crate::{ConcreteValue, Instruction, NativeFunctionError, Value, Vm, VmError};
|
||||
|
||||
pub fn panic<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Value>, VmError> {
|
||||
let argument_count = instruction.c();
|
||||
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 = if let Some(value) = vm.open_register_allow_empty(argument_index)? {
|
||||
value
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let argument_string = argument.display(vm)?;
|
||||
|
||||
message.push_str(&argument_string);
|
||||
}
|
||||
|
||||
Some(message)
|
||||
};
|
||||
|
||||
Err(VmError::NativeFunction(NativeFunctionError::Panic {
|
||||
message,
|
||||
position: vm.current_position(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn to_string<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Value>, VmError> {
|
||||
let argument_count = instruction.c();
|
||||
|
||||
if argument_count != 1 {
|
||||
return Err(VmError::NativeFunction(
|
||||
NativeFunctionError::ExpectedArgumentCount {
|
||||
expected: 1,
|
||||
found: argument_count as usize,
|
||||
position: vm.current_position(),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
let mut string = String::new();
|
||||
|
||||
for argument_index in 0..argument_count {
|
||||
let argument = if let Some(value) = vm.open_register_allow_empty(argument_index)? {
|
||||
value
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let argument_string = argument.display(vm)?;
|
||||
|
||||
string.push_str(&argument_string);
|
||||
}
|
||||
|
||||
Ok(Some(Value::Concrete(ConcreteValue::String(string))))
|
||||
}
|
||||
|
||||
pub fn read_line<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Value>, VmError> {
|
||||
let argument_count = instruction.c();
|
||||
|
||||
if argument_count != 0 {
|
||||
return Err(VmError::NativeFunction(
|
||||
NativeFunctionError::ExpectedArgumentCount {
|
||||
expected: 0,
|
||||
found: argument_count as usize,
|
||||
position: vm.current_position(),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
let mut buffer = String::new();
|
||||
|
||||
match io::stdin().read_line(&mut buffer) {
|
||||
Ok(_) => {
|
||||
buffer = buffer.trim_end_matches('\n').to_string();
|
||||
|
||||
Ok(Some(Value::Concrete(ConcreteValue::String(buffer))))
|
||||
}
|
||||
Err(error) => Err(VmError::NativeFunction(NativeFunctionError::Io {
|
||||
error: error.kind(),
|
||||
position: vm.current_position(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Value>, VmError> {
|
||||
let to_register = instruction.a();
|
||||
let argument_count = instruction.c();
|
||||
let mut stdout = stdout();
|
||||
let map_err = |io_error: io::Error| {
|
||||
VmError::NativeFunction(NativeFunctionError::Io {
|
||||
error: io_error.kind(),
|
||||
position: vm.current_position(),
|
||||
})
|
||||
};
|
||||
|
||||
let first_argument = to_register.saturating_sub(argument_count);
|
||||
|
||||
for argument_index in first_argument..to_register {
|
||||
let argument = if let Some(value) = vm.open_register_allow_empty(argument_index)? {
|
||||
value
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let argument_string = argument.display(vm)?;
|
||||
|
||||
stdout
|
||||
.write_all(argument_string.as_bytes())
|
||||
.map_err(map_err)?;
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn write_line<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Value>, VmError> {
|
||||
let to_register = instruction.a();
|
||||
let argument_count = instruction.c();
|
||||
let mut stdout = stdout();
|
||||
let map_err = |io_error: io::Error| {
|
||||
VmError::NativeFunction(NativeFunctionError::Io {
|
||||
error: io_error.kind(),
|
||||
position: vm.current_position(),
|
||||
})
|
||||
};
|
||||
|
||||
let first_argument = to_register.saturating_sub(argument_count);
|
||||
|
||||
for argument_index in first_argument..to_register {
|
||||
let argument = if let Some(value) = vm.open_register_allow_empty(argument_index)? {
|
||||
value
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let argument_string = argument.display(vm)?;
|
||||
|
||||
stdout
|
||||
.write_all(argument_string.as_bytes())
|
||||
.map_err(map_err)?;
|
||||
}
|
||||
|
||||
stdout.write(b"\n").map_err(map_err)?;
|
||||
|
||||
Ok(None)
|
||||
}
|
@ -2,17 +2,20 @@
|
||||
//!
|
||||
//! Native functions are used to implement features that are not possible to implement in Dust
|
||||
//! itself or that are more efficient to implement in Rust.
|
||||
mod logic;
|
||||
mod assert;
|
||||
mod io;
|
||||
mod string;
|
||||
|
||||
use std::{
|
||||
fmt::{self, Display, Formatter},
|
||||
io::{self},
|
||||
string::{self},
|
||||
io::ErrorKind as IoErrorKind,
|
||||
ops::Range,
|
||||
string::ParseError,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{AnnotatedError, FunctionType, Instruction, Span, Type, Value, Vm, VmError};
|
||||
use crate::{vm::ThreadData, AnnotatedError, FunctionType, Span, Type};
|
||||
|
||||
macro_rules! define_native_function {
|
||||
($(($name:ident, $bytes:literal, $str:expr, $type:expr, $function:expr)),*) => {
|
||||
@ -29,12 +32,13 @@ macro_rules! define_native_function {
|
||||
impl NativeFunction {
|
||||
pub fn call(
|
||||
&self,
|
||||
vm: &mut Vm,
|
||||
instruction: Instruction,
|
||||
) -> Result<Option<Value>, VmError> {
|
||||
data: &mut ThreadData,
|
||||
destination: Option<u8>,
|
||||
argument_range: Range<u8>,
|
||||
) -> bool {
|
||||
match self {
|
||||
$(
|
||||
NativeFunction::$name => $function(vm, instruction),
|
||||
NativeFunction::$name => $function(data, destination, argument_range),
|
||||
)*
|
||||
}
|
||||
}
|
||||
@ -68,14 +72,14 @@ macro_rules! define_native_function {
|
||||
pub fn returns_value(&self) -> bool {
|
||||
match self {
|
||||
$(
|
||||
NativeFunction::$name => *$type.return_type != Type::None,
|
||||
NativeFunction::$name => $type.return_type != Type::None,
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u16> for NativeFunction {
|
||||
fn from(bytes: u16) -> Self {
|
||||
impl From<u8> for NativeFunction {
|
||||
fn from(bytes: u8) -> Self {
|
||||
match bytes {
|
||||
$(
|
||||
$bytes => NativeFunction::$name,
|
||||
@ -129,11 +133,11 @@ define_native_function! {
|
||||
3,
|
||||
"panic",
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::None)
|
||||
type_parameters: Vec::with_capacity(0),
|
||||
value_parameters: Vec::with_capacity(0),
|
||||
return_type: Type::None
|
||||
},
|
||||
logic::panic
|
||||
assert::panic
|
||||
),
|
||||
|
||||
// // Type conversion
|
||||
@ -146,11 +150,11 @@ define_native_function! {
|
||||
8,
|
||||
"to_string",
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: Some(vec![(0, Type::Any)]),
|
||||
return_type: Box::new(Type::String)
|
||||
type_parameters: Vec::with_capacity(0),
|
||||
value_parameters: vec![(0, Type::Any)],
|
||||
return_type: Type::String
|
||||
},
|
||||
logic::to_string
|
||||
string::to_string
|
||||
),
|
||||
|
||||
// // List and string
|
||||
@ -207,11 +211,11 @@ define_native_function! {
|
||||
50,
|
||||
"read_line",
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::String)
|
||||
type_parameters: Vec::with_capacity(0),
|
||||
value_parameters: Vec::with_capacity(0),
|
||||
return_type: Type::String
|
||||
},
|
||||
logic::read_line
|
||||
io::read_line
|
||||
),
|
||||
// (ReadTo, 51_u8, "read_to", false),
|
||||
// (ReadUntil, 52_u8, "read_until", true),
|
||||
@ -223,11 +227,11 @@ define_native_function! {
|
||||
55,
|
||||
"write",
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: Some(vec![(0, Type::String)]),
|
||||
return_type: Box::new(Type::None)
|
||||
type_parameters: Vec::with_capacity(0),
|
||||
value_parameters: vec![(0, Type::String)],
|
||||
return_type: Type::None
|
||||
},
|
||||
logic::write
|
||||
io::write
|
||||
),
|
||||
// (WriteFile, 56_u8, "write_file", false),
|
||||
(
|
||||
@ -235,11 +239,11 @@ define_native_function! {
|
||||
57,
|
||||
"write_line",
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: Some(vec![(0, Type::String)]),
|
||||
return_type: Box::new(Type::None)
|
||||
type_parameters: Vec::with_capacity(0),
|
||||
value_parameters: vec![(0, Type::String)],
|
||||
return_type: Type::None
|
||||
},
|
||||
logic::write_line
|
||||
io::write_line
|
||||
)
|
||||
|
||||
// // Random
|
||||
@ -255,15 +259,15 @@ pub enum NativeFunctionError {
|
||||
position: Span,
|
||||
},
|
||||
Panic {
|
||||
message: Option<String>,
|
||||
message: String,
|
||||
position: Span,
|
||||
},
|
||||
Parse {
|
||||
error: string::ParseError,
|
||||
error: ParseError,
|
||||
position: Span,
|
||||
},
|
||||
Io {
|
||||
error: io::ErrorKind,
|
||||
error: IoErrorKind,
|
||||
position: Span,
|
||||
},
|
||||
}
|
||||
@ -284,23 +288,29 @@ impl AnnotatedError for NativeFunctionError {
|
||||
}
|
||||
}
|
||||
|
||||
fn details(&self) -> Option<String> {
|
||||
fn detail_snippets(&self) -> Vec<(String, Span)> {
|
||||
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)),
|
||||
expected,
|
||||
found,
|
||||
position,
|
||||
} => vec![(
|
||||
format!("Expected {expected} arguments, found {found}"),
|
||||
*position,
|
||||
)],
|
||||
NativeFunctionError::Panic { message, position } => {
|
||||
vec![(format!("Dust panic!\n{message}"), *position)]
|
||||
}
|
||||
NativeFunctionError::Parse { error, position } => {
|
||||
vec![(format!("{error}"), *position)]
|
||||
}
|
||||
NativeFunctionError::Io { error, position } => {
|
||||
vec![(format!("{error}"), *position)]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn position(&self) -> Span {
|
||||
match self {
|
||||
NativeFunctionError::ExpectedArgumentCount { position, .. } => *position,
|
||||
NativeFunctionError::Panic { position, .. } => *position,
|
||||
NativeFunctionError::Parse { position, .. } => *position,
|
||||
NativeFunctionError::Io { position, .. } => *position,
|
||||
}
|
||||
fn help_snippets(&self) -> Vec<(String, Span)> {
|
||||
Vec::with_capacity(0)
|
||||
}
|
||||
}
|
||||
|
23
dust-lang/src/native_function/string.rs
Normal file
23
dust-lang/src/native_function/string.rs
Normal file
@ -0,0 +1,23 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use crate::{
|
||||
vm::{get_next_action, Register, ThreadData},
|
||||
ConcreteValue, Value,
|
||||
};
|
||||
|
||||
pub fn to_string(
|
||||
data: &mut ThreadData,
|
||||
destination: Option<u8>,
|
||||
argument_range: Range<u8>,
|
||||
) -> bool {
|
||||
let argument_value = data.open_register_unchecked(argument_range.start);
|
||||
let argument_string = argument_value.display(data);
|
||||
let destination = destination.unwrap();
|
||||
let register = Register::Value(Value::Concrete(ConcreteValue::string(argument_string)));
|
||||
|
||||
data.set_register(destination, register);
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
//! Part of an [Instruction][crate::Instruction] that is encoded as a single byte.
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
macro_rules! define_operation {
|
||||
($(($name:ident, $byte:literal, $str:expr)),*) => {
|
||||
/// Part of an [Instruction][crate::Instruction] that is encoded as a single byte.
|
||||
#[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, 0, "MOVE"),
|
||||
(Close, 1, "CLOSE"),
|
||||
|
||||
(LoadBoolean, 2, "LOAD_BOOLEAN"),
|
||||
(LoadConstant, 3, "LOAD_CONSTANT"),
|
||||
(LoadList, 4, "LOAD_LIST"),
|
||||
(LoadSelf, 5, "LOAD_SELF"),
|
||||
|
||||
(DefineLocal, 6, "DEFINE_LOCAL"),
|
||||
(GetLocal, 7, "GET_LOCAL"),
|
||||
(SetLocal, 8, "SET_LOCAL"),
|
||||
|
||||
(Add, 9, "ADD"),
|
||||
(Subtract, 10, "SUBTRACT"),
|
||||
(Multiply, 11, "MULTIPLY"),
|
||||
(Divide, 12, "DIVIDE"),
|
||||
(Modulo, 13, "MODULO"),
|
||||
|
||||
(Test, 14, "TEST"),
|
||||
(TestSet, 15, "TEST_SET"),
|
||||
|
||||
(Equal, 16, "EQUAL"),
|
||||
(Less, 17, "LESS"),
|
||||
(LessEqual, 18, "LESS_EQUAL"),
|
||||
|
||||
(Negate, 19, "NEGATE"),
|
||||
(Not, 20, "NOT"),
|
||||
|
||||
(Call, 21, "CALL"),
|
||||
(CallNative, 22, "CALL_NATIVE"),
|
||||
|
||||
(Jump, 23, "JUMP"),
|
||||
(Return, 24, "RETURN")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
//! Tools used by the compiler to optimize a chunk's bytecode.
|
||||
|
||||
use crate::{instruction::SetLocal, Instruction, Operation, Span, Type};
|
||||
|
||||
fn get_last_operations<const COUNT: usize>(
|
||||
instructions: &[(Instruction, Type, Span)],
|
||||
) -> Option<[Operation; COUNT]> {
|
||||
let mut n_operations = [Operation::Return; COUNT];
|
||||
|
||||
for (nth, operation) in n_operations.iter_mut().rev().zip(
|
||||
instructions
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|(instruction, _, _)| instruction.operation()),
|
||||
) {
|
||||
*nth = operation;
|
||||
}
|
||||
|
||||
Some(n_operations)
|
||||
}
|
||||
|
||||
/// Optimizes a short control flow pattern.
|
||||
///
|
||||
/// Comparison and test instructions (which are always followed by a JUMP) can be optimized when
|
||||
/// the next instructions are two constant or boolean loaders. The first loader is set to skip
|
||||
/// an instruction if it is run while the second loader is modified to use the first's register.
|
||||
/// This makes the following two code snippets compile to the same bytecode:
|
||||
///
|
||||
/// ```dust
|
||||
/// 4 == 4
|
||||
/// ```
|
||||
///
|
||||
/// ```dust
|
||||
/// if 4 == 4 { true } else { false }
|
||||
/// ```
|
||||
///
|
||||
/// The instructions must be in the following order:
|
||||
/// - `Equal`, `Less`, `LessEqual` or `Test`
|
||||
/// - `Jump`
|
||||
/// - `LoadBoolean` or `LoadConstant`
|
||||
/// - `LoadBoolean` or `LoadConstant`
|
||||
pub fn optimize_control_flow(instructions: &mut [(Instruction, Type, Span)]) {
|
||||
if !matches!(
|
||||
get_last_operations(instructions),
|
||||
Some([
|
||||
Operation::Equal | Operation::Less | Operation::LessEqual | Operation::Test,
|
||||
Operation::Jump,
|
||||
Operation::LoadBoolean | Operation::LoadConstant,
|
||||
Operation::LoadBoolean | Operation::LoadConstant,
|
||||
])
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
log::debug!("Consolidating registers for control flow optimization");
|
||||
|
||||
let first_loader = &mut instructions.iter_mut().nth_back(1).unwrap().0;
|
||||
|
||||
first_loader.set_c_to_boolean(true);
|
||||
|
||||
let first_loader_register = first_loader.a();
|
||||
let second_loader = &mut instructions.last_mut().unwrap().0;
|
||||
let second_loader_new = *second_loader.clone().set_a(first_loader_register);
|
||||
|
||||
*second_loader = second_loader_new;
|
||||
}
|
||||
|
||||
/// Optimizes a math instruction followed by a SetLocal instruction.
|
||||
///
|
||||
/// The SetLocal instruction is removed and the math instruction is modified to use the local as
|
||||
/// its destination. This makes the following two code snippets compile to the same bytecode:
|
||||
///
|
||||
/// ```dust
|
||||
/// let a = 0;
|
||||
/// a = a + 1;
|
||||
/// ```
|
||||
///
|
||||
/// ```dust
|
||||
/// let a = 0;
|
||||
/// a += 1;
|
||||
/// ```
|
||||
///
|
||||
/// The instructions must be in the following order:
|
||||
/// - `Add`, `Subtract`, `Multiply`, `Divide` or `Modulo`
|
||||
/// - `SetLocal`
|
||||
pub fn optimize_set_local(instructions: &mut Vec<(Instruction, Type, Span)>) {
|
||||
if !matches!(
|
||||
get_last_operations(instructions),
|
||||
Some([
|
||||
Operation::Add
|
||||
| Operation::Subtract
|
||||
| Operation::Multiply
|
||||
| Operation::Divide
|
||||
| Operation::Modulo,
|
||||
Operation::SetLocal,
|
||||
])
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
log::debug!("Condensing math and SetLocal to math instruction");
|
||||
|
||||
let set_local = SetLocal::from(&instructions.pop().unwrap().0);
|
||||
let math_instruction = instructions.last_mut().unwrap().0;
|
||||
let math_instruction_new = *math_instruction
|
||||
.clone()
|
||||
.set_a(set_local.local_index)
|
||||
.set_a_is_local(true);
|
||||
|
||||
instructions.last_mut().unwrap().0 = math_instruction_new;
|
||||
}
|
@ -1,32 +1,8 @@
|
||||
//! Token, TokenOwned and TokenKind types.
|
||||
use std::{
|
||||
fmt::{self, Display, Formatter},
|
||||
io::Write,
|
||||
};
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use colored::Colorize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::Span;
|
||||
|
||||
pub fn write_token_list<W: Write>(tokens: &[(Token, Span)], styled: bool, writer: &mut W) {
|
||||
const HEADER: [&str; 2] = [" TOKEN POSITION ", "------------- ----------"];
|
||||
|
||||
writeln!(writer, "{}", HEADER[0]).unwrap();
|
||||
writeln!(writer, "{}", HEADER[1]).unwrap();
|
||||
|
||||
for (token, position) in tokens {
|
||||
let token = if styled {
|
||||
format!("{:^13}", token.as_str().bold())
|
||||
} else {
|
||||
token.to_string()
|
||||
};
|
||||
let position = position.to_string();
|
||||
|
||||
writeln!(writer, "{token:^13} {position:^10}").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! define_tokens {
|
||||
($($variant:ident $(($data_type:ty))?),+) => {
|
||||
/// Source token.
|
||||
@ -116,7 +92,7 @@ define_tokens! {
|
||||
StarEqual
|
||||
}
|
||||
|
||||
impl<'src> Token<'src> {
|
||||
impl Token<'_> {
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
@ -405,7 +381,7 @@ impl<'src> Token<'src> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'src> Display for Token<'src> {
|
||||
impl Display for Token<'_> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Token::ArrowThin => write!(f, "->"),
|
||||
@ -603,63 +579,6 @@ impl Display for TokenOwned {
|
||||
|
||||
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"),
|
||||
TokenKind::Break => Token::Break.fmt(f),
|
||||
TokenKind::Byte => write!(f, "byte"),
|
||||
TokenKind::Character => write!(f, "character"),
|
||||
TokenKind::Colon => Token::Colon.fmt(f),
|
||||
TokenKind::Comma => Token::Comma.fmt(f),
|
||||
TokenKind::Dot => Token::Dot.fmt(f),
|
||||
TokenKind::DoubleAmpersand => Token::DoubleAmpersand.fmt(f),
|
||||
TokenKind::DoubleDot => Token::DoubleDot.fmt(f),
|
||||
TokenKind::DoubleEqual => Token::DoubleEqual.fmt(f),
|
||||
TokenKind::DoublePipe => Token::DoublePipe.fmt(f),
|
||||
TokenKind::Else => Token::Else.fmt(f),
|
||||
TokenKind::Eof => Token::Eof.fmt(f),
|
||||
TokenKind::Equal => Token::Equal.fmt(f),
|
||||
TokenKind::Float => write!(f, "float"),
|
||||
TokenKind::FloatKeyword => Token::FloatKeyword.fmt(f),
|
||||
TokenKind::Fn => Token::Fn.fmt(f),
|
||||
TokenKind::Greater => Token::Greater.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),
|
||||
TokenKind::Integer => write!(f, "integer"),
|
||||
TokenKind::LeftBrace => Token::LeftBrace.fmt(f),
|
||||
TokenKind::LeftParenthesis => Token::LeftParenthesis.fmt(f),
|
||||
TokenKind::LeftBracket => Token::LeftBracket.fmt(f),
|
||||
TokenKind::Let => Token::Let.fmt(f),
|
||||
TokenKind::Less => Token::Less.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::RightBrace => Token::RightBrace.fmt(f),
|
||||
TokenKind::RightParenthesis => Token::RightParenthesis.fmt(f),
|
||||
TokenKind::RightBracket => Token::RightBracket.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"),
|
||||
TokenKind::Struct => Token::Struct.fmt(f),
|
||||
TokenKind::While => Token::While.fmt(f),
|
||||
}
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ pub enum Type {
|
||||
Character,
|
||||
Enum(EnumType),
|
||||
Float,
|
||||
Function(FunctionType),
|
||||
Function(Box<FunctionType>),
|
||||
Generic {
|
||||
identifier_index: u8,
|
||||
concrete_type: Option<Box<Type>>,
|
||||
@ -24,21 +24,29 @@ pub enum Type {
|
||||
Integer,
|
||||
List(Box<Type>),
|
||||
Map {
|
||||
pairs: HashMap<u8, Type>,
|
||||
pairs: Vec<(u8, Type)>,
|
||||
},
|
||||
None,
|
||||
Range {
|
||||
r#type: Box<Type>,
|
||||
},
|
||||
SelfChunk,
|
||||
SelfFunction,
|
||||
String,
|
||||
Struct(StructType),
|
||||
Tuple {
|
||||
fields: Option<Vec<Type>>,
|
||||
fields: Vec<Type>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Type {
|
||||
pub fn function(function_type: FunctionType) -> Self {
|
||||
Type::Function(Box::new(function_type))
|
||||
}
|
||||
|
||||
pub fn list(element_type: Type) -> Self {
|
||||
Type::List(Box::new(element_type))
|
||||
}
|
||||
|
||||
/// Returns a concrete type, either the type itself or the concrete type of a generic type.
|
||||
pub fn concrete_type(&self) -> &Type {
|
||||
if let Type::Generic {
|
||||
@ -107,18 +115,18 @@ impl Type {
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
(
|
||||
Type::Function(FunctionType {
|
||||
(Type::Function(left_function_type), Type::Function(right_function_type)) => {
|
||||
let FunctionType {
|
||||
type_parameters: left_type_parameters,
|
||||
value_parameters: left_value_parameters,
|
||||
return_type: left_return,
|
||||
}),
|
||||
Type::Function(FunctionType {
|
||||
} = left_function_type.as_ref();
|
||||
let FunctionType {
|
||||
type_parameters: right_type_parameters,
|
||||
value_parameters: right_value_parameters,
|
||||
return_type: right_return,
|
||||
}),
|
||||
) => {
|
||||
} = right_function_type.as_ref();
|
||||
|
||||
if left_return != right_return
|
||||
|| left_type_parameters != right_type_parameters
|
||||
|| left_value_parameters != right_value_parameters
|
||||
@ -185,25 +193,21 @@ impl Display for Type {
|
||||
}
|
||||
Type::None => write!(f, "none"),
|
||||
Type::Range { r#type } => write!(f, "{type} range"),
|
||||
Type::SelfChunk => write!(f, "self"),
|
||||
Type::SelfFunction => write!(f, "self"),
|
||||
Type::String => write!(f, "str"),
|
||||
Type::Struct(struct_type) => write!(f, "{struct_type}"),
|
||||
Type::Tuple { fields } => {
|
||||
if let Some(fields) = fields {
|
||||
write!(f, "(")?;
|
||||
|
||||
for (index, r#type) in fields.iter().enumerate() {
|
||||
write!(f, "{type}")?;
|
||||
|
||||
if index != fields.len() - 1 {
|
||||
if index > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
|
||||
write!(f, "{type}")?;
|
||||
}
|
||||
|
||||
write!(f, ")")
|
||||
} else {
|
||||
write!(f, "tuple")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -252,8 +256,8 @@ impl Ord for Type {
|
||||
left_type.cmp(right_type)
|
||||
}
|
||||
(Type::Range { .. }, _) => Ordering::Greater,
|
||||
(Type::SelfChunk, Type::SelfChunk) => Ordering::Equal,
|
||||
(Type::SelfChunk, _) => Ordering::Greater,
|
||||
(Type::SelfFunction, Type::SelfFunction) => Ordering::Equal,
|
||||
(Type::SelfFunction, _) => Ordering::Greater,
|
||||
(Type::String, Type::String) => Ordering::Equal,
|
||||
(Type::String { .. }, _) => Ordering::Greater,
|
||||
(Type::Struct(left_struct), Type::Struct(right_struct)) => {
|
||||
@ -269,19 +273,33 @@ impl Ord for Type {
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct FunctionType {
|
||||
pub type_parameters: Option<Vec<u16>>,
|
||||
pub value_parameters: Option<Vec<(u16, Type)>>,
|
||||
pub return_type: Box<Type>,
|
||||
pub type_parameters: Vec<u8>,
|
||||
pub value_parameters: Vec<(u8, Type)>,
|
||||
pub return_type: Type,
|
||||
}
|
||||
|
||||
impl FunctionType {
|
||||
pub fn new<T: Into<Vec<u8>>, U: Into<Vec<(u8, Type)>>>(
|
||||
type_parameters: T,
|
||||
value_parameters: U,
|
||||
return_type: Type,
|
||||
) -> Self {
|
||||
FunctionType {
|
||||
type_parameters: type_parameters.into(),
|
||||
value_parameters: value_parameters.into(),
|
||||
return_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FunctionType {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "fn ")?;
|
||||
|
||||
if let Some(type_parameters) = &self.type_parameters {
|
||||
if !self.type_parameters.is_empty() {
|
||||
write!(f, "<")?;
|
||||
|
||||
for (index, type_parameter) in type_parameters.iter().enumerate() {
|
||||
for (index, type_parameter) in self.type_parameters.iter().enumerate() {
|
||||
if index > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
@ -294,19 +312,19 @@ impl Display for FunctionType {
|
||||
|
||||
write!(f, "(")?;
|
||||
|
||||
if let Some(value_parameters) = &self.value_parameters {
|
||||
for (index, (identifier, r#type)) in value_parameters.iter().enumerate() {
|
||||
if !self.value_parameters.is_empty() {
|
||||
for (index, (_, r#type)) in self.value_parameters.iter().enumerate() {
|
||||
if index > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
|
||||
write!(f, "{identifier}: {type}")?;
|
||||
write!(f, "{type}")?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(f, ")")?;
|
||||
|
||||
if *self.return_type != Type::None {
|
||||
if self.return_type != Type::None {
|
||||
write!(f, " -> {}", self.return_type)?;
|
||||
}
|
||||
|
||||
|
45
dust-lang/src/value/abstract_list.rs
Normal file
45
dust-lang/src/value/abstract_list.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use crate::{vm::ThreadData, Pointer, Type};
|
||||
|
||||
use super::DustString;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd)]
|
||||
pub struct AbstractList {
|
||||
pub item_type: Type,
|
||||
pub item_pointers: Vec<Pointer>,
|
||||
}
|
||||
|
||||
impl AbstractList {
|
||||
pub fn display(&self, data: &ThreadData) -> DustString {
|
||||
let mut display = DustString::new();
|
||||
|
||||
display.push('[');
|
||||
|
||||
for (i, item) in self.item_pointers.iter().enumerate() {
|
||||
if i > 0 {
|
||||
display.push_str(", ");
|
||||
}
|
||||
|
||||
let item_display = data.follow_pointer_unchecked(*item).display(data);
|
||||
|
||||
display.push_str(&item_display);
|
||||
}
|
||||
|
||||
display.push(']');
|
||||
|
||||
display
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AbstractList {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "[")?;
|
||||
|
||||
for pointer in &self.item_pointers {
|
||||
write!(f, "{}", pointer)?;
|
||||
}
|
||||
|
||||
write!(f, "]")
|
||||
}
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use crate::{vm::Pointer, ConcreteValue, Value, ValueRef, Vm, VmError};
|
||||
|
||||
#[derive(Debug, PartialEq, PartialOrd)]
|
||||
pub enum AbstractValue {
|
||||
FunctionSelf,
|
||||
List { items: Vec<Pointer> },
|
||||
}
|
||||
|
||||
impl AbstractValue {
|
||||
pub fn to_value(self) -> Value {
|
||||
Value::Abstract(self)
|
||||
}
|
||||
|
||||
pub fn to_value_ref(&self) -> ValueRef {
|
||||
ValueRef::Abstract(self)
|
||||
}
|
||||
|
||||
pub fn to_concrete_owned(&self, vm: &Vm) -> Result<ConcreteValue, VmError> {
|
||||
match self {
|
||||
AbstractValue::FunctionSelf => Ok(ConcreteValue::Function(vm.chunk().clone())),
|
||||
AbstractValue::List { items, .. } => {
|
||||
let mut resolved_items = Vec::with_capacity(items.len());
|
||||
|
||||
for pointer in items {
|
||||
let resolved_item = vm.follow_pointer(*pointer)?.to_concrete_owned(vm)?;
|
||||
|
||||
resolved_items.push(resolved_item);
|
||||
}
|
||||
|
||||
Ok(ConcreteValue::List(resolved_items))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display(&self, vm: &Vm) -> Result<String, VmError> {
|
||||
match self {
|
||||
AbstractValue::FunctionSelf => Ok("self".to_string()),
|
||||
AbstractValue::List { items, .. } => {
|
||||
let mut display = "[".to_string();
|
||||
|
||||
for (i, item) in items.iter().enumerate() {
|
||||
if i > 0 {
|
||||
display.push_str(", ");
|
||||
}
|
||||
|
||||
let item_display = vm.follow_pointer(*item)?.display(vm)?;
|
||||
|
||||
display.push_str(&item_display);
|
||||
}
|
||||
|
||||
display.push(']');
|
||||
|
||||
Ok(display)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for AbstractValue {
|
||||
fn clone(&self) -> Self {
|
||||
log::trace!("Cloning abstract value {:?}", self);
|
||||
|
||||
match self {
|
||||
AbstractValue::FunctionSelf => AbstractValue::FunctionSelf,
|
||||
AbstractValue::List { items } => AbstractValue::List {
|
||||
items: items.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AbstractValue {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
AbstractValue::FunctionSelf => write!(f, "self"),
|
||||
AbstractValue::List { items, .. } => {
|
||||
write!(f, "[")?;
|
||||
|
||||
for (i, item) in items.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
|
||||
write!(f, "{}", item)?;
|
||||
}
|
||||
|
||||
write!(f, "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,22 +1,25 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smartstring::{LazyCompact, SmartString};
|
||||
use tracing::trace;
|
||||
|
||||
use crate::{Chunk, Type, Value, ValueError, ValueRef};
|
||||
use crate::{Type, Value, ValueError};
|
||||
|
||||
use super::RangeValue;
|
||||
|
||||
pub type DustString = SmartString<LazyCompact>;
|
||||
|
||||
#[derive(Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
|
||||
pub enum ConcreteValue {
|
||||
Boolean(bool),
|
||||
Byte(u8),
|
||||
Character(char),
|
||||
Float(f64),
|
||||
Function(Chunk),
|
||||
Integer(i64),
|
||||
List(Vec<ConcreteValue>),
|
||||
Range(RangeValue),
|
||||
String(String),
|
||||
String(DustString),
|
||||
}
|
||||
|
||||
impl ConcreteValue {
|
||||
@ -24,19 +27,15 @@ impl ConcreteValue {
|
||||
Value::Concrete(self)
|
||||
}
|
||||
|
||||
pub fn to_value_ref(&self) -> ValueRef {
|
||||
ValueRef::Concrete(self)
|
||||
}
|
||||
|
||||
pub fn list<T: Into<Vec<ConcreteValue>>>(into_list: T) -> Self {
|
||||
ConcreteValue::List(into_list.into())
|
||||
}
|
||||
|
||||
pub fn string<T: ToString>(to_string: T) -> Self {
|
||||
ConcreteValue::String(to_string.to_string())
|
||||
pub fn string<T: Into<SmartString<LazyCompact>>>(to_string: T) -> Self {
|
||||
ConcreteValue::String(to_string.into())
|
||||
}
|
||||
|
||||
pub fn as_string(&self) -> Option<&String> {
|
||||
pub fn as_string(&self) -> Option<&DustString> {
|
||||
if let ConcreteValue::String(string) = self {
|
||||
Some(string)
|
||||
} else {
|
||||
@ -44,13 +43,16 @@ impl ConcreteValue {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display(&self) -> DustString {
|
||||
DustString::from(self.to_string())
|
||||
}
|
||||
|
||||
pub fn r#type(&self) -> Type {
|
||||
match self {
|
||||
ConcreteValue::Boolean(_) => Type::Boolean,
|
||||
ConcreteValue::Byte(_) => Type::Byte,
|
||||
ConcreteValue::Character(_) => Type::Character,
|
||||
ConcreteValue::Float(_) => Type::Float,
|
||||
ConcreteValue::Function(chunk) => Type::Function(chunk.r#type().clone()),
|
||||
ConcreteValue::Integer(_) => Type::Integer,
|
||||
ConcreteValue::List(list) => {
|
||||
let item_type = list.first().map_or(Type::Any, |item| item.r#type());
|
||||
@ -62,41 +64,76 @@ impl ConcreteValue {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&self, other: &Self) -> Result<ConcreteValue, ValueError> {
|
||||
pub fn add(&self, other: &Self) -> ConcreteValue {
|
||||
use ConcreteValue::*;
|
||||
|
||||
let sum = match (self, other) {
|
||||
(Byte(left), Byte(right)) => ConcreteValue::Byte(left.saturating_add(*right)),
|
||||
(Float(left), Float(right)) => ConcreteValue::Float(*left + *right),
|
||||
(Integer(left), Integer(right)) => ConcreteValue::Integer(left.saturating_add(*right)),
|
||||
(String(left), String(right)) => ConcreteValue::string(format!("{}{}", left, right)),
|
||||
_ => {
|
||||
return Err(ValueError::CannotAdd(
|
||||
self.clone().to_value(),
|
||||
other.clone().to_value(),
|
||||
))
|
||||
}
|
||||
};
|
||||
match (self, other) {
|
||||
(Byte(left), Byte(right)) => {
|
||||
let sum = left.saturating_add(*right);
|
||||
|
||||
Ok(sum)
|
||||
Byte(sum)
|
||||
}
|
||||
(Character(left), Character(right)) => {
|
||||
let mut concatenated = DustString::new();
|
||||
|
||||
concatenated.push(*left);
|
||||
concatenated.push(*right);
|
||||
|
||||
String(concatenated)
|
||||
}
|
||||
(Character(left), String(right)) => {
|
||||
let mut concatenated = DustString::new();
|
||||
|
||||
concatenated.push(*left);
|
||||
concatenated.push_str(right);
|
||||
|
||||
String(concatenated)
|
||||
}
|
||||
(Float(left), Float(right)) => {
|
||||
let sum = left + right;
|
||||
|
||||
Float(sum)
|
||||
}
|
||||
(Integer(left), Integer(right)) => {
|
||||
let sum = left.saturating_add(*right);
|
||||
|
||||
Integer(sum)
|
||||
}
|
||||
(String(left), Character(right)) => {
|
||||
let concatenated = format!("{}{}", left, right);
|
||||
|
||||
String(DustString::from(concatenated))
|
||||
}
|
||||
(String(left), String(right)) => {
|
||||
let concatenated = format!("{}{}", left, right);
|
||||
|
||||
String(DustString::from(concatenated))
|
||||
}
|
||||
_ => panic!(
|
||||
"{}",
|
||||
ValueError::CannotAdd(
|
||||
Value::Concrete(self.clone()),
|
||||
Value::Concrete(other.clone())
|
||||
)
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subtract(&self, other: &Self) -> Result<ConcreteValue, ValueError> {
|
||||
pub fn subtract(&self, other: &Self) -> ConcreteValue {
|
||||
use ConcreteValue::*;
|
||||
|
||||
let difference = match (self, other) {
|
||||
match (self, other) {
|
||||
(Byte(left), Byte(right)) => ConcreteValue::Byte(left.saturating_sub(*right)),
|
||||
(Float(left), Float(right)) => ConcreteValue::Float(left - right),
|
||||
(Integer(left), Integer(right)) => ConcreteValue::Integer(left.saturating_sub(*right)),
|
||||
_ => {
|
||||
return Err(ValueError::CannotSubtract(
|
||||
self.clone().to_value(),
|
||||
other.clone().to_value(),
|
||||
))
|
||||
_ => panic!(
|
||||
"{}",
|
||||
ValueError::CannotSubtract(
|
||||
Value::Concrete(self.clone()),
|
||||
Value::Concrete(other.clone())
|
||||
)
|
||||
),
|
||||
}
|
||||
};
|
||||
|
||||
Ok(difference)
|
||||
}
|
||||
|
||||
pub fn multiply(&self, other: &Self) -> Result<ConcreteValue, ValueError> {
|
||||
@ -155,18 +192,16 @@ impl ConcreteValue {
|
||||
Ok(product)
|
||||
}
|
||||
|
||||
pub fn negate(&self) -> Result<ConcreteValue, ValueError> {
|
||||
pub fn negate(&self) -> ConcreteValue {
|
||||
use ConcreteValue::*;
|
||||
|
||||
let negated = match self {
|
||||
match self {
|
||||
Boolean(value) => ConcreteValue::Boolean(!value),
|
||||
Byte(value) => ConcreteValue::Byte(value.wrapping_neg()),
|
||||
Float(value) => ConcreteValue::Float(-value),
|
||||
Integer(value) => ConcreteValue::Integer(value.wrapping_neg()),
|
||||
_ => return Err(ValueError::CannotNegate(self.clone().to_value())),
|
||||
};
|
||||
|
||||
Ok(negated)
|
||||
_ => panic!("{}", ValueError::CannotNegate(self.clone().to_value())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn not(&self) -> Result<ConcreteValue, ValueError> {
|
||||
@ -180,28 +215,28 @@ impl ConcreteValue {
|
||||
Ok(not)
|
||||
}
|
||||
|
||||
pub fn equal(&self, other: &ConcreteValue) -> Result<ConcreteValue, ValueError> {
|
||||
pub fn equals(&self, other: &ConcreteValue) -> bool {
|
||||
use ConcreteValue::*;
|
||||
|
||||
let equal = match (self, other) {
|
||||
(Boolean(left), Boolean(right)) => ConcreteValue::Boolean(left == right),
|
||||
(Byte(left), Byte(right)) => ConcreteValue::Boolean(left == right),
|
||||
(Character(left), Character(right)) => ConcreteValue::Boolean(left == right),
|
||||
(Float(left), Float(right)) => ConcreteValue::Boolean(left == right),
|
||||
(Function(left), Function(right)) => ConcreteValue::Boolean(left == right),
|
||||
(Integer(left), Integer(right)) => ConcreteValue::Boolean(left == right),
|
||||
(List(left), List(right)) => ConcreteValue::Boolean(left == right),
|
||||
(Range(left), Range(right)) => ConcreteValue::Boolean(left == right),
|
||||
(String(left), String(right)) => ConcreteValue::Boolean(left == right),
|
||||
match (self, other) {
|
||||
(Boolean(left), Boolean(right)) => left == right,
|
||||
(Byte(left), Byte(right)) => left == right,
|
||||
(Character(left), Character(right)) => left == right,
|
||||
(Float(left), Float(right)) => left == right,
|
||||
(Integer(left), Integer(right)) => left == right,
|
||||
(List(left), List(right)) => left == right,
|
||||
(Range(left), Range(right)) => left == right,
|
||||
(String(left), String(right)) => left == right,
|
||||
_ => {
|
||||
return Err(ValueError::CannotCompare(
|
||||
self.clone().to_value(),
|
||||
other.clone().to_value(),
|
||||
))
|
||||
panic!(
|
||||
"{}",
|
||||
ValueError::CannotCompare(
|
||||
Value::Concrete(self.clone()),
|
||||
Value::Concrete(other.clone())
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(equal)
|
||||
}
|
||||
|
||||
pub fn less_than(&self, other: &ConcreteValue) -> Result<ConcreteValue, ValueError> {
|
||||
@ -212,7 +247,6 @@ impl ConcreteValue {
|
||||
(Byte(left), Byte(right)) => ConcreteValue::Boolean(left < right),
|
||||
(Character(left), Character(right)) => ConcreteValue::Boolean(left < right),
|
||||
(Float(left), Float(right)) => ConcreteValue::Boolean(left < right),
|
||||
(Function(left), Function(right)) => ConcreteValue::Boolean(left < right),
|
||||
(Integer(left), Integer(right)) => ConcreteValue::Boolean(left < right),
|
||||
(List(left), List(right)) => ConcreteValue::Boolean(left < right),
|
||||
(Range(left), Range(right)) => ConcreteValue::Boolean(left < right),
|
||||
@ -228,7 +262,7 @@ impl ConcreteValue {
|
||||
Ok(less_than)
|
||||
}
|
||||
|
||||
pub fn less_than_or_equal(&self, other: &ConcreteValue) -> Result<ConcreteValue, ValueError> {
|
||||
pub fn less_than_or_equals(&self, other: &ConcreteValue) -> Result<ConcreteValue, ValueError> {
|
||||
use ConcreteValue::*;
|
||||
|
||||
let less_than_or_equal = match (self, other) {
|
||||
@ -236,7 +270,6 @@ impl ConcreteValue {
|
||||
(Byte(left), Byte(right)) => ConcreteValue::Boolean(left <= right),
|
||||
(Character(left), Character(right)) => ConcreteValue::Boolean(left <= right),
|
||||
(Float(left), Float(right)) => ConcreteValue::Boolean(left <= right),
|
||||
(Function(left), Function(right)) => ConcreteValue::Boolean(left <= right),
|
||||
(Integer(left), Integer(right)) => ConcreteValue::Boolean(left <= right),
|
||||
(List(left), List(right)) => ConcreteValue::Boolean(left <= right),
|
||||
(Range(left), Range(right)) => ConcreteValue::Boolean(left <= right),
|
||||
@ -255,14 +288,13 @@ impl ConcreteValue {
|
||||
|
||||
impl Clone for ConcreteValue {
|
||||
fn clone(&self) -> Self {
|
||||
log::trace!("Cloning concrete value {}", self);
|
||||
trace!("Cloning concrete value {}", self);
|
||||
|
||||
match self {
|
||||
ConcreteValue::Boolean(boolean) => ConcreteValue::Boolean(*boolean),
|
||||
ConcreteValue::Byte(byte) => ConcreteValue::Byte(*byte),
|
||||
ConcreteValue::Character(character) => ConcreteValue::Character(*character),
|
||||
ConcreteValue::Float(float) => ConcreteValue::Float(*float),
|
||||
ConcreteValue::Function(function) => ConcreteValue::Function(function.clone()),
|
||||
ConcreteValue::Integer(integer) => ConcreteValue::Integer(*integer),
|
||||
ConcreteValue::List(list) => ConcreteValue::List(list.clone()),
|
||||
ConcreteValue::Range(range) => ConcreteValue::Range(*range),
|
||||
@ -286,7 +318,6 @@ impl Display for ConcreteValue {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
ConcreteValue::Function(chunk) => write!(f, "{}", chunk.r#type()),
|
||||
ConcreteValue::Integer(integer) => write!(f, "{integer}"),
|
||||
ConcreteValue::List(list) => {
|
||||
write!(f, "[")?;
|
||||
|
26
dust-lang/src/value/function.rs
Normal file
26
dust-lang/src/value/function.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use crate::FunctionType;
|
||||
|
||||
use super::DustString;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd)]
|
||||
pub struct Function {
|
||||
pub name: Option<DustString>,
|
||||
pub r#type: FunctionType,
|
||||
pub prototype_index: u8,
|
||||
}
|
||||
|
||||
impl Display for Function {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
let mut type_string = self.r#type.to_string();
|
||||
|
||||
if let Some(name) = &self.name {
|
||||
debug_assert!(type_string.starts_with("fn "));
|
||||
|
||||
type_string.insert_str(3, name);
|
||||
}
|
||||
|
||||
write!(f, "{type_string}")
|
||||
}
|
||||
}
|
@ -1,107 +1,114 @@
|
||||
//! Runtime values used by the VM.
|
||||
mod abstract_value;
|
||||
mod abstract_list;
|
||||
mod concrete_value;
|
||||
mod function;
|
||||
mod range_value;
|
||||
|
||||
pub use abstract_value::AbstractValue;
|
||||
pub use concrete_value::ConcreteValue;
|
||||
pub use abstract_list::AbstractList;
|
||||
pub use concrete_value::{ConcreteValue, DustString};
|
||||
pub use function::Function;
|
||||
pub use range_value::RangeValue;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
|
||||
use crate::{Vm, VmError};
|
||||
use crate::{vm::ThreadData, Type};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd)]
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
|
||||
pub enum Value {
|
||||
Abstract(AbstractValue),
|
||||
Concrete(ConcreteValue),
|
||||
|
||||
#[serde(skip)]
|
||||
AbstractList(AbstractList),
|
||||
|
||||
#[serde(skip)]
|
||||
Function(Function),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn to_ref(&self) -> ValueRef {
|
||||
pub fn boolean(boolean: bool) -> Self {
|
||||
Value::Concrete(ConcreteValue::Boolean(boolean))
|
||||
}
|
||||
|
||||
pub fn byte(byte: u8) -> Self {
|
||||
Value::Concrete(ConcreteValue::Byte(byte))
|
||||
}
|
||||
|
||||
pub fn character(character: char) -> Self {
|
||||
Value::Concrete(ConcreteValue::Character(character))
|
||||
}
|
||||
|
||||
pub fn float(float: f64) -> Self {
|
||||
Value::Concrete(ConcreteValue::Float(float))
|
||||
}
|
||||
|
||||
pub fn integer(integer: i64) -> Self {
|
||||
Value::Concrete(ConcreteValue::Integer(integer))
|
||||
}
|
||||
|
||||
pub fn string(string: impl Into<DustString>) -> Self {
|
||||
Value::Concrete(ConcreteValue::String(string.into()))
|
||||
}
|
||||
|
||||
pub fn as_boolean(&self) -> Option<&bool> {
|
||||
if let Value::Concrete(ConcreteValue::Boolean(value)) = self {
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_function(&self) -> Option<&Function> {
|
||||
if let Value::Function(function) = self {
|
||||
Some(function)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_string(&self) -> Option<&DustString> {
|
||||
if let Value::Concrete(ConcreteValue::String(value)) = self {
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn r#type(&self) -> Type {
|
||||
match self {
|
||||
Value::Abstract(abstract_value) => ValueRef::Abstract(abstract_value),
|
||||
Value::Concrete(concrete_value) => ValueRef::Concrete(concrete_value),
|
||||
Value::Concrete(concrete_value) => concrete_value.r#type(),
|
||||
Value::AbstractList(AbstractList { item_type, .. }) => {
|
||||
Type::List(Box::new(item_type.clone()))
|
||||
}
|
||||
Value::Function(Function { r#type, .. }) => Type::Function(Box::new(r#type.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_concrete_owned(&self, vm: &Vm) -> Result<ConcreteValue, VmError> {
|
||||
match self {
|
||||
Value::Abstract(abstract_value) => abstract_value.to_concrete_owned(vm),
|
||||
Value::Concrete(concrete_value) => Ok(concrete_value.clone()),
|
||||
}
|
||||
pub fn add(&self, other: &Value) -> Value {
|
||||
let sum = match (self, other) {
|
||||
(Value::Concrete(left), Value::Concrete(right)) => left.add(right),
|
||||
_ => panic!("{}", ValueError::CannotAdd(self.clone(), other.clone())),
|
||||
};
|
||||
|
||||
Value::Concrete(sum)
|
||||
}
|
||||
|
||||
pub fn display(&self, vm: &Vm) -> Result<String, VmError> {
|
||||
match self {
|
||||
Value::Abstract(abstract_value) => abstract_value.display(vm),
|
||||
Value::Concrete(concrete_value) => Ok(concrete_value.to_string()),
|
||||
}
|
||||
}
|
||||
pub fn subtract(&self, other: &Value) -> Value {
|
||||
let difference = match (self, other) {
|
||||
(Value::Concrete(left), Value::Concrete(right)) => left.subtract(right),
|
||||
_ => panic!(
|
||||
"{}",
|
||||
ValueError::CannotSubtract(self.clone(), other.clone())
|
||||
),
|
||||
};
|
||||
|
||||
Value::Concrete(difference)
|
||||
}
|
||||
|
||||
impl Display for Value {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Value::Abstract(abstract_value) => write!(f, "{}", abstract_value),
|
||||
Value::Concrete(concrete_value) => write!(f, "{}", concrete_value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
|
||||
pub enum ValueRef<'a> {
|
||||
Abstract(&'a AbstractValue),
|
||||
Concrete(&'a ConcreteValue),
|
||||
}
|
||||
|
||||
impl ValueRef<'_> {
|
||||
pub fn to_owned(&self) -> Value {
|
||||
match self {
|
||||
ValueRef::Abstract(abstract_value) => Value::Abstract((*abstract_value).clone()),
|
||||
ValueRef::Concrete(concrete_value) => Value::Concrete((*concrete_value).clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_concrete_owned(&self, vm: &Vm) -> Result<ConcreteValue, VmError> {
|
||||
match self {
|
||||
ValueRef::Abstract(abstract_value) => abstract_value.to_concrete_owned(vm),
|
||||
ValueRef::Concrete(concrete_value) => Ok((*concrete_value).clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display(&self, vm: &Vm) -> Result<String, VmError> {
|
||||
match self {
|
||||
ValueRef::Abstract(abstract_value) => abstract_value.display(vm),
|
||||
ValueRef::Concrete(concrete_value) => Ok(concrete_value.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&self, other: ValueRef) -> Result<Value, ValueError> {
|
||||
pub fn multiply(&self, other: &Value) -> Result<Value, ValueError> {
|
||||
match (self, other) {
|
||||
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => {
|
||||
left.add(right).map(|result| result.to_value())
|
||||
}
|
||||
_ => Err(ValueError::CannotAdd(self.to_owned(), other.to_owned())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subtract(&self, other: ValueRef) -> Result<Value, ValueError> {
|
||||
match (self, other) {
|
||||
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => {
|
||||
left.subtract(right).map(|result| result.to_value())
|
||||
}
|
||||
_ => Err(ValueError::CannotSubtract(
|
||||
self.to_owned(),
|
||||
other.to_owned(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn multiply(&self, other: ValueRef) -> Result<Value, ValueError> {
|
||||
match (self, other) {
|
||||
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => {
|
||||
left.multiply(right).map(|result| result.to_value())
|
||||
(Value::Concrete(left), Value::Concrete(right)) => {
|
||||
left.multiply(right).map(Value::Concrete)
|
||||
}
|
||||
_ => Err(ValueError::CannotMultiply(
|
||||
self.to_owned(),
|
||||
@ -110,75 +117,83 @@ impl ValueRef<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn divide(&self, other: ValueRef) -> Result<Value, ValueError> {
|
||||
pub fn divide(&self, other: &Value) -> Result<Value, ValueError> {
|
||||
match (self, other) {
|
||||
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => {
|
||||
left.divide(right).map(|result| result.to_value())
|
||||
(Value::Concrete(left), Value::Concrete(right)) => {
|
||||
left.divide(right).map(Value::Concrete)
|
||||
}
|
||||
_ => Err(ValueError::CannotDivide(self.to_owned(), other.to_owned())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn modulo(&self, other: ValueRef) -> Result<Value, ValueError> {
|
||||
pub fn modulo(&self, other: &Value) -> Result<Value, ValueError> {
|
||||
match (self, other) {
|
||||
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => {
|
||||
left.modulo(right).map(|result| result.to_value())
|
||||
(Value::Concrete(left), Value::Concrete(right)) => {
|
||||
left.modulo(right).map(Value::Concrete)
|
||||
}
|
||||
_ => Err(ValueError::CannotModulo(self.to_owned(), other.to_owned())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn negate(&self) -> Result<Value, ValueError> {
|
||||
match self {
|
||||
ValueRef::Concrete(concrete_value) => {
|
||||
concrete_value.negate().map(|result| result.to_value())
|
||||
}
|
||||
_ => Err(ValueError::CannotNegate(self.to_owned())),
|
||||
}
|
||||
pub fn negate(&self) -> Value {
|
||||
let concrete = match self {
|
||||
Value::Concrete(concrete_value) => concrete_value.negate(),
|
||||
_ => panic!("{}", ValueError::CannotNegate(self.clone())),
|
||||
};
|
||||
|
||||
Value::Concrete(concrete)
|
||||
}
|
||||
|
||||
pub fn not(&self) -> Result<Value, ValueError> {
|
||||
match self {
|
||||
ValueRef::Concrete(concrete_value) => {
|
||||
concrete_value.not().map(|result| result.to_value())
|
||||
}
|
||||
Value::Concrete(concrete_value) => concrete_value.not().map(Value::Concrete),
|
||||
_ => Err(ValueError::CannotNot(self.to_owned())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn equal(&self, other: ValueRef) -> Result<Value, ValueError> {
|
||||
pub fn equals(&self, other: &Value) -> bool {
|
||||
match (self, other) {
|
||||
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => {
|
||||
left.equal(right).map(|result| result.to_value())
|
||||
(Value::Concrete(left), Value::Concrete(right)) => left.equals(right),
|
||||
_ => panic!(
|
||||
"{}",
|
||||
ValueError::CannotCompare(self.to_owned(), other.to_owned())
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn less(&self, other: &Value) -> Result<Value, ValueError> {
|
||||
match (self, other) {
|
||||
(Value::Concrete(left), Value::Concrete(right)) => {
|
||||
left.less_than(right).map(Value::Concrete)
|
||||
}
|
||||
_ => Err(ValueError::CannotCompare(self.to_owned(), other.to_owned())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn less_than(&self, other: ValueRef) -> Result<Value, ValueError> {
|
||||
pub fn less_than_or_equals(&self, other: &Value) -> Result<Value, ValueError> {
|
||||
match (self, other) {
|
||||
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => {
|
||||
left.less_than(right).map(|result| result.to_value())
|
||||
(Value::Concrete(left), Value::Concrete(right)) => {
|
||||
left.less_than_or_equals(right).map(Value::Concrete)
|
||||
}
|
||||
_ => Err(ValueError::CannotCompare(self.to_owned(), other.to_owned())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn less_than_or_equal(&self, other: ValueRef) -> Result<Value, ValueError> {
|
||||
match (self, other) {
|
||||
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => left
|
||||
.less_than_or_equal(right)
|
||||
.map(|result| result.to_value()),
|
||||
_ => Err(ValueError::CannotCompare(self.to_owned(), other.to_owned())),
|
||||
pub fn display(&self, data: &ThreadData) -> DustString {
|
||||
match self {
|
||||
Value::AbstractList(list) => list.display(data),
|
||||
Value::Concrete(concrete_value) => concrete_value.display(),
|
||||
Value::Function(function) => DustString::from(function.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ValueRef<'_> {
|
||||
impl Display for Value {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ValueRef::Abstract(abstract_value) => write!(f, "{}", abstract_value),
|
||||
ValueRef::Concrete(concrete_value) => write!(f, "{}", concrete_value),
|
||||
Value::Concrete(concrete_value) => write!(f, "{concrete_value}"),
|
||||
Value::AbstractList(list) => write!(f, "{list}"),
|
||||
Value::Function(function) => write!(f, "{function}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,903 +0,0 @@
|
||||
//! Virtual machine and errors
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
fmt::{self, Display, Formatter},
|
||||
io,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
compile, instruction::*, AbstractValue, AnnotatedError, Argument, Chunk, ConcreteValue,
|
||||
Destination, DustError, Instruction, NativeFunctionError, Operation, Span, Value, ValueError,
|
||||
ValueRef,
|
||||
};
|
||||
|
||||
pub fn run(source: &str) -> Result<Option<ConcreteValue>, DustError> {
|
||||
let chunk = compile(source)?;
|
||||
let mut vm = Vm::new(&chunk, None);
|
||||
|
||||
vm.run()
|
||||
.map_err(|error| DustError::Runtime { error, source })
|
||||
}
|
||||
|
||||
/// Dust virtual machine.
|
||||
///
|
||||
/// See the [module-level documentation](index.html) for more information.
|
||||
#[derive(Debug)]
|
||||
pub struct Vm<'a> {
|
||||
chunk: &'a Chunk,
|
||||
stack: Vec<Register>,
|
||||
parent: Option<&'a Vm<'a>>,
|
||||
local_definitions: Vec<Option<u16>>,
|
||||
|
||||
ip: usize,
|
||||
last_assigned_register: Option<u16>,
|
||||
current_position: Span,
|
||||
}
|
||||
|
||||
impl<'a> Vm<'a> {
|
||||
const STACK_LIMIT: usize = u16::MAX as usize;
|
||||
|
||||
pub fn new(chunk: &'a Chunk, parent: Option<&'a Vm<'a>>) -> Self {
|
||||
Self {
|
||||
chunk,
|
||||
stack: Vec::new(),
|
||||
parent,
|
||||
local_definitions: vec![None; chunk.locals().len()],
|
||||
ip: 0,
|
||||
last_assigned_register: None,
|
||||
current_position: Span(0, 0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chunk(&self) -> &Chunk {
|
||||
self.chunk
|
||||
}
|
||||
|
||||
pub fn current_position(&self) -> Span {
|
||||
self.current_position
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Result<Option<ConcreteValue>, VmError> {
|
||||
while let Ok(instruction) = self.read() {
|
||||
log::info!(
|
||||
"{} | {} | {} | {}",
|
||||
self.ip - 1,
|
||||
self.current_position,
|
||||
instruction.operation(),
|
||||
instruction.disassembly_info()
|
||||
);
|
||||
|
||||
match instruction.operation() {
|
||||
Operation::Move => {
|
||||
let Move { from, to } = Move::from(&instruction);
|
||||
let from_register_has_value = self
|
||||
.stack
|
||||
.get(from as usize)
|
||||
.is_some_and(|register| !matches!(register, Register::Empty));
|
||||
let register = Register::Pointer(Pointer::Stack(from));
|
||||
|
||||
if from_register_has_value {
|
||||
self.set_register(to, register)?;
|
||||
}
|
||||
}
|
||||
Operation::Close => {
|
||||
let Close { from, to } = Close::from(&instruction);
|
||||
|
||||
if self.stack.len() < to as usize {
|
||||
return Err(VmError::StackUnderflow {
|
||||
position: self.current_position,
|
||||
});
|
||||
}
|
||||
|
||||
for register_index in from..to {
|
||||
self.stack[register_index as usize] = Register::Empty;
|
||||
}
|
||||
}
|
||||
Operation::LoadBoolean => {
|
||||
let LoadBoolean {
|
||||
destination,
|
||||
value,
|
||||
jump_next,
|
||||
} = LoadBoolean::from(&instruction);
|
||||
let register_index = self.get_destination(destination)?;
|
||||
let boolean = ConcreteValue::Boolean(value).to_value();
|
||||
let register = Register::Value(boolean);
|
||||
|
||||
self.set_register(register_index, register)?;
|
||||
|
||||
if jump_next {
|
||||
self.jump(1, true);
|
||||
}
|
||||
}
|
||||
Operation::LoadConstant => {
|
||||
let LoadConstant {
|
||||
destination,
|
||||
constant_index,
|
||||
jump_next,
|
||||
} = LoadConstant::from(&instruction);
|
||||
let register_index = self.get_destination(destination)?;
|
||||
let register = Register::Pointer(Pointer::Constant(constant_index));
|
||||
|
||||
self.set_register(register_index, register)?;
|
||||
|
||||
if jump_next {
|
||||
self.jump(1, true);
|
||||
}
|
||||
}
|
||||
Operation::LoadList => {
|
||||
let LoadList {
|
||||
destination,
|
||||
start_register,
|
||||
} = LoadList::from(&instruction);
|
||||
let register_index = self.get_destination(destination)?;
|
||||
let mut pointers = Vec::new();
|
||||
|
||||
for register in start_register..register_index {
|
||||
if let Some(Register::Empty) = self.stack.get(register as usize) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let pointer = Pointer::Stack(register);
|
||||
|
||||
pointers.push(pointer);
|
||||
}
|
||||
|
||||
let register =
|
||||
Register::Value(AbstractValue::List { items: pointers }.to_value());
|
||||
|
||||
self.set_register(register_index, register)?;
|
||||
}
|
||||
Operation::LoadSelf => {
|
||||
let LoadSelf { destination } = LoadSelf::from(&instruction);
|
||||
let register_index = self.get_destination(destination)?;
|
||||
let register = Register::Value(AbstractValue::FunctionSelf.to_value());
|
||||
|
||||
self.set_register(register_index, register)?;
|
||||
}
|
||||
Operation::DefineLocal => {
|
||||
let DefineLocal {
|
||||
register,
|
||||
local_index,
|
||||
is_mutable,
|
||||
} = DefineLocal::from(&instruction);
|
||||
|
||||
self.local_definitions[local_index as usize] = Some(register);
|
||||
}
|
||||
Operation::GetLocal => {
|
||||
let GetLocal {
|
||||
destination,
|
||||
local_index,
|
||||
} = GetLocal::from(&instruction);
|
||||
let register_index = self.get_destination(destination)?;
|
||||
let local_register = self.local_definitions[local_index as usize].ok_or(
|
||||
VmError::UndefinedLocal {
|
||||
local_index,
|
||||
position: self.current_position,
|
||||
},
|
||||
)?;
|
||||
let register = Register::Pointer(Pointer::Stack(local_register));
|
||||
|
||||
self.set_register(register_index, register)?;
|
||||
}
|
||||
Operation::SetLocal => {
|
||||
let SetLocal {
|
||||
register,
|
||||
local_index,
|
||||
} = SetLocal::from(&instruction);
|
||||
|
||||
self.local_definitions[local_index as usize] = Some(register);
|
||||
}
|
||||
Operation::Add => {
|
||||
let Add {
|
||||
destination,
|
||||
left,
|
||||
right,
|
||||
} = Add::from(&instruction);
|
||||
let register_index = self.get_destination(destination)?;
|
||||
let left = self.get_argument(left)?;
|
||||
let right = self.get_argument(right)?;
|
||||
let sum = left.add(right).map_err(|error| VmError::Value {
|
||||
error,
|
||||
position: self.current_position,
|
||||
})?;
|
||||
|
||||
self.set_register(register_index, Register::Value(sum))?;
|
||||
}
|
||||
Operation::Subtract => {
|
||||
let Subtract {
|
||||
destination,
|
||||
left,
|
||||
right,
|
||||
} = Subtract::from(&instruction);
|
||||
let register_index = self.get_destination(destination)?;
|
||||
let left = self.get_argument(left)?;
|
||||
let right = self.get_argument(right)?;
|
||||
let difference = left.subtract(right).map_err(|error| VmError::Value {
|
||||
error,
|
||||
position: self.current_position,
|
||||
})?;
|
||||
|
||||
self.set_register(register_index, Register::Value(difference))?;
|
||||
}
|
||||
Operation::Multiply => {
|
||||
let Multiply {
|
||||
destination,
|
||||
left,
|
||||
right,
|
||||
} = Multiply::from(&instruction);
|
||||
let register_index = self.get_destination(destination)?;
|
||||
let left = self.get_argument(left)?;
|
||||
let right = self.get_argument(right)?;
|
||||
let product = left.multiply(right).map_err(|error| VmError::Value {
|
||||
error,
|
||||
position: self.current_position,
|
||||
})?;
|
||||
|
||||
self.set_register(register_index, Register::Value(product))?;
|
||||
}
|
||||
Operation::Divide => {
|
||||
let Divide {
|
||||
destination,
|
||||
left,
|
||||
right,
|
||||
} = Divide::from(&instruction);
|
||||
let register_index = self.get_destination(destination)?;
|
||||
let left = self.get_argument(left)?;
|
||||
let right = self.get_argument(right)?;
|
||||
let quotient = left.divide(right).map_err(|error| VmError::Value {
|
||||
error,
|
||||
position: self.current_position,
|
||||
})?;
|
||||
|
||||
self.set_register(register_index, Register::Value(quotient))?;
|
||||
}
|
||||
Operation::Modulo => {
|
||||
let Modulo {
|
||||
destination,
|
||||
left,
|
||||
right,
|
||||
} = Modulo::from(&instruction);
|
||||
let register_index = self.get_destination(destination)?;
|
||||
let left = self.get_argument(left)?;
|
||||
let right = self.get_argument(right)?;
|
||||
let remainder = left.modulo(right).map_err(|error| VmError::Value {
|
||||
error,
|
||||
position: self.current_position,
|
||||
})?;
|
||||
|
||||
self.set_register(register_index, Register::Value(remainder))?;
|
||||
}
|
||||
Operation::Test => {
|
||||
let Test {
|
||||
argument,
|
||||
test_value,
|
||||
} = Test::from(&instruction);
|
||||
let value = self.get_argument(argument)?;
|
||||
let boolean = if let ValueRef::Concrete(ConcreteValue::Boolean(boolean)) = value
|
||||
{
|
||||
*boolean
|
||||
} else {
|
||||
return Err(VmError::ExpectedBoolean {
|
||||
found: value.to_owned(),
|
||||
position: self.current_position,
|
||||
});
|
||||
};
|
||||
|
||||
if boolean == test_value {
|
||||
self.jump(1, true);
|
||||
}
|
||||
}
|
||||
Operation::TestSet => {
|
||||
let TestSet {
|
||||
destination,
|
||||
argument,
|
||||
test_value,
|
||||
} = TestSet::from(&instruction);
|
||||
let register_index = self.get_destination(destination)?;
|
||||
let value = self.get_argument(argument)?;
|
||||
let boolean = if let ValueRef::Concrete(ConcreteValue::Boolean(boolean)) = value
|
||||
{
|
||||
*boolean
|
||||
} else {
|
||||
return Err(VmError::ExpectedBoolean {
|
||||
found: value.to_owned(),
|
||||
position: self.current_position,
|
||||
});
|
||||
};
|
||||
|
||||
if boolean == test_value {
|
||||
self.jump(1, true);
|
||||
} else {
|
||||
let pointer = match argument {
|
||||
Argument::Constant(constant_index) => Pointer::Constant(constant_index),
|
||||
Argument::Local(local_index) => {
|
||||
let register_index = self.local_definitions[local_index as usize]
|
||||
.ok_or(VmError::UndefinedLocal {
|
||||
local_index,
|
||||
position: self.current_position,
|
||||
})?;
|
||||
|
||||
Pointer::Stack(register_index)
|
||||
}
|
||||
Argument::Register(register_index) => Pointer::Stack(register_index),
|
||||
};
|
||||
let register = Register::Pointer(pointer);
|
||||
|
||||
self.set_register(register_index, register)?;
|
||||
}
|
||||
}
|
||||
Operation::Equal => {
|
||||
let Equal { value, left, right } = Equal::from(&instruction);
|
||||
let left = self.get_argument(left)?;
|
||||
let right = self.get_argument(right)?;
|
||||
let equal_result = left.equal(right).map_err(|error| VmError::Value {
|
||||
error,
|
||||
position: self.current_position,
|
||||
})?;
|
||||
let is_equal =
|
||||
if let Value::Concrete(ConcreteValue::Boolean(boolean)) = equal_result {
|
||||
boolean
|
||||
} else {
|
||||
return Err(VmError::ExpectedBoolean {
|
||||
found: equal_result,
|
||||
position: self.current_position,
|
||||
});
|
||||
};
|
||||
|
||||
if is_equal == value {
|
||||
self.jump(1, true);
|
||||
}
|
||||
}
|
||||
Operation::Less => {
|
||||
let Less { value, left, right } = Less::from(&instruction);
|
||||
let left = self.get_argument(left)?;
|
||||
let right = self.get_argument(right)?;
|
||||
let less_result = left.less_than(right).map_err(|error| VmError::Value {
|
||||
error,
|
||||
position: self.current_position,
|
||||
})?;
|
||||
let is_less_than =
|
||||
if let Value::Concrete(ConcreteValue::Boolean(boolean)) = less_result {
|
||||
boolean
|
||||
} else {
|
||||
return Err(VmError::ExpectedBoolean {
|
||||
found: less_result,
|
||||
position: self.current_position,
|
||||
});
|
||||
};
|
||||
|
||||
if is_less_than == value {
|
||||
self.jump(1, true);
|
||||
}
|
||||
}
|
||||
Operation::LessEqual => {
|
||||
let LessEqual { value, left, right } = LessEqual::from(&instruction);
|
||||
let left = self.get_argument(left)?;
|
||||
let right = self.get_argument(right)?;
|
||||
let less_or_equal_result =
|
||||
left.less_than_or_equal(right)
|
||||
.map_err(|error| VmError::Value {
|
||||
error,
|
||||
position: self.current_position,
|
||||
})?;
|
||||
let is_less_than_or_equal =
|
||||
if let Value::Concrete(ConcreteValue::Boolean(boolean)) =
|
||||
less_or_equal_result
|
||||
{
|
||||
boolean
|
||||
} else {
|
||||
return Err(VmError::ExpectedBoolean {
|
||||
found: less_or_equal_result,
|
||||
position: self.current_position,
|
||||
});
|
||||
};
|
||||
|
||||
if is_less_than_or_equal == value {
|
||||
self.jump(1, true);
|
||||
}
|
||||
}
|
||||
Operation::Negate => {
|
||||
let Negate {
|
||||
destination,
|
||||
argument,
|
||||
} = Negate::from(&instruction);
|
||||
let value = self.get_argument(argument)?;
|
||||
let negated = value.negate().map_err(|error| VmError::Value {
|
||||
error,
|
||||
position: self.current_position,
|
||||
})?;
|
||||
let register_index = self.get_destination(destination)?;
|
||||
let register = Register::Value(negated);
|
||||
|
||||
self.set_register(register_index, register)?;
|
||||
}
|
||||
Operation::Not => {
|
||||
let Not {
|
||||
destination,
|
||||
argument,
|
||||
} = Not::from(&instruction);
|
||||
let value = self.get_argument(argument)?;
|
||||
let not = value.not().map_err(|error| VmError::Value {
|
||||
error,
|
||||
position: self.current_position,
|
||||
})?;
|
||||
let register_index = self.get_destination(destination)?;
|
||||
let register = Register::Value(not);
|
||||
|
||||
self.set_register(register_index, register)?;
|
||||
}
|
||||
Operation::Jump => {
|
||||
let Jump {
|
||||
offset,
|
||||
is_positive,
|
||||
} = Jump::from(&instruction);
|
||||
|
||||
self.jump(offset as usize, is_positive);
|
||||
}
|
||||
Operation::Call => {
|
||||
let Call {
|
||||
destination,
|
||||
function,
|
||||
argument_count,
|
||||
} = Call::from(&instruction);
|
||||
let register_index = self.get_destination(destination)?;
|
||||
let function = self.get_argument(function)?;
|
||||
let chunk = if let ValueRef::Concrete(ConcreteValue::Function(chunk)) = function
|
||||
{
|
||||
chunk
|
||||
} else if let ValueRef::Abstract(AbstractValue::FunctionSelf) = function {
|
||||
self.chunk
|
||||
} else {
|
||||
return Err(VmError::ExpectedFunction {
|
||||
found: function.to_concrete_owned(self)?,
|
||||
position: self.current_position,
|
||||
});
|
||||
};
|
||||
let mut function_vm = Vm::new(chunk, Some(self));
|
||||
let first_argument_index = register_index - argument_count;
|
||||
|
||||
for (argument_index, argument_register_index) in
|
||||
(first_argument_index..register_index).enumerate()
|
||||
{
|
||||
function_vm.set_register(
|
||||
argument_index as u16,
|
||||
Register::Pointer(Pointer::ParentStack(argument_register_index)),
|
||||
)?;
|
||||
|
||||
function_vm.local_definitions[argument_index] = Some(argument_index as u16);
|
||||
}
|
||||
|
||||
let return_value = function_vm.run()?;
|
||||
|
||||
if let Some(concrete_value) = return_value {
|
||||
let register = Register::Value(concrete_value.to_value());
|
||||
|
||||
self.set_register(register_index, register)?;
|
||||
}
|
||||
}
|
||||
Operation::CallNative => {
|
||||
let CallNative {
|
||||
destination,
|
||||
function,
|
||||
argument_count,
|
||||
} = CallNative::from(&instruction);
|
||||
let return_value = function.call(self, instruction)?;
|
||||
|
||||
if let Some(value) = return_value {
|
||||
let register_index = self.get_destination(destination)?;
|
||||
let register = Register::Value(value);
|
||||
|
||||
self.set_register(register_index, register)?;
|
||||
}
|
||||
}
|
||||
Operation::Return => {
|
||||
let Return {
|
||||
should_return_value,
|
||||
} = Return::from(&instruction);
|
||||
|
||||
if !should_return_value {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
return if let Some(register_index) = self.last_assigned_register {
|
||||
let return_value = self
|
||||
.open_register(register_index)?
|
||||
.to_concrete_owned(self)?;
|
||||
|
||||
Ok(Some(return_value))
|
||||
} else {
|
||||
Err(VmError::StackUnderflow {
|
||||
position: self.current_position,
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub(crate) fn follow_pointer(&self, pointer: Pointer) -> Result<ValueRef, VmError> {
|
||||
match pointer {
|
||||
Pointer::Stack(register_index) => self.open_register(register_index),
|
||||
Pointer::Constant(constant_index) => {
|
||||
let constant = self.get_constant(constant_index)?;
|
||||
|
||||
Ok(ValueRef::Concrete(constant))
|
||||
}
|
||||
Pointer::ParentStack(register_index) => {
|
||||
let parent = self
|
||||
.parent
|
||||
.as_ref()
|
||||
.ok_or_else(|| VmError::ExpectedParent {
|
||||
position: self.current_position,
|
||||
})?;
|
||||
|
||||
parent.open_register(register_index)
|
||||
}
|
||||
Pointer::ParentConstant(constant_index) => {
|
||||
let parent = self
|
||||
.parent
|
||||
.as_ref()
|
||||
.ok_or_else(|| VmError::ExpectedParent {
|
||||
position: self.current_position,
|
||||
})?;
|
||||
let constant = parent.get_constant(constant_index)?;
|
||||
|
||||
Ok(ValueRef::Concrete(constant))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn open_register(&self, register_index: u16) -> Result<ValueRef, VmError> {
|
||||
let register_index = register_index as usize;
|
||||
let register =
|
||||
self.stack
|
||||
.get(register_index)
|
||||
.ok_or_else(|| VmError::RegisterIndexOutOfBounds {
|
||||
index: register_index,
|
||||
position: self.current_position,
|
||||
})?;
|
||||
|
||||
log::trace!("Open R{register_index} to {register}");
|
||||
|
||||
match register {
|
||||
Register::Value(value) => Ok(value.to_ref()),
|
||||
Register::Pointer(pointer) => self.follow_pointer(*pointer),
|
||||
Register::Empty => Err(VmError::EmptyRegister {
|
||||
index: register_index,
|
||||
position: self.current_position,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn open_register_allow_empty(
|
||||
&self,
|
||||
register_index: u16,
|
||||
) -> Result<Option<ValueRef>, VmError> {
|
||||
let register_index = register_index as usize;
|
||||
let register =
|
||||
self.stack
|
||||
.get(register_index)
|
||||
.ok_or_else(|| VmError::RegisterIndexOutOfBounds {
|
||||
index: register_index,
|
||||
position: self.current_position,
|
||||
})?;
|
||||
|
||||
log::trace!("Open R{register_index} to {register}");
|
||||
|
||||
match register {
|
||||
Register::Value(value) => Ok(Some(value.to_ref())),
|
||||
Register::Pointer(pointer) => self.follow_pointer(*pointer).map(Some),
|
||||
Register::Empty => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// DRY helper for handling JUMP instructions
|
||||
fn jump(&mut self, offset: usize, is_positive: bool) {
|
||||
log::trace!(
|
||||
"Jumping {}",
|
||||
if is_positive {
|
||||
format!("+{}", offset)
|
||||
} else {
|
||||
format!("-{}", offset)
|
||||
}
|
||||
);
|
||||
|
||||
let new_ip = if is_positive {
|
||||
self.ip + offset
|
||||
} else {
|
||||
self.ip - offset - 1
|
||||
};
|
||||
self.ip = new_ip;
|
||||
}
|
||||
|
||||
/// DRY helper to get a register index from a Destination
|
||||
fn get_destination(&self, destination: Destination) -> Result<u16, VmError> {
|
||||
let index = match destination {
|
||||
Destination::Register(register_index) => register_index,
|
||||
Destination::Local(local_index) => self
|
||||
.local_definitions
|
||||
.get(local_index as usize)
|
||||
.copied()
|
||||
.flatten()
|
||||
.ok_or_else(|| VmError::UndefinedLocal {
|
||||
local_index,
|
||||
position: self.current_position,
|
||||
})?,
|
||||
};
|
||||
|
||||
Ok(index)
|
||||
}
|
||||
|
||||
/// DRY helper to get a value from an Argument
|
||||
fn get_argument(&self, argument: Argument) -> Result<ValueRef, VmError> {
|
||||
let value_ref = match argument {
|
||||
Argument::Constant(constant_index) => {
|
||||
ValueRef::Concrete(self.get_constant(constant_index)?)
|
||||
}
|
||||
Argument::Register(register_index) => self.open_register(register_index)?,
|
||||
Argument::Local(local_index) => self.get_local(local_index)?,
|
||||
};
|
||||
|
||||
Ok(value_ref)
|
||||
}
|
||||
|
||||
fn set_register(&mut self, to_register: u16, register: Register) -> Result<(), VmError> {
|
||||
self.last_assigned_register = Some(to_register);
|
||||
|
||||
let length = self.stack.len();
|
||||
let to_register = to_register as usize;
|
||||
|
||||
if length == Self::STACK_LIMIT {
|
||||
return Err(VmError::StackOverflow {
|
||||
position: self.current_position,
|
||||
});
|
||||
}
|
||||
|
||||
match to_register.cmp(&length) {
|
||||
Ordering::Less => {
|
||||
log::trace!("Change R{to_register} to {register}");
|
||||
|
||||
self.stack[to_register] = register;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Ordering::Equal => {
|
||||
log::trace!("Set R{to_register} to {register}");
|
||||
|
||||
self.stack.push(register);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Ordering::Greater => {
|
||||
let difference = to_register - length;
|
||||
|
||||
for index in 0..difference {
|
||||
log::trace!("Set R{index} to {register}");
|
||||
|
||||
self.stack.push(Register::Empty);
|
||||
}
|
||||
|
||||
log::trace!("Set R{to_register} to {register}");
|
||||
|
||||
self.stack.push(register);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_constant(&self, constant_index: u16) -> Result<&ConcreteValue, VmError> {
|
||||
self.chunk
|
||||
.constants()
|
||||
.get(constant_index as usize)
|
||||
.ok_or_else(|| VmError::ConstantIndexOutOfBounds {
|
||||
index: constant_index as usize,
|
||||
position: self.current_position,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_local(&self, local_index: u16) -> Result<ValueRef, VmError> {
|
||||
let register_index = self
|
||||
.local_definitions
|
||||
.get(local_index as usize)
|
||||
.ok_or_else(|| VmError::UndefinedLocal {
|
||||
local_index,
|
||||
position: self.current_position,
|
||||
})?
|
||||
.ok_or_else(|| VmError::UndefinedLocal {
|
||||
local_index,
|
||||
position: self.current_position,
|
||||
})?;
|
||||
|
||||
self.open_register(register_index)
|
||||
}
|
||||
|
||||
fn read(&mut self) -> Result<Instruction, VmError> {
|
||||
let (instruction, _type, position) =
|
||||
self.chunk.instructions().get(self.ip).ok_or_else(|| {
|
||||
VmError::InstructionIndexOutOfBounds {
|
||||
index: self.ip,
|
||||
position: self.current_position,
|
||||
}
|
||||
})?;
|
||||
|
||||
self.ip += 1;
|
||||
self.current_position = *position;
|
||||
|
||||
Ok(*instruction)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Register {
|
||||
Empty,
|
||||
Value(Value),
|
||||
Pointer(Pointer),
|
||||
}
|
||||
|
||||
impl Display for Register {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Empty => write!(f, "empty"),
|
||||
Self::Value(value) => write!(f, "{}", value),
|
||||
Self::Pointer(pointer) => write!(f, "{}", pointer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub enum Pointer {
|
||||
Stack(u16),
|
||||
Constant(u16),
|
||||
ParentStack(u16),
|
||||
ParentConstant(u16),
|
||||
}
|
||||
|
||||
impl Display for Pointer {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Stack(index) => write!(f, "R{}", index),
|
||||
Self::Constant(index) => write!(f, "C{}", index),
|
||||
Self::ParentStack(index) => write!(f, "PR{}", index),
|
||||
Self::ParentConstant(index) => write!(f, "PC{}", index),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum VmError {
|
||||
// Stack errors
|
||||
StackOverflow {
|
||||
position: Span,
|
||||
},
|
||||
StackUnderflow {
|
||||
position: Span,
|
||||
},
|
||||
|
||||
// Register errors
|
||||
EmptyRegister {
|
||||
index: usize,
|
||||
position: Span,
|
||||
},
|
||||
ExpectedConcreteValue {
|
||||
found: AbstractValue,
|
||||
position: Span,
|
||||
},
|
||||
ExpectedValue {
|
||||
found: Register,
|
||||
position: Span,
|
||||
},
|
||||
RegisterIndexOutOfBounds {
|
||||
index: usize,
|
||||
position: Span,
|
||||
},
|
||||
|
||||
// Local errors
|
||||
UndefinedLocal {
|
||||
local_index: u16,
|
||||
position: Span,
|
||||
},
|
||||
|
||||
// Execution errors
|
||||
ExpectedBoolean {
|
||||
found: Value,
|
||||
position: Span,
|
||||
},
|
||||
ExpectedFunction {
|
||||
found: ConcreteValue,
|
||||
position: Span,
|
||||
},
|
||||
ExpectedParent {
|
||||
position: Span,
|
||||
},
|
||||
ValueDisplay {
|
||||
error: io::ErrorKind,
|
||||
position: Span,
|
||||
},
|
||||
|
||||
// Chunk errors
|
||||
ConstantIndexOutOfBounds {
|
||||
index: usize,
|
||||
position: Span,
|
||||
},
|
||||
InstructionIndexOutOfBounds {
|
||||
index: usize,
|
||||
position: Span,
|
||||
},
|
||||
LocalIndexOutOfBounds {
|
||||
index: usize,
|
||||
position: Span,
|
||||
},
|
||||
|
||||
// Wrappers for foreign errors
|
||||
NativeFunction(NativeFunctionError),
|
||||
Value {
|
||||
error: ValueError,
|
||||
position: Span,
|
||||
},
|
||||
}
|
||||
|
||||
impl AnnotatedError for VmError {
|
||||
fn title() -> &'static str {
|
||||
"Runtime Error"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
match self {
|
||||
Self::ConstantIndexOutOfBounds { .. } => "Constant index out of bounds",
|
||||
Self::EmptyRegister { .. } => "Empty register",
|
||||
Self::ExpectedBoolean { .. } => "Expected boolean",
|
||||
Self::ExpectedConcreteValue { .. } => "Expected concrete value",
|
||||
Self::ExpectedFunction { .. } => "Expected function",
|
||||
Self::ExpectedParent { .. } => "Expected parent",
|
||||
Self::ExpectedValue { .. } => "Expected value",
|
||||
Self::InstructionIndexOutOfBounds { .. } => "Instruction index out of bounds",
|
||||
Self::LocalIndexOutOfBounds { .. } => "Local index out of bounds",
|
||||
Self::NativeFunction(error) => error.description(),
|
||||
Self::RegisterIndexOutOfBounds { .. } => "Register index out of bounds",
|
||||
Self::StackOverflow { .. } => "Stack overflow",
|
||||
Self::StackUnderflow { .. } => "Stack underflow",
|
||||
Self::UndefinedLocal { .. } => "Undefined local",
|
||||
Self::Value { .. } => "Value error",
|
||||
Self::ValueDisplay { .. } => "Value display error",
|
||||
}
|
||||
}
|
||||
|
||||
fn details(&self) -> Option<String> {
|
||||
match self {
|
||||
Self::EmptyRegister { index, .. } => Some(format!("Register R{index} is empty")),
|
||||
Self::ExpectedFunction { found, .. } => Some(format!("{found} is not a function")),
|
||||
|
||||
Self::RegisterIndexOutOfBounds { index, .. } => {
|
||||
Some(format!("Register {index} does not exist"))
|
||||
}
|
||||
Self::NativeFunction(error) => error.details(),
|
||||
Self::Value { error, .. } => Some(error.to_string()),
|
||||
Self::ValueDisplay { error, .. } => Some(error.to_string() + " while displaying value"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn position(&self) -> Span {
|
||||
match self {
|
||||
Self::ConstantIndexOutOfBounds { position, .. } => *position,
|
||||
Self::EmptyRegister { position, .. } => *position,
|
||||
Self::ExpectedBoolean { position, .. } => *position,
|
||||
Self::ExpectedConcreteValue { position, .. } => *position,
|
||||
Self::ExpectedFunction { position, .. } => *position,
|
||||
Self::ExpectedParent { position } => *position,
|
||||
Self::ExpectedValue { position, .. } => *position,
|
||||
Self::InstructionIndexOutOfBounds { position, .. } => *position,
|
||||
Self::LocalIndexOutOfBounds { position, .. } => *position,
|
||||
Self::NativeFunction(error) => error.position(),
|
||||
Self::RegisterIndexOutOfBounds { position, .. } => *position,
|
||||
Self::StackOverflow { position } => *position,
|
||||
Self::StackUnderflow { position } => *position,
|
||||
Self::UndefinedLocal { position, .. } => *position,
|
||||
Self::Value { position, .. } => *position,
|
||||
Self::ValueDisplay { position, .. } => *position,
|
||||
}
|
||||
}
|
||||
}
|
39
dust-lang/src/vm/function_call.rs
Normal file
39
dust-lang/src/vm/function_call.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
|
||||
use crate::{Chunk, DustString};
|
||||
|
||||
use super::Register;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FunctionCall<'a> {
|
||||
pub chunk: &'a Chunk,
|
||||
pub ip: usize,
|
||||
pub return_register: u8,
|
||||
pub registers: Vec<Register>,
|
||||
}
|
||||
|
||||
impl<'a> FunctionCall<'a> {
|
||||
pub fn new(chunk: &'a Chunk, return_register: u8) -> Self {
|
||||
Self {
|
||||
chunk,
|
||||
ip: 0,
|
||||
return_register,
|
||||
registers: vec![Register::Empty; chunk.register_count],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FunctionCall<'_> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"FunctionCall: {} | IP: {} | Registers: {}",
|
||||
self.chunk
|
||||
.name
|
||||
.as_ref()
|
||||
.unwrap_or(&DustString::from("anonymous")),
|
||||
self.ip,
|
||||
self.registers.len()
|
||||
)
|
||||
}
|
||||
}
|
103
dust-lang/src/vm/mod.rs
Normal file
103
dust-lang/src/vm/mod.rs
Normal file
@ -0,0 +1,103 @@
|
||||
//! Virtual machine and errors
|
||||
mod function_call;
|
||||
mod run_action;
|
||||
mod stack;
|
||||
mod thread;
|
||||
|
||||
use std::{
|
||||
fmt::{self, Debug, Display, Formatter},
|
||||
sync::mpsc,
|
||||
thread::spawn,
|
||||
};
|
||||
|
||||
pub use function_call::FunctionCall;
|
||||
pub(crate) use run_action::get_next_action;
|
||||
pub use run_action::RunAction;
|
||||
pub use stack::Stack;
|
||||
pub use thread::{Thread, ThreadData};
|
||||
|
||||
use tracing::{span, Level};
|
||||
|
||||
use crate::{compile, Chunk, DustError, Value};
|
||||
|
||||
pub fn run(source: &str) -> Result<Option<Value>, DustError> {
|
||||
let chunk = compile(source)?;
|
||||
let vm = Vm::new(chunk);
|
||||
|
||||
Ok(vm.run())
|
||||
}
|
||||
|
||||
pub struct Vm {
|
||||
threads: Vec<Thread>,
|
||||
}
|
||||
|
||||
impl Vm {
|
||||
pub fn new(chunk: Chunk) -> Self {
|
||||
let threads = vec![Thread::new(chunk)];
|
||||
|
||||
debug_assert_eq!(1, threads.capacity());
|
||||
|
||||
Self { threads }
|
||||
}
|
||||
|
||||
pub fn run(mut self) -> Option<Value> {
|
||||
let span = span!(Level::INFO, "Run");
|
||||
let _enter = span.enter();
|
||||
|
||||
if self.threads.len() == 1 {
|
||||
return self.threads[0].run();
|
||||
}
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
for mut thread in self.threads {
|
||||
let tx = tx.clone();
|
||||
|
||||
spawn(move || {
|
||||
let return_value = thread.run();
|
||||
|
||||
if let Some(value) = return_value {
|
||||
tx.send(value).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
rx.into_iter().last()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Register {
|
||||
Empty,
|
||||
Value(Value),
|
||||
Pointer(Pointer),
|
||||
}
|
||||
|
||||
impl Display for Register {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Empty => write!(f, "empty"),
|
||||
Self::Value(value) => write!(f, "{}", value),
|
||||
Self::Pointer(pointer) => write!(f, "{}", pointer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub enum Pointer {
|
||||
Register(u8),
|
||||
Constant(u8),
|
||||
Stack(usize, u8),
|
||||
}
|
||||
|
||||
impl Display for Pointer {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Register(index) => write!(f, "PR{}", index),
|
||||
Self::Constant(index) => write!(f, "PC{}", index),
|
||||
Self::Stack(call_index, register_index) => {
|
||||
write!(f, "PS{}R{}", call_index, register_index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
645
dust-lang/src/vm/run_action.rs
Normal file
645
dust-lang/src/vm/run_action.rs
Normal file
@ -0,0 +1,645 @@
|
||||
use tracing::trace;
|
||||
|
||||
use crate::{
|
||||
instruction::{
|
||||
Add, Call, CallNative, Close, Divide, Equal, GetLocal, Jump, Less, LessEqual, LoadBoolean,
|
||||
LoadConstant, LoadFunction, LoadList, LoadSelf, Modulo, Multiply, Negate, Not, Point,
|
||||
Return, SetLocal, Subtract, Test, TestSet,
|
||||
},
|
||||
vm::FunctionCall,
|
||||
AbstractList, Argument, ConcreteValue, Instruction, Type, Value,
|
||||
};
|
||||
|
||||
use super::{thread::ThreadData, Pointer, Register};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct RunAction {
|
||||
pub logic: RunnerLogic,
|
||||
pub instruction: Instruction,
|
||||
}
|
||||
|
||||
impl From<Instruction> for RunAction {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let operation = instruction.operation();
|
||||
let logic = RUNNER_LOGIC_TABLE[operation.0 as usize];
|
||||
|
||||
RunAction { logic, instruction }
|
||||
}
|
||||
}
|
||||
|
||||
pub type RunnerLogic = fn(Instruction, &mut ThreadData) -> bool;
|
||||
|
||||
pub const RUNNER_LOGIC_TABLE: [RunnerLogic; 25] = [
|
||||
point,
|
||||
close,
|
||||
load_boolean,
|
||||
load_constant,
|
||||
load_function,
|
||||
load_list,
|
||||
load_self,
|
||||
get_local,
|
||||
set_local,
|
||||
add,
|
||||
subtract,
|
||||
multiply,
|
||||
divide,
|
||||
modulo,
|
||||
test,
|
||||
test_set,
|
||||
equal,
|
||||
less,
|
||||
less_equal,
|
||||
negate,
|
||||
not,
|
||||
call,
|
||||
call_native,
|
||||
jump,
|
||||
r#return,
|
||||
];
|
||||
|
||||
pub(crate) fn get_next_action(data: &mut ThreadData) -> RunAction {
|
||||
let current_call = data.call_stack.last_mut_unchecked();
|
||||
let instruction = current_call.chunk.instructions[current_call.ip];
|
||||
let operation = instruction.operation();
|
||||
let logic = RUNNER_LOGIC_TABLE[operation.0 as usize];
|
||||
|
||||
current_call.ip += 1;
|
||||
|
||||
RunAction { logic, instruction }
|
||||
}
|
||||
|
||||
pub fn point(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let Point { from, to } = instruction.into();
|
||||
let from_register = data.get_register_unchecked(from);
|
||||
let from_register_is_empty = matches!(from_register, Register::Empty);
|
||||
|
||||
if !from_register_is_empty {
|
||||
let register = Register::Pointer(Pointer::Register(to));
|
||||
|
||||
data.set_register(from, register);
|
||||
}
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn close(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let Close { from, to } = instruction.into();
|
||||
|
||||
for register_index in from..to {
|
||||
data.set_register(register_index, Register::Empty);
|
||||
}
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn load_boolean(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let LoadBoolean {
|
||||
destination,
|
||||
value,
|
||||
jump_next,
|
||||
} = instruction.into();
|
||||
let boolean = Value::Concrete(ConcreteValue::Boolean(value));
|
||||
let register = Register::Value(boolean);
|
||||
|
||||
data.set_register(destination, register);
|
||||
|
||||
if jump_next {
|
||||
let current_call = data.call_stack.last_mut_unchecked();
|
||||
|
||||
current_call.ip += 1;
|
||||
}
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn load_constant(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let LoadConstant {
|
||||
destination,
|
||||
constant_index,
|
||||
jump_next,
|
||||
} = instruction.into();
|
||||
let register = Register::Pointer(Pointer::Constant(constant_index));
|
||||
|
||||
trace!("Load constant {constant_index} into R{destination}");
|
||||
|
||||
data.set_register(destination, register);
|
||||
|
||||
if jump_next {
|
||||
let current_call = data.call_stack.last_mut_unchecked();
|
||||
|
||||
current_call.ip += 1;
|
||||
}
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn load_list(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let LoadList {
|
||||
destination,
|
||||
start_register,
|
||||
} = instruction.into();
|
||||
let mut item_pointers = Vec::with_capacity((destination - start_register) as usize);
|
||||
let mut item_type = Type::Any;
|
||||
|
||||
for register_index in start_register..destination {
|
||||
match data.get_register_unchecked(register_index) {
|
||||
Register::Empty => continue,
|
||||
Register::Value(value) => {
|
||||
if item_type == Type::Any {
|
||||
item_type = value.r#type();
|
||||
}
|
||||
}
|
||||
Register::Pointer(pointer) => {
|
||||
if item_type == Type::Any {
|
||||
item_type = data.follow_pointer_unchecked(*pointer).r#type();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let pointer = Pointer::Register(register_index);
|
||||
|
||||
item_pointers.push(pointer);
|
||||
}
|
||||
|
||||
let list_value = Value::AbstractList(AbstractList {
|
||||
item_type,
|
||||
item_pointers,
|
||||
});
|
||||
let register = Register::Value(list_value);
|
||||
|
||||
data.set_register(destination, register);
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn load_function(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let LoadFunction {
|
||||
destination,
|
||||
prototype_index,
|
||||
} = instruction.into();
|
||||
let prototype_index = prototype_index as usize;
|
||||
let current_call = data.call_stack.last_mut_unchecked();
|
||||
let prototype = ¤t_call.chunk.prototypes[prototype_index];
|
||||
let function = prototype.as_function();
|
||||
let register = Register::Value(Value::Function(function));
|
||||
|
||||
data.set_register(destination, register);
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn load_self(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let LoadSelf { destination } = instruction.into();
|
||||
let current_call = data.call_stack.last_mut_unchecked();
|
||||
let prototype = ¤t_call.chunk;
|
||||
let function = prototype.as_function();
|
||||
let register = Register::Value(Value::Function(function));
|
||||
|
||||
data.set_register(destination, register);
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn get_local(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let GetLocal {
|
||||
destination,
|
||||
local_index,
|
||||
} = instruction.into();
|
||||
let local_register_index = data.get_local_register(local_index);
|
||||
let register = Register::Pointer(Pointer::Register(local_register_index));
|
||||
|
||||
data.set_register(destination, register);
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn set_local(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let SetLocal {
|
||||
register_index,
|
||||
local_index,
|
||||
} = instruction.into();
|
||||
let local_register_index = data.get_local_register(local_index);
|
||||
let register = Register::Pointer(Pointer::Register(register_index));
|
||||
|
||||
data.set_register(local_register_index, register);
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn add(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let Add {
|
||||
destination,
|
||||
left,
|
||||
right,
|
||||
} = instruction.into();
|
||||
let left = data.get_argument_unchecked(left);
|
||||
let right = data.get_argument_unchecked(right);
|
||||
let sum = left.add(right);
|
||||
let register = Register::Value(sum);
|
||||
|
||||
data.set_register(destination, register);
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn subtract(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let Subtract {
|
||||
destination,
|
||||
left,
|
||||
right,
|
||||
} = instruction.into();
|
||||
let left = data.get_argument_unchecked(left);
|
||||
let right = data.get_argument_unchecked(right);
|
||||
let difference = left.subtract(right);
|
||||
let register = Register::Value(difference);
|
||||
|
||||
data.set_register(destination, register);
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn multiply(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let Multiply {
|
||||
destination,
|
||||
left,
|
||||
right,
|
||||
} = instruction.into();
|
||||
let left = data.get_argument_unchecked(left);
|
||||
let right = data.get_argument_unchecked(right);
|
||||
let product = match (left, right) {
|
||||
(Value::Concrete(left), Value::Concrete(right)) => match (left, right) {
|
||||
(ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => {
|
||||
ConcreteValue::Integer(left * right).to_value()
|
||||
}
|
||||
_ => panic!("Value Error: Cannot multiply values"),
|
||||
},
|
||||
_ => panic!("Value Error: Cannot multiply values"),
|
||||
};
|
||||
let register = Register::Value(product);
|
||||
|
||||
data.set_register(destination, register);
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn divide(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let Divide {
|
||||
destination,
|
||||
left,
|
||||
right,
|
||||
} = instruction.into();
|
||||
let left = data.get_argument_unchecked(left);
|
||||
let right = data.get_argument_unchecked(right);
|
||||
let quotient = match (left, right) {
|
||||
(Value::Concrete(left), Value::Concrete(right)) => match (left, right) {
|
||||
(ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => {
|
||||
ConcreteValue::Integer(left / right).to_value()
|
||||
}
|
||||
_ => panic!("Value Error: Cannot divide values"),
|
||||
},
|
||||
_ => panic!("Value Error: Cannot divide values"),
|
||||
};
|
||||
let register = Register::Value(quotient);
|
||||
|
||||
data.set_register(destination, register);
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn modulo(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let Modulo {
|
||||
destination,
|
||||
left,
|
||||
right,
|
||||
} = instruction.into();
|
||||
let left = data.get_argument_unchecked(left);
|
||||
let right = data.get_argument_unchecked(right);
|
||||
let remainder = match (left, right) {
|
||||
(Value::Concrete(left), Value::Concrete(right)) => match (left, right) {
|
||||
(ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => {
|
||||
ConcreteValue::Integer(left % right).to_value()
|
||||
}
|
||||
_ => panic!("Value Error: Cannot modulo values"),
|
||||
},
|
||||
_ => panic!("Value Error: Cannot modulo values"),
|
||||
};
|
||||
let register = Register::Value(remainder);
|
||||
|
||||
data.set_register(destination, register);
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn test(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let Test {
|
||||
operand_register,
|
||||
test_value,
|
||||
} = instruction.into();
|
||||
let value = data.open_register_unchecked(operand_register);
|
||||
let boolean = if let Value::Concrete(ConcreteValue::Boolean(boolean)) = value {
|
||||
*boolean
|
||||
} else {
|
||||
panic!("VM Error: Expected boolean value for TEST operation",);
|
||||
};
|
||||
|
||||
if boolean == test_value {
|
||||
let current_call = data.call_stack.last_mut_unchecked();
|
||||
|
||||
current_call.ip += 1;
|
||||
}
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn test_set(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let TestSet {
|
||||
destination,
|
||||
argument,
|
||||
test_value,
|
||||
} = instruction.into();
|
||||
let value = data.get_argument_unchecked(argument);
|
||||
let boolean = if let Value::Concrete(ConcreteValue::Boolean(boolean)) = value {
|
||||
*boolean
|
||||
} else {
|
||||
panic!("VM Error: Expected boolean value for TEST_SET operation",);
|
||||
};
|
||||
|
||||
if boolean == test_value {
|
||||
} else {
|
||||
let pointer = match argument {
|
||||
Argument::Constant(constant_index) => Pointer::Constant(constant_index),
|
||||
Argument::Register(register_index) => Pointer::Register(register_index),
|
||||
};
|
||||
let register = Register::Pointer(pointer);
|
||||
|
||||
data.set_register(destination, register);
|
||||
}
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn equal(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let Equal { value, left, right } = instruction.into();
|
||||
let left = data.get_argument_unchecked(left);
|
||||
let right = data.get_argument_unchecked(right);
|
||||
let is_equal = left.equals(right);
|
||||
|
||||
if is_equal == value {
|
||||
let current_call = data.call_stack.last_mut_unchecked();
|
||||
|
||||
current_call.ip += 1;
|
||||
}
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn less(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let Less { value, left, right } = instruction.into();
|
||||
let left = data.get_argument_unchecked(left);
|
||||
let right = data.get_argument_unchecked(right);
|
||||
let is_less = left < right;
|
||||
|
||||
if is_less == value {
|
||||
let current_call = data.call_stack.last_mut_unchecked();
|
||||
|
||||
current_call.ip += 1;
|
||||
}
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn less_equal(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let LessEqual { value, left, right } = instruction.into();
|
||||
let left = data.get_argument_unchecked(left);
|
||||
let right = data.get_argument_unchecked(right);
|
||||
let is_less_or_equal = left <= right;
|
||||
|
||||
if is_less_or_equal == value {
|
||||
let current_call = data.call_stack.last_mut_unchecked();
|
||||
|
||||
current_call.ip += 1;
|
||||
}
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn negate(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let Negate {
|
||||
destination,
|
||||
argument,
|
||||
} = instruction.into();
|
||||
let argument = data.get_argument_unchecked(argument);
|
||||
let negated = argument.negate();
|
||||
let register = Register::Value(negated);
|
||||
|
||||
data.set_register(destination, register);
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn not(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let Not {
|
||||
destination,
|
||||
argument,
|
||||
} = instruction.into();
|
||||
let argument = data.get_argument_unchecked(argument);
|
||||
let not = match argument {
|
||||
Value::Concrete(ConcreteValue::Boolean(boolean)) => ConcreteValue::Boolean(!boolean),
|
||||
_ => panic!("VM Error: Expected boolean value for NOT operation"),
|
||||
};
|
||||
let register = Register::Value(Value::Concrete(not));
|
||||
|
||||
data.set_register(destination, register);
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn jump(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let Jump {
|
||||
offset,
|
||||
is_positive,
|
||||
} = instruction.into();
|
||||
let offset = offset as usize;
|
||||
let current_call = data.call_stack.last_mut_unchecked();
|
||||
|
||||
if is_positive {
|
||||
current_call.ip += offset;
|
||||
} else {
|
||||
current_call.ip -= offset + 1;
|
||||
}
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn call(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let Call {
|
||||
destination: return_register,
|
||||
function_register,
|
||||
argument_count,
|
||||
is_recursive,
|
||||
} = instruction.into();
|
||||
let current_call = data.call_stack.last_unchecked();
|
||||
let first_argument_register = return_register - argument_count;
|
||||
let prototype = if is_recursive {
|
||||
current_call.chunk
|
||||
} else {
|
||||
let function = data
|
||||
.open_register_unchecked(function_register)
|
||||
.as_function()
|
||||
.unwrap();
|
||||
|
||||
¤t_call.chunk.prototypes[function.prototype_index as usize]
|
||||
};
|
||||
let mut next_call = FunctionCall::new(prototype, return_register);
|
||||
let mut argument_index = 0;
|
||||
|
||||
for register_index in first_argument_register..return_register {
|
||||
let value_option = data.open_register_allow_empty_unchecked(register_index);
|
||||
let argument = if let Some(value) = value_option {
|
||||
value.clone()
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
next_call.registers[argument_index] = Register::Value(argument);
|
||||
argument_index += 1;
|
||||
}
|
||||
|
||||
data.call_stack.push(next_call);
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn call_native(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
let CallNative {
|
||||
destination,
|
||||
function,
|
||||
argument_count,
|
||||
} = instruction.into();
|
||||
let first_argument_index = destination - argument_count;
|
||||
let argument_range = first_argument_index..destination;
|
||||
|
||||
function.call(data, Some(destination), argument_range)
|
||||
}
|
||||
|
||||
pub fn r#return(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
trace!("Returning with call stack:\n{}", data.call_stack);
|
||||
|
||||
let Return {
|
||||
should_return_value,
|
||||
return_register,
|
||||
} = instruction.into();
|
||||
let (destination, return_value) = if data.call_stack.len() == 1 {
|
||||
if should_return_value {
|
||||
data.return_value_index = Some(return_register);
|
||||
};
|
||||
|
||||
return true;
|
||||
} else {
|
||||
let return_value = data.empty_register_or_clone_constant_unchecked(return_register);
|
||||
let destination = data.call_stack.pop_unchecked().return_register;
|
||||
|
||||
(destination, return_value)
|
||||
};
|
||||
|
||||
if should_return_value {
|
||||
data.set_register(destination, Register::Value(return_value));
|
||||
}
|
||||
|
||||
data.next_action = get_next_action(data);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::Operation;
|
||||
|
||||
use super::*;
|
||||
|
||||
const ALL_OPERATIONS: [(Operation, RunnerLogic); 24] = [
|
||||
(Operation::POINT, point),
|
||||
(Operation::CLOSE, close),
|
||||
(Operation::LOAD_BOOLEAN, load_boolean),
|
||||
(Operation::LOAD_CONSTANT, load_constant),
|
||||
(Operation::LOAD_LIST, load_list),
|
||||
(Operation::LOAD_SELF, load_self),
|
||||
(Operation::GET_LOCAL, get_local),
|
||||
(Operation::SET_LOCAL, set_local),
|
||||
(Operation::ADD, add),
|
||||
(Operation::SUBTRACT, subtract),
|
||||
(Operation::MULTIPLY, multiply),
|
||||
(Operation::DIVIDE, divide),
|
||||
(Operation::MODULO, modulo),
|
||||
(Operation::TEST, test),
|
||||
(Operation::TEST_SET, test_set),
|
||||
(Operation::EQUAL, equal),
|
||||
(Operation::LESS, less),
|
||||
(Operation::LESS_EQUAL, less_equal),
|
||||
(Operation::NEGATE, negate),
|
||||
(Operation::NOT, not),
|
||||
(Operation::CALL, call),
|
||||
(Operation::CALL_NATIVE, call_native),
|
||||
(Operation::JUMP, jump),
|
||||
(Operation::RETURN, r#return),
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn operations_map_to_the_correct_runner() {
|
||||
for (operation, expected_runner) in ALL_OPERATIONS {
|
||||
let actual_runner = RUNNER_LOGIC_TABLE[operation.0 as usize];
|
||||
|
||||
assert_eq!(
|
||||
expected_runner, actual_runner,
|
||||
"{operation} runner is incorrect"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
137
dust-lang/src/vm/stack.rs
Normal file
137
dust-lang/src/vm/stack.rs
Normal file
@ -0,0 +1,137 @@
|
||||
use std::{
|
||||
fmt::{self, Debug, Display, Formatter},
|
||||
ops::{Index, IndexMut, Range},
|
||||
};
|
||||
|
||||
use super::FunctionCall;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Stack<T> {
|
||||
items: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> Stack<T> {
|
||||
pub fn new() -> Self {
|
||||
Stack {
|
||||
items: Vec::with_capacity(1),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Stack {
|
||||
items: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.items.is_empty()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.items.len()
|
||||
}
|
||||
|
||||
pub fn get_unchecked(&self, index: usize) -> &T {
|
||||
if cfg!(debug_assertions) {
|
||||
assert!(index < self.len(), "Stack underflow");
|
||||
|
||||
&self.items[index]
|
||||
} else {
|
||||
unsafe { self.items.get_unchecked(index) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_unchecked_mut(&mut self, index: usize) -> &mut T {
|
||||
if cfg!(debug_assertions) {
|
||||
assert!(index < self.len(), "Stack underflow");
|
||||
|
||||
&mut self.items[index]
|
||||
} else {
|
||||
unsafe { self.items.get_unchecked_mut(index) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, item: T) {
|
||||
self.items.push(item);
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> Option<T> {
|
||||
self.items.pop()
|
||||
}
|
||||
|
||||
pub fn last(&self) -> Option<&T> {
|
||||
self.items.last()
|
||||
}
|
||||
|
||||
pub fn last_mut(&mut self) -> Option<&mut T> {
|
||||
self.items.last_mut()
|
||||
}
|
||||
|
||||
pub fn pop_unchecked(&mut self) -> T {
|
||||
if cfg!(debug_assertions) {
|
||||
assert!(!self.is_empty(), "Stack underflow");
|
||||
|
||||
self.items.pop().unwrap()
|
||||
} else {
|
||||
unsafe { self.items.pop().unwrap_unchecked() }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn last_unchecked(&self) -> &T {
|
||||
if cfg!(debug_assertions) {
|
||||
assert!(!self.is_empty(), "Stack underflow");
|
||||
|
||||
self.items.last().unwrap()
|
||||
} else {
|
||||
unsafe { self.items.last().unwrap_unchecked() }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn last_mut_unchecked(&mut self) -> &mut T {
|
||||
if cfg!(debug_assertions) {
|
||||
assert!(!self.is_empty(), "Stack underflow");
|
||||
|
||||
self.items.last_mut().unwrap()
|
||||
} else {
|
||||
unsafe { self.items.last_mut().unwrap_unchecked() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Stack<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Index<Range<usize>> for Stack<T> {
|
||||
type Output = [T];
|
||||
|
||||
fn index(&self, index: Range<usize>) -> &Self::Output {
|
||||
&self.items[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IndexMut<Range<usize>> for Stack<T> {
|
||||
fn index_mut(&mut self, index: Range<usize>) -> &mut Self::Output {
|
||||
&mut self.items[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for Stack<T> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self.items)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Stack<FunctionCall<'_>> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
writeln!(f, "----- DUST CALL STACK -----")?;
|
||||
|
||||
for (index, function_call) in self.items.iter().enumerate().rev() {
|
||||
writeln!(f, "{index:02} | {function_call}")?;
|
||||
}
|
||||
|
||||
write!(f, "---------------------------")
|
||||
}
|
||||
}
|
272
dust-lang/src/vm/thread.rs
Normal file
272
dust-lang/src/vm/thread.rs
Normal file
@ -0,0 +1,272 @@
|
||||
use std::mem::replace;
|
||||
|
||||
use tracing::{info, trace};
|
||||
|
||||
use crate::{vm::FunctionCall, Argument, Chunk, DustString, Span, Value};
|
||||
|
||||
use super::{Pointer, Register, RunAction, Stack};
|
||||
|
||||
pub struct Thread {
|
||||
chunk: Chunk,
|
||||
}
|
||||
|
||||
impl Thread {
|
||||
pub fn new(chunk: Chunk) -> Self {
|
||||
Thread { chunk }
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Option<Value> {
|
||||
info!(
|
||||
"Starting thread with {}",
|
||||
self.chunk
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or_else(|| DustString::from("anonymous"))
|
||||
);
|
||||
|
||||
let mut call_stack = Stack::with_capacity(self.chunk.prototypes.len() + 1);
|
||||
let main_call = FunctionCall::new(&self.chunk, 0);
|
||||
|
||||
call_stack.push(main_call);
|
||||
|
||||
let first_action = RunAction::from(*self.chunk.instructions.first().unwrap());
|
||||
let mut thread_data = ThreadData {
|
||||
call_stack,
|
||||
next_action: first_action,
|
||||
return_value_index: None,
|
||||
};
|
||||
|
||||
loop {
|
||||
trace!("Instruction: {}", thread_data.next_action.instruction);
|
||||
|
||||
let should_end = (thread_data.next_action.logic)(
|
||||
thread_data.next_action.instruction,
|
||||
&mut thread_data,
|
||||
);
|
||||
|
||||
if should_end {
|
||||
let return_value = if let Some(register_index) = thread_data.return_value_index {
|
||||
let value =
|
||||
thread_data.empty_register_or_clone_constant_unchecked(register_index);
|
||||
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
return return_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ThreadData<'a> {
|
||||
pub call_stack: Stack<FunctionCall<'a>>,
|
||||
pub next_action: RunAction,
|
||||
pub return_value_index: Option<u8>,
|
||||
}
|
||||
|
||||
impl ThreadData<'_> {
|
||||
pub fn current_position(&self) -> Span {
|
||||
let current_call = self.call_stack.last_unchecked();
|
||||
|
||||
current_call.chunk.positions[current_call.ip]
|
||||
}
|
||||
|
||||
pub(crate) fn follow_pointer_unchecked(&self, pointer: Pointer) -> &Value {
|
||||
trace!("Follow pointer {pointer}");
|
||||
|
||||
match pointer {
|
||||
Pointer::Register(register_index) => self.open_register_unchecked(register_index),
|
||||
Pointer::Constant(constant_index) => self.get_constant_unchecked(constant_index),
|
||||
Pointer::Stack(stack_index, register_index) => unsafe {
|
||||
let register = self
|
||||
.call_stack
|
||||
.get_unchecked(stack_index)
|
||||
.registers
|
||||
.get_unchecked(register_index as usize);
|
||||
|
||||
match register {
|
||||
Register::Value(value) => value,
|
||||
Register::Pointer(pointer) => self.follow_pointer_unchecked(*pointer),
|
||||
Register::Empty => panic!("VM Error: Register {register_index} is empty"),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_register_unchecked(&self, register_index: u8) -> &Register {
|
||||
trace!("Get register R{register_index}");
|
||||
|
||||
let register_index = register_index as usize;
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
&self.call_stack.last_unchecked().registers[register_index]
|
||||
} else {
|
||||
unsafe {
|
||||
self.call_stack
|
||||
.last_unchecked()
|
||||
.registers
|
||||
.get_unchecked(register_index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_register(&mut self, to_register: u8, register: Register) {
|
||||
let to_register = to_register as usize;
|
||||
|
||||
self.call_stack.last_mut_unchecked().registers[to_register] = register;
|
||||
}
|
||||
|
||||
pub fn open_register_unchecked(&self, register_index: u8) -> &Value {
|
||||
let register_index = register_index as usize;
|
||||
|
||||
let register = if cfg!(debug_assertions) {
|
||||
&self.call_stack.last_unchecked().registers[register_index]
|
||||
} else {
|
||||
unsafe {
|
||||
self.call_stack
|
||||
.last_unchecked()
|
||||
.registers
|
||||
.get_unchecked(register_index)
|
||||
}
|
||||
};
|
||||
|
||||
match register {
|
||||
Register::Value(value) => {
|
||||
trace!("Register R{register_index} opened to value {value}");
|
||||
|
||||
value
|
||||
}
|
||||
Register::Pointer(pointer) => {
|
||||
trace!("Open register R{register_index} opened to pointer {pointer}");
|
||||
|
||||
self.follow_pointer_unchecked(*pointer)
|
||||
}
|
||||
Register::Empty => panic!("VM Error: Register {register_index} is empty"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_register_allow_empty_unchecked(&self, register_index: u8) -> Option<&Value> {
|
||||
trace!("Open register R{register_index}");
|
||||
|
||||
let register = self.get_register_unchecked(register_index);
|
||||
|
||||
match register {
|
||||
Register::Value(value) => {
|
||||
trace!("Register R{register_index} openned to value {value}");
|
||||
|
||||
Some(value)
|
||||
}
|
||||
Register::Pointer(pointer) => {
|
||||
trace!("Open register R{register_index} openned to pointer {pointer}");
|
||||
|
||||
Some(self.follow_pointer_unchecked(*pointer))
|
||||
}
|
||||
Register::Empty => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty_register_or_clone_constant_unchecked(&mut self, register_index: u8) -> Value {
|
||||
let register_index = register_index as usize;
|
||||
let old_register = replace(
|
||||
&mut self.call_stack.last_mut_unchecked().registers[register_index],
|
||||
Register::Empty,
|
||||
);
|
||||
|
||||
match old_register {
|
||||
Register::Value(value) => value,
|
||||
Register::Pointer(pointer) => match pointer {
|
||||
Pointer::Register(register_index) => {
|
||||
self.empty_register_or_clone_constant_unchecked(register_index)
|
||||
}
|
||||
Pointer::Constant(constant_index) => {
|
||||
self.get_constant_unchecked(constant_index).clone()
|
||||
}
|
||||
Pointer::Stack(stack_index, register_index) => {
|
||||
let call = self.call_stack.get_unchecked_mut(stack_index);
|
||||
|
||||
let old_register = replace(
|
||||
&mut call.registers[register_index as usize],
|
||||
Register::Empty,
|
||||
);
|
||||
|
||||
match old_register {
|
||||
Register::Value(value) => value,
|
||||
Register::Pointer(pointer) => {
|
||||
self.follow_pointer_unchecked(pointer).clone()
|
||||
}
|
||||
Register::Empty => panic!("VM Error: Register {register_index} is empty"),
|
||||
}
|
||||
}
|
||||
},
|
||||
Register::Empty => panic!("VM Error: Register {register_index} is empty"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clone_register_value_or_constant_unchecked(&self, register_index: u8) -> Value {
|
||||
let register = self.get_register_unchecked(register_index);
|
||||
|
||||
match register {
|
||||
Register::Value(value) => value.clone(),
|
||||
Register::Pointer(pointer) => match pointer {
|
||||
Pointer::Register(register_index) => {
|
||||
self.open_register_unchecked(*register_index).clone()
|
||||
}
|
||||
Pointer::Constant(constant_index) => {
|
||||
self.get_constant_unchecked(*constant_index).clone()
|
||||
}
|
||||
Pointer::Stack(stack_index, register_index) => {
|
||||
let call = self.call_stack.get_unchecked(*stack_index);
|
||||
let register = &call.registers[*register_index as usize];
|
||||
|
||||
match register {
|
||||
Register::Value(value) => value.clone(),
|
||||
Register::Pointer(pointer) => {
|
||||
self.follow_pointer_unchecked(*pointer).clone()
|
||||
}
|
||||
Register::Empty => panic!("VM Error: Register {register_index} is empty"),
|
||||
}
|
||||
}
|
||||
},
|
||||
Register::Empty => panic!("VM Error: Register {register_index} is empty"),
|
||||
}
|
||||
}
|
||||
|
||||
/// DRY helper to get a value from an Argument
|
||||
pub fn get_argument_unchecked(&self, argument: Argument) -> &Value {
|
||||
match argument {
|
||||
Argument::Constant(constant_index) => self.get_constant_unchecked(constant_index),
|
||||
Argument::Register(register_index) => self.open_register_unchecked(register_index),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_constant_unchecked(&self, constant_index: u8) -> &Value {
|
||||
let constant_index = constant_index as usize;
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
&self.call_stack.last().unwrap().chunk.constants[constant_index]
|
||||
} else {
|
||||
unsafe {
|
||||
self.call_stack
|
||||
.last_unchecked()
|
||||
.chunk
|
||||
.constants
|
||||
.get_unchecked(constant_index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_local_register(&self, local_index: u8) -> u8 {
|
||||
let local_index = local_index as usize;
|
||||
let chunk = self.call_stack.last_unchecked().chunk;
|
||||
|
||||
assert!(
|
||||
local_index < chunk.locals.len(),
|
||||
"VM Error: Local index out of bounds"
|
||||
);
|
||||
|
||||
chunk.locals[local_index].register_index
|
||||
}
|
||||
}
|
81
dust-lang/tests/assignment_errors.rs
Normal file
81
dust-lang/tests/assignment_errors.rs
Normal file
@ -0,0 +1,81 @@
|
||||
use dust_lang::*;
|
||||
|
||||
#[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 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 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_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 modulo_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
|
||||
})
|
||||
);
|
||||
}
|
@ -11,15 +11,11 @@ fn constant() {
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::Integer)
|
||||
return_type: Type::Integer
|
||||
},
|
||||
vec![
|
||||
(
|
||||
Instruction::load_constant(Destination::Register(0), 0, false),
|
||||
Type::Integer,
|
||||
Span(0, 2)
|
||||
),
|
||||
(Instruction::r#return(true), Type::None, Span(2, 2))
|
||||
(Instruction::load_constant(0, 0, false), Span(0, 2)),
|
||||
(Instruction::r#return(true), Span(2, 2))
|
||||
],
|
||||
vec![ConcreteValue::Integer(42)],
|
||||
vec![]
|
||||
@ -40,9 +36,9 @@ fn empty() {
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::None)
|
||||
return_type: Type::None
|
||||
},
|
||||
vec![(Instruction::r#return(false), Type::None, Span(0, 0))],
|
||||
vec![(Instruction::r#return(false), Span(0, 0))],
|
||||
vec![],
|
||||
vec![]
|
||||
))
|
||||
@ -61,28 +57,18 @@ fn parentheses_precedence() {
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::Integer)
|
||||
return_type: Type::Integer
|
||||
},
|
||||
vec![
|
||||
(
|
||||
Instruction::add(
|
||||
Destination::Register(0),
|
||||
Argument::Constant(0),
|
||||
Argument::Constant(1)
|
||||
),
|
||||
Type::Integer,
|
||||
Instruction::add(0, Argument::Constant(0), Argument::Constant(1)),
|
||||
Span(3, 4)
|
||||
),
|
||||
(
|
||||
Instruction::multiply(
|
||||
Destination::Register(1),
|
||||
Argument::Register(0),
|
||||
Argument::Constant(2)
|
||||
),
|
||||
Type::Integer,
|
||||
Instruction::multiply(1, Argument::Register(0), Argument::Constant(2)),
|
||||
Span(8, 9)
|
||||
),
|
||||
(Instruction::r#return(true), Type::None, Span(11, 11)),
|
||||
(Instruction::r#return(true), Span(11, 11)),
|
||||
],
|
||||
vec![
|
||||
ConcreteValue::Integer(1),
|
||||
@ -95,3 +81,49 @@ fn parentheses_precedence() {
|
||||
|
||||
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(9))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_operator_precedence() {
|
||||
let source = "1 + 2 - 3 * 4 / 5";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Type::Integer,
|
||||
},
|
||||
vec![
|
||||
(
|
||||
Instruction::add(0, Argument::Constant(0), Argument::Constant(1)),
|
||||
Span(2, 3)
|
||||
),
|
||||
(
|
||||
Instruction::multiply(1, Argument::Constant(2), Argument::Constant(3)),
|
||||
Span(10, 11)
|
||||
),
|
||||
(
|
||||
Instruction::divide(2, Argument::Register(1), Argument::Constant(4)),
|
||||
Span(14, 15)
|
||||
),
|
||||
(
|
||||
Instruction::subtract(3, Argument::Register(0), Argument::Register(2)),
|
||||
Span(6, 7)
|
||||
),
|
||||
(Instruction::r#return(true), Span(17, 17)),
|
||||
],
|
||||
vec![
|
||||
ConcreteValue::Integer(1),
|
||||
ConcreteValue::Integer(2),
|
||||
ConcreteValue::Integer(3),
|
||||
ConcreteValue::Integer(4),
|
||||
ConcreteValue::Integer(5),
|
||||
],
|
||||
vec![]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(1))));
|
||||
}
|
||||
|
@ -11,26 +11,14 @@ fn equal() {
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::Boolean)
|
||||
return_type: Type::Boolean
|
||||
},
|
||||
vec![
|
||||
(
|
||||
Instruction::equal(true, Argument::Constant(0), Argument::Constant(1)),
|
||||
Type::None,
|
||||
Instruction::equal(0, true, Argument::Constant(0), Argument::Constant(1)),
|
||||
Span(2, 4)
|
||||
),
|
||||
(Instruction::jump(1, true), Type::None, Span(2, 4)),
|
||||
(
|
||||
Instruction::load_boolean(Destination::Register(0), true, true),
|
||||
Type::Boolean,
|
||||
Span(2, 4)
|
||||
),
|
||||
(
|
||||
Instruction::load_boolean(Destination::Register(0), false, false),
|
||||
Type::Boolean,
|
||||
Span(2, 4)
|
||||
),
|
||||
(Instruction::r#return(true), Type::None, Span(6, 6)),
|
||||
(Instruction::r#return(true), Span(6, 6)),
|
||||
],
|
||||
vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)],
|
||||
vec![]
|
||||
@ -51,26 +39,14 @@ fn greater() {
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::Boolean)
|
||||
return_type: Type::Boolean
|
||||
},
|
||||
vec![
|
||||
(
|
||||
Instruction::less_equal(false, Argument::Constant(0), Argument::Constant(1)),
|
||||
Type::None,
|
||||
Instruction::less_equal(0, false, Argument::Constant(0), Argument::Constant(1)),
|
||||
Span(2, 3)
|
||||
),
|
||||
(Instruction::jump(1, true), Type::None, Span(2, 3)),
|
||||
(
|
||||
Instruction::load_boolean(Destination::Register(0), true, true),
|
||||
Type::Boolean,
|
||||
Span(2, 3)
|
||||
),
|
||||
(
|
||||
Instruction::load_boolean(Destination::Register(0), false, false),
|
||||
Type::Boolean,
|
||||
Span(2, 3)
|
||||
),
|
||||
(Instruction::r#return(true), Type::None, Span(5, 5)),
|
||||
(Instruction::r#return(true), Span(5, 5)),
|
||||
],
|
||||
vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)],
|
||||
vec![]
|
||||
@ -91,26 +67,14 @@ fn greater_than_or_equal() {
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::Boolean)
|
||||
return_type: Type::Boolean
|
||||
},
|
||||
vec![
|
||||
(
|
||||
Instruction::less(false, Argument::Constant(0), Argument::Constant(1)),
|
||||
Type::None,
|
||||
Instruction::less(0, false, Argument::Constant(0), Argument::Constant(1)),
|
||||
Span(2, 4)
|
||||
),
|
||||
(Instruction::jump(1, true), Type::None, Span(2, 4)),
|
||||
(
|
||||
Instruction::load_boolean(Destination::Register(0), true, true),
|
||||
Type::Boolean,
|
||||
Span(2, 4)
|
||||
),
|
||||
(
|
||||
Instruction::load_boolean(Destination::Register(0), false, false),
|
||||
Type::Boolean,
|
||||
Span(2, 4)
|
||||
),
|
||||
(Instruction::r#return(true), Type::None, Span(6, 6)),
|
||||
(Instruction::r#return(true), Span(6, 6)),
|
||||
],
|
||||
vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)],
|
||||
vec![]
|
||||
@ -131,26 +95,14 @@ fn less_than() {
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::Boolean)
|
||||
return_type: Type::Boolean
|
||||
},
|
||||
vec![
|
||||
(
|
||||
Instruction::less(true, Argument::Constant(0), Argument::Constant(1)),
|
||||
Type::None,
|
||||
Instruction::less(0, true, Argument::Constant(0), Argument::Constant(1)),
|
||||
Span(2, 3)
|
||||
),
|
||||
(Instruction::jump(1, true), Type::None, Span(2, 3)),
|
||||
(
|
||||
Instruction::load_boolean(Destination::Register(0), true, true),
|
||||
Type::Boolean,
|
||||
Span(2, 3)
|
||||
),
|
||||
(
|
||||
Instruction::load_boolean(Destination::Register(0), false, false),
|
||||
Type::Boolean,
|
||||
Span(2, 3)
|
||||
),
|
||||
(Instruction::r#return(true), Type::None, Span(5, 5)),
|
||||
(Instruction::r#return(true), Span(5, 5)),
|
||||
],
|
||||
vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)],
|
||||
vec![]
|
||||
@ -171,26 +123,14 @@ fn less_than_or_equal() {
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::Boolean)
|
||||
return_type: Type::Boolean
|
||||
},
|
||||
vec![
|
||||
(
|
||||
Instruction::less_equal(true, Argument::Constant(0), Argument::Constant(1)),
|
||||
Type::None,
|
||||
Instruction::less_equal(0, true, Argument::Constant(0), Argument::Constant(1)),
|
||||
Span(2, 4)
|
||||
),
|
||||
(Instruction::jump(1, true), Type::None, Span(2, 4)),
|
||||
(
|
||||
Instruction::load_boolean(Destination::Register(0), true, true),
|
||||
Type::Boolean,
|
||||
Span(2, 4)
|
||||
),
|
||||
(
|
||||
Instruction::load_boolean(Destination::Register(0), false, false),
|
||||
Type::Boolean,
|
||||
Span(2, 4)
|
||||
),
|
||||
(Instruction::r#return(true), Type::None, Span(6, 6)),
|
||||
(Instruction::r#return(true), Span(6, 6)),
|
||||
],
|
||||
vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)],
|
||||
vec![]
|
||||
@ -211,26 +151,14 @@ fn not_equal() {
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::Boolean)
|
||||
return_type: Type::Boolean
|
||||
},
|
||||
vec![
|
||||
(
|
||||
Instruction::equal(false, Argument::Constant(0), Argument::Constant(1)),
|
||||
Type::None,
|
||||
Instruction::equal(0, false, Argument::Constant(0), Argument::Constant(1)),
|
||||
Span(2, 4)
|
||||
),
|
||||
(Instruction::jump(1, true), Type::None, Span(2, 4)),
|
||||
(
|
||||
Instruction::load_boolean(Destination::Register(0), true, true),
|
||||
Type::Boolean,
|
||||
Span(2, 4)
|
||||
),
|
||||
(
|
||||
Instruction::load_boolean(Destination::Register(0), false, false),
|
||||
Type::Boolean,
|
||||
Span(2, 4)
|
||||
),
|
||||
(Instruction::r#return(true), Type::None, Span(6, 6)),
|
||||
(Instruction::r#return(true), Span(6, 6)),
|
||||
],
|
||||
vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)],
|
||||
vec![]
|
||||
|
@ -1,409 +0,0 @@
|
||||
use dust_lang::*;
|
||||
|
||||
#[test]
|
||||
fn equality_assignment_long() {
|
||||
let source = "let a = if 4 == 4 { true } else { false }; a";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::Boolean)
|
||||
},
|
||||
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![ConcreteValue::Integer(4), ConcreteValue::string("a")],
|
||||
vec![Local::new(1, Type::Boolean, false, Scope::default(),)]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(true))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equality_assignment_short() {
|
||||
let source = "let a = 4 == 4 a";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::Boolean)
|
||||
},
|
||||
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![ConcreteValue::Integer(4), ConcreteValue::string("a")],
|
||||
vec![Local::new(1, Type::Boolean, false, Scope::default())]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(true))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_else_assigment_false() {
|
||||
let source = r#"
|
||||
let a = if 4 == 3 {
|
||||
panic();
|
||||
0
|
||||
} else {
|
||||
1; 2; 3; 4;
|
||||
42
|
||||
};
|
||||
a"#;
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::Integer)
|
||||
},
|
||||
vec![
|
||||
(
|
||||
*Instruction::equal(true, 0, 1)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(22, 24)
|
||||
),
|
||||
(Instruction::jump(3, true), Span(27, 28)),
|
||||
(
|
||||
Instruction::call_native(0, NativeFunction::Panic, 0),
|
||||
Span(41, 48)
|
||||
),
|
||||
(Instruction::load_constant(0, 2, false), Span(62, 63)),
|
||||
(Instruction::jump(5, true), Span(129, 130)),
|
||||
(Instruction::load_constant(1, 3, false), Span(93, 94)),
|
||||
(Instruction::load_constant(2, 4, false), Span(96, 97)),
|
||||
(Instruction::load_constant(3, 1, false), Span(99, 100)),
|
||||
(Instruction::load_constant(4, 0, false), Span(102, 103)),
|
||||
(Instruction::load_constant(5, 5, false), Span(117, 119)),
|
||||
(Instruction::r#move(5, 0), Span(129, 130)),
|
||||
(Instruction::define_local(5, 0, false), Span(13, 14)),
|
||||
(Instruction::get_local(6, 0), Span(139, 140)),
|
||||
(Instruction::r#return(true), Span(140, 140)),
|
||||
],
|
||||
vec![
|
||||
ConcreteValue::Integer(4),
|
||||
ConcreteValue::Integer(3),
|
||||
ConcreteValue::Integer(0),
|
||||
ConcreteValue::Integer(1),
|
||||
ConcreteValue::Integer(2),
|
||||
ConcreteValue::Integer(42),
|
||||
ConcreteValue::string("a")
|
||||
],
|
||||
vec![Local::new(6, Type::Integer, false, Scope::default())]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(42))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_else_assigment_true() {
|
||||
let source = r#"
|
||||
let a = if 4 == 4 {
|
||||
1; 2; 3; 4;
|
||||
42
|
||||
} else {
|
||||
panic();
|
||||
0
|
||||
};
|
||||
a"#;
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::Integer)
|
||||
},
|
||||
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(2, true), Span(129, 130)),
|
||||
(
|
||||
Instruction::call_native(5, NativeFunction::Panic, 0),
|
||||
Span(97, 104)
|
||||
),
|
||||
(Instruction::load_constant(5, 5, false), Span(118, 119)),
|
||||
(Instruction::r#move(5, 4), Span(129, 130)),
|
||||
(Instruction::define_local(5, 0, false), Span(13, 14)),
|
||||
(Instruction::get_local(6, 0), Span(139, 140)),
|
||||
(Instruction::r#return(true), Span(140, 140)),
|
||||
],
|
||||
vec![
|
||||
ConcreteValue::Integer(4),
|
||||
ConcreteValue::Integer(1),
|
||||
ConcreteValue::Integer(2),
|
||||
ConcreteValue::Integer(3),
|
||||
ConcreteValue::Integer(42),
|
||||
ConcreteValue::Integer(0),
|
||||
ConcreteValue::string("a")
|
||||
],
|
||||
vec![Local::new(6, Type::Integer, false, Scope::default())]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(ConcreteValue::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,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::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![
|
||||
ConcreteValue::Integer(1),
|
||||
ConcreteValue::Integer(2),
|
||||
ConcreteValue::Integer(3),
|
||||
ConcreteValue::Integer(4),
|
||||
],
|
||||
vec![]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_else_false() {
|
||||
let source = "if 1 == 2 { panic(); 0 } else { 42 }";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::Integer)
|
||||
},
|
||||
vec![
|
||||
(
|
||||
*Instruction::equal(true, 0, 1)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(5, 7)
|
||||
),
|
||||
(Instruction::jump(2, true), Span(10, 11)),
|
||||
(
|
||||
Instruction::call_native(0, NativeFunction::Panic, 0),
|
||||
Span(12, 19)
|
||||
),
|
||||
(Instruction::load_constant(0, 2, true), Span(21, 22)),
|
||||
(Instruction::load_constant(1, 3, false), Span(32, 34)),
|
||||
(Instruction::r#move(1, 0), Span(36, 36)),
|
||||
(Instruction::r#return(true), Span(36, 36)),
|
||||
],
|
||||
vec![
|
||||
ConcreteValue::Integer(1),
|
||||
ConcreteValue::Integer(2),
|
||||
ConcreteValue::Integer(0),
|
||||
ConcreteValue::Integer(42)
|
||||
],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(42))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_else_true() {
|
||||
let source = "if 1 == 1 { 42 } else { panic(); 0 }";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::Integer)
|
||||
},
|
||||
vec![
|
||||
(
|
||||
*Instruction::equal(true, 0, 0)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(5, 7)
|
||||
),
|
||||
(Instruction::jump(2, true), Span(10, 11)),
|
||||
(Instruction::load_constant(0, 1, false), Span(12, 14)),
|
||||
(Instruction::jump(2, true), Span(36, 36)),
|
||||
(
|
||||
Instruction::call_native(1, NativeFunction::Panic, 0),
|
||||
Span(24, 31)
|
||||
),
|
||||
(Instruction::load_constant(1, 2, false), Span(33, 34)),
|
||||
(Instruction::r#move(1, 0), Span(36, 36)),
|
||||
(Instruction::r#return(true), Span(36, 36))
|
||||
],
|
||||
vec![
|
||||
ConcreteValue::Integer(1),
|
||||
ConcreteValue::Integer(42),
|
||||
ConcreteValue::Integer(0)
|
||||
],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(42))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_false() {
|
||||
let source = "if 1 == 2 { panic() }";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::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::r#return(false), Span(21, 21))
|
||||
],
|
||||
vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_true() {
|
||||
let source = "if 1 == 1 { panic() }";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::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::call_native(0, NativeFunction::Panic, 0),
|
||||
Span(12, 19)
|
||||
),
|
||||
(Instruction::r#return(false), Span(21, 21))
|
||||
],
|
||||
vec![ConcreteValue::Integer(1)],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
run(source),
|
||||
Err(DustError::Runtime {
|
||||
error: VmError::NativeFunction(NativeFunctionError::Panic {
|
||||
message: None,
|
||||
position: Span(12, 19)
|
||||
}),
|
||||
source
|
||||
})
|
||||
);
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
#[test]
|
||||
fn if_true() {
|
||||
let source = "if true && true { 42 } else { 0 }";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::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::call_native(0, NativeFunction::Panic, 0),
|
||||
Span(12, 19)
|
||||
),
|
||||
(Instruction::r#return(false), Span(21, 21))
|
||||
],
|
||||
vec![ConcreteValue::Integer(1)],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(42))),);
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
use dust_lang::*;
|
||||
use smallvec::smallvec;
|
||||
|
||||
#[test]
|
||||
fn function() {
|
||||
@ -6,33 +7,28 @@ fn function() {
|
||||
|
||||
assert_eq!(
|
||||
run(source),
|
||||
Ok(Some(ConcreteValue::Function(Chunk::with_data(
|
||||
Ok(Some(ConcreteValue::function(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::Function(FunctionType {
|
||||
return_type: Type::function(FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
|
||||
return_type: Box::new(Type::Integer),
|
||||
}))
|
||||
value_parameters: Some(smallvec![(0, Type::Integer), (1, Type::Integer)]),
|
||||
return_type: Type::Integer,
|
||||
})
|
||||
},
|
||||
vec![
|
||||
(
|
||||
Instruction::add(
|
||||
Destination::Register(2),
|
||||
Argument::Local(0),
|
||||
Argument::Local(1)
|
||||
),
|
||||
Type::Integer,
|
||||
Instruction::add(2, Argument::Register(0), Argument::Register(1)),
|
||||
Span(30, 31)
|
||||
),
|
||||
(Instruction::r#return(true), Type::None, Span(35, 35)),
|
||||
(Instruction::r#return(true), Span(34, 35)),
|
||||
],
|
||||
vec![ConcreteValue::string("a"), ConcreteValue::string("b"),],
|
||||
vec![
|
||||
Local::new(0, Type::Integer, false, Scope::default()),
|
||||
Local::new(1, Type::Integer, false, Scope::default())
|
||||
Local::new(0, 0, false, Scope::default()),
|
||||
Local::new(1, 1, false, Scope::default())
|
||||
]
|
||||
))))
|
||||
);
|
||||
@ -49,59 +45,34 @@ fn function_call() {
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::Integer)
|
||||
return_type: Type::Integer
|
||||
},
|
||||
vec![
|
||||
(
|
||||
Instruction::load_constant(Destination::Register(0), 0, false),
|
||||
Type::Function(FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
|
||||
return_type: Box::new(Type::Integer),
|
||||
}),
|
||||
Span(0, 36)
|
||||
),
|
||||
(
|
||||
Instruction::load_constant(Destination::Register(1), 1, false),
|
||||
Type::Integer,
|
||||
Span(36, 37)
|
||||
),
|
||||
(
|
||||
Instruction::load_constant(Destination::Register(2), 2, false),
|
||||
Type::Integer,
|
||||
Span(39, 40)
|
||||
),
|
||||
(
|
||||
Instruction::call(Destination::Register(3), Argument::Constant(0), 2),
|
||||
Type::Integer,
|
||||
Span(35, 41)
|
||||
),
|
||||
(Instruction::r#return(true), Type::None, Span(41, 41)),
|
||||
(Instruction::load_constant(0, 0, false), Span(0, 35)),
|
||||
(Instruction::load_constant(1, 1, false), Span(36, 37)),
|
||||
(Instruction::load_constant(2, 2, false), Span(39, 40)),
|
||||
(Instruction::call(3, Argument::Constant(0), 2), Span(35, 41)),
|
||||
(Instruction::r#return(true), Span(41, 41)),
|
||||
],
|
||||
vec![
|
||||
ConcreteValue::Function(Chunk::with_data(
|
||||
ConcreteValue::function(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
|
||||
return_type: Box::new(Type::Integer)
|
||||
value_parameters: Some(smallvec![(0, Type::Integer), (1, Type::Integer)]),
|
||||
return_type: Type::Integer
|
||||
},
|
||||
vec![
|
||||
(
|
||||
Instruction::add(
|
||||
Destination::Register(2),
|
||||
Argument::Local(0),
|
||||
Argument::Local(1)
|
||||
),
|
||||
Type::Integer,
|
||||
Instruction::add(2, Argument::Register(0), Argument::Register(1)),
|
||||
Span(30, 31)
|
||||
),
|
||||
(Instruction::r#return(true), Type::None, Span(35, 36)),
|
||||
(Instruction::r#return(true), Span(34, 35)),
|
||||
],
|
||||
vec![ConcreteValue::string("a"), ConcreteValue::string("b"),],
|
||||
vec![
|
||||
Local::new(0, Type::Integer, false, Scope::default()),
|
||||
Local::new(1, Type::Integer, false, Scope::default())
|
||||
Local::new(0, 0, false, Scope::default()),
|
||||
Local::new(1, 1, false, Scope::default())
|
||||
]
|
||||
)),
|
||||
ConcreteValue::Integer(1),
|
||||
@ -125,63 +96,36 @@ fn function_declaration() {
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::None)
|
||||
return_type: Type::None
|
||||
},
|
||||
vec![
|
||||
(
|
||||
Instruction::load_constant(Destination::Register(0), 0, false),
|
||||
Type::Function(FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
|
||||
return_type: Box::new(Type::Integer),
|
||||
}),
|
||||
Span(0, 40)
|
||||
),
|
||||
(
|
||||
Instruction::define_local(0, 0, false),
|
||||
Type::None,
|
||||
Span(3, 6)
|
||||
),
|
||||
(Instruction::r#return(false), Type::None, Span(40, 40))
|
||||
(Instruction::load_constant(0, 0, false), Span(0, 40)),
|
||||
(Instruction::r#return(false), Span(40, 40))
|
||||
],
|
||||
vec![
|
||||
ConcreteValue::Function(Chunk::with_data(
|
||||
Some("add".to_string()),
|
||||
ConcreteValue::function(Chunk::with_data(
|
||||
Some("add".into()),
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
|
||||
return_type: Box::new(Type::Integer)
|
||||
value_parameters: Some(smallvec![(0, Type::Integer), (1, Type::Integer)]),
|
||||
return_type: Type::Integer
|
||||
},
|
||||
vec![
|
||||
(
|
||||
Instruction::add(
|
||||
Destination::Register(2),
|
||||
Argument::Local(0),
|
||||
Argument::Local(1)
|
||||
),
|
||||
Type::Integer,
|
||||
Instruction::add(2, Argument::Register(0), Argument::Register(1)),
|
||||
Span(35, 36)
|
||||
),
|
||||
(Instruction::r#return(true), Type::None, Span(40, 40)),
|
||||
(Instruction::r#return(true), Span(39, 40)),
|
||||
],
|
||||
vec![ConcreteValue::string("a"), ConcreteValue::string("b")],
|
||||
vec![
|
||||
Local::new(0, Type::Integer, false, Scope::default()),
|
||||
Local::new(1, Type::Integer, false, Scope::default())
|
||||
Local::new(0, 0, false, Scope::default()),
|
||||
Local::new(1, 1, false, Scope::default())
|
||||
]
|
||||
)),
|
||||
ConcreteValue::string("add"),
|
||||
],
|
||||
vec![Local::new(
|
||||
1,
|
||||
Type::Function(FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
|
||||
return_type: Box::new(Type::Integer),
|
||||
}),
|
||||
false,
|
||||
Scope::default(),
|
||||
),],
|
||||
vec![Local::new(1, 0, false, Scope::default(),),],
|
||||
)),
|
||||
);
|
||||
|
||||
|
@ -11,15 +11,11 @@ fn empty_list() {
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::List(Box::new(Type::Any))),
|
||||
return_type: Type::List(Box::new(Type::Any)),
|
||||
},
|
||||
vec![
|
||||
(
|
||||
Instruction::load_list(Destination::Register(0), 0),
|
||||
Type::List(Box::new(Type::Any)),
|
||||
Span(0, 2)
|
||||
),
|
||||
(Instruction::r#return(true), Type::None, Span(2, 2)),
|
||||
(Instruction::load_list(0, 0), Span(0, 2)),
|
||||
(Instruction::r#return(true), Span(2, 2)),
|
||||
],
|
||||
vec![],
|
||||
vec![]
|
||||
@ -40,30 +36,14 @@ fn list() {
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::List(Box::new(Type::Integer))),
|
||||
return_type: Type::List(Box::new(Type::Integer)),
|
||||
},
|
||||
vec![
|
||||
(
|
||||
Instruction::load_constant(Destination::Register(0), 0, false),
|
||||
Type::Integer,
|
||||
Span(1, 2)
|
||||
),
|
||||
(
|
||||
Instruction::load_constant(Destination::Register(1), 1, false),
|
||||
Type::Integer,
|
||||
Span(4, 5)
|
||||
),
|
||||
(
|
||||
Instruction::load_constant(Destination::Register(2), 2, false),
|
||||
Type::Integer,
|
||||
Span(7, 8)
|
||||
),
|
||||
(
|
||||
Instruction::load_list(Destination::Register(3), 0),
|
||||
Type::List(Box::new(Type::Integer)),
|
||||
Span(0, 9)
|
||||
),
|
||||
(Instruction::r#return(true), Type::None, Span(9, 9)),
|
||||
(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![
|
||||
ConcreteValue::Integer(1),
|
||||
@ -95,48 +75,25 @@ fn list_with_complex_expression() {
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::List(Box::new(Type::Integer))),
|
||||
return_type: Type::List(Box::new(Type::Integer)),
|
||||
},
|
||||
vec![
|
||||
(Instruction::load_constant(0, 0, false), Span(1, 2)),
|
||||
(
|
||||
Instruction::load_constant(Destination::Register(0), 0, false),
|
||||
Type::Integer,
|
||||
Span(1, 2)
|
||||
),
|
||||
(
|
||||
Instruction::add(
|
||||
Destination::Register(1),
|
||||
Argument::Constant(1),
|
||||
Argument::Constant(2)
|
||||
),
|
||||
Type::Integer,
|
||||
Instruction::add(1, Argument::Constant(1), Argument::Constant(2)),
|
||||
Span(6, 7)
|
||||
),
|
||||
(
|
||||
Instruction::multiply(
|
||||
Destination::Register(2),
|
||||
Argument::Constant(3),
|
||||
Argument::Constant(4)
|
||||
),
|
||||
Type::Integer,
|
||||
Instruction::multiply(2, Argument::Constant(3), Argument::Constant(4)),
|
||||
Span(14, 15)
|
||||
),
|
||||
(
|
||||
Instruction::subtract(
|
||||
Destination::Register(3),
|
||||
Argument::Register(1),
|
||||
Argument::Register(2)
|
||||
),
|
||||
Type::Integer,
|
||||
Instruction::subtract(3, Argument::Register(1), Argument::Register(2)),
|
||||
Span(10, 11)
|
||||
),
|
||||
(Instruction::close(1, 3), Type::None, Span(17, 18)),
|
||||
(
|
||||
Instruction::load_list(Destination::Register(4), 0),
|
||||
Type::List(Box::new(Type::Integer)),
|
||||
Span(0, 18)
|
||||
),
|
||||
(Instruction::r#return(true), Type::None, Span(18, 18)),
|
||||
(Instruction::close(1, 3), Span(17, 18)),
|
||||
(Instruction::load_list(4, 0), Span(0, 18)),
|
||||
(Instruction::r#return(true), Span(18, 18)),
|
||||
],
|
||||
vec![
|
||||
ConcreteValue::Integer(1),
|
||||
@ -169,34 +126,17 @@ fn list_with_simple_expression() {
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::List(Box::new(Type::Integer))),
|
||||
return_type: Type::List(Box::new(Type::Integer)),
|
||||
},
|
||||
vec![
|
||||
(Instruction::load_constant(0, 0, false), Span(1, 2)),
|
||||
(
|
||||
Instruction::load_constant(Destination::Register(0), 0, false),
|
||||
Type::Integer,
|
||||
Span(1, 2)
|
||||
),
|
||||
(
|
||||
Instruction::add(
|
||||
Destination::Register(1),
|
||||
Argument::Constant(1),
|
||||
Argument::Constant(2)
|
||||
),
|
||||
Type::Integer,
|
||||
Instruction::add(1, Argument::Constant(1), Argument::Constant(2)),
|
||||
Span(6, 7)
|
||||
),
|
||||
(
|
||||
Instruction::load_constant(Destination::Register(2), 3, false),
|
||||
Type::Integer,
|
||||
Span(11, 12)
|
||||
),
|
||||
(
|
||||
Instruction::load_list(Destination::Register(3), 0),
|
||||
Type::List(Box::new(Type::Integer)),
|
||||
Span(0, 13)
|
||||
),
|
||||
(Instruction::r#return(true), Type::None, Span(13, 13)),
|
||||
(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![
|
||||
ConcreteValue::Integer(1),
|
||||
|
@ -1,160 +0,0 @@
|
||||
use dust_lang::*;
|
||||
|
||||
#[test]
|
||||
fn and() {
|
||||
let source = "true && false";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::Boolean),
|
||||
},
|
||||
vec![
|
||||
(
|
||||
Instruction::load_boolean(Destination::Register(0), true, false),
|
||||
Type::Boolean,
|
||||
Span(0, 4)
|
||||
),
|
||||
(
|
||||
Instruction::test(Argument::Register(0), true),
|
||||
Type::None,
|
||||
Span(5, 7)
|
||||
),
|
||||
(Instruction::jump(1, true), Type::None, Span(5, 7)),
|
||||
(
|
||||
Instruction::load_boolean(Destination::Register(1), false, false),
|
||||
Type::Boolean,
|
||||
Span(8, 13)
|
||||
),
|
||||
(Instruction::r#return(true), Type::None, Span(13, 13)),
|
||||
],
|
||||
vec![],
|
||||
vec![]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(false))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn or() {
|
||||
let source = "true || false";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::Boolean),
|
||||
},
|
||||
vec![
|
||||
(
|
||||
Instruction::load_boolean(Destination::Register(0), true, false),
|
||||
Type::Boolean,
|
||||
Span(0, 4)
|
||||
),
|
||||
(
|
||||
Instruction::test(Argument::Register(0), false),
|
||||
Type::None,
|
||||
Span(5, 7)
|
||||
),
|
||||
(Instruction::jump(1, true), Type::None, Span(5, 7)),
|
||||
(
|
||||
Instruction::load_boolean(Destination::Register(1), false, false),
|
||||
Type::Boolean,
|
||||
Span(8, 13)
|
||||
),
|
||||
(Instruction::r#return(true), Type::None, Span(13, 13)),
|
||||
],
|
||||
vec![],
|
||||
vec![]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(true))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn and_and_or() {
|
||||
let source = "let a = true; let b = true; let c = false; a && b || c";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::Boolean),
|
||||
},
|
||||
vec![
|
||||
(
|
||||
Instruction::load_boolean(Destination::Register(0), true, false),
|
||||
Type::Boolean,
|
||||
Span(8, 12)
|
||||
),
|
||||
(
|
||||
Instruction::define_local(0, 0, false),
|
||||
Type::None,
|
||||
Span(4, 5)
|
||||
),
|
||||
(
|
||||
Instruction::load_boolean(Destination::Register(1), true, false),
|
||||
Type::Boolean,
|
||||
Span(22, 26)
|
||||
),
|
||||
(
|
||||
Instruction::define_local(1, 1, false),
|
||||
Type::None,
|
||||
Span(18, 19)
|
||||
),
|
||||
(
|
||||
Instruction::load_boolean(Destination::Register(2), false, false),
|
||||
Type::Boolean,
|
||||
Span(36, 41)
|
||||
),
|
||||
(
|
||||
Instruction::define_local(2, 2, false),
|
||||
Type::None,
|
||||
Span(32, 33)
|
||||
),
|
||||
(
|
||||
Instruction::test(Argument::Local(0), true),
|
||||
Type::None,
|
||||
Span(45, 47)
|
||||
),
|
||||
(Instruction::jump(1, true), Type::None, Span(45, 47)),
|
||||
(
|
||||
Instruction::test(Argument::Local(1), false),
|
||||
Type::None,
|
||||
Span(50, 52)
|
||||
),
|
||||
(Instruction::jump(1, true), Type::None, Span(50, 52)),
|
||||
(
|
||||
Instruction::get_local(Destination::Register(3), 2),
|
||||
Type::Boolean,
|
||||
Span(53, 54)
|
||||
),
|
||||
(Instruction::r#return(true), Type::None, Span(54, 54)),
|
||||
],
|
||||
vec![
|
||||
ConcreteValue::string("a"),
|
||||
ConcreteValue::string("b"),
|
||||
ConcreteValue::string("c")
|
||||
],
|
||||
vec![
|
||||
Local::new(0, Type::Boolean, false, Scope::default()),
|
||||
Local::new(1, Type::Boolean, false, Scope::default()),
|
||||
Local::new(2, Type::Boolean, false, Scope::default())
|
||||
]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(true))));
|
||||
}
|
146
dust-lang/tests/logic/and.rs
Normal file
146
dust-lang/tests/logic/and.rs
Normal file
@ -0,0 +1,146 @@
|
||||
use dust_lang::*;
|
||||
use smallvec::smallvec;
|
||||
|
||||
#[test]
|
||||
fn true_and_true() {
|
||||
let source = "true && true";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Type::Boolean,
|
||||
},
|
||||
smallvec![
|
||||
Instruction::load_boolean(0, true, false),
|
||||
Instruction::test(0, true),
|
||||
Instruction::jump(1, true),
|
||||
Instruction::load_boolean(1, true, false),
|
||||
Instruction::r#return(true),
|
||||
],
|
||||
smallvec![
|
||||
Span(0, 4),
|
||||
Span(5, 7),
|
||||
Span(5, 7),
|
||||
Span(8, 12),
|
||||
Span(12, 12),
|
||||
],
|
||||
smallvec![],
|
||||
smallvec![],
|
||||
vec![]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn false_and_false() {
|
||||
let source = "false && false";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Type::Boolean,
|
||||
},
|
||||
smallvec![
|
||||
Instruction::load_boolean(0, false, false),
|
||||
Instruction::test(0, true),
|
||||
Instruction::jump(1, true),
|
||||
Instruction::load_boolean(1, false, false),
|
||||
Instruction::r#return(true),
|
||||
],
|
||||
smallvec![
|
||||
Span(0, 5),
|
||||
Span(6, 8),
|
||||
Span(6, 8),
|
||||
Span(9, 14),
|
||||
Span(14, 14),
|
||||
],
|
||||
smallvec![],
|
||||
smallvec![],
|
||||
vec![]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn false_and_true() {
|
||||
let source = "false && true";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Type::Boolean,
|
||||
},
|
||||
smallvec![
|
||||
Instruction::load_boolean(0, false, false),
|
||||
Instruction::test(0, true),
|
||||
Instruction::jump(1, true),
|
||||
Instruction::load_boolean(1, true, false),
|
||||
Instruction::r#return(true),
|
||||
],
|
||||
smallvec![
|
||||
Span(0, 5),
|
||||
Span(6, 8),
|
||||
Span(6, 8),
|
||||
Span(9, 13),
|
||||
Span(13, 13)
|
||||
],
|
||||
smallvec![],
|
||||
smallvec![],
|
||||
vec![]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn true_and_false() {
|
||||
let source = "true && false";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Type::Boolean,
|
||||
},
|
||||
smallvec![
|
||||
Instruction::load_boolean(0, true, false),
|
||||
Instruction::test(0, true),
|
||||
Instruction::jump(1, true),
|
||||
Instruction::load_boolean(1, false, false),
|
||||
Instruction::r#return(true),
|
||||
],
|
||||
smallvec![
|
||||
Span(0, 4),
|
||||
Span(5, 7),
|
||||
Span(5, 7),
|
||||
Span(8, 13),
|
||||
Span(13, 13)
|
||||
],
|
||||
smallvec![],
|
||||
smallvec![],
|
||||
vec![]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
|
||||
}
|
42
dust-lang/tests/logic/and_and.rs
Normal file
42
dust-lang/tests/logic/and_and.rs
Normal file
@ -0,0 +1,42 @@
|
||||
use dust_lang::*;
|
||||
use smallvec::smallvec;
|
||||
|
||||
#[test]
|
||||
fn true_and_true_and_true() {
|
||||
let source = "true && true && true";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Type::Boolean,
|
||||
},
|
||||
smallvec![
|
||||
Instruction::load_boolean(0, true, false),
|
||||
Instruction::test(0, true),
|
||||
Instruction::jump(1, true),
|
||||
Instruction::load_boolean(1, true, false),
|
||||
Instruction::test(1, true),
|
||||
Instruction::jump(1, true),
|
||||
Instruction::load_boolean(2, true, false),
|
||||
Instruction::r#return(true),
|
||||
],
|
||||
smallvec![
|
||||
Span(0, 4),
|
||||
Span(5, 7),
|
||||
Span(5, 7),
|
||||
Span(8, 12),
|
||||
Span(13, 15),
|
||||
Span(13, 15),
|
||||
Span(16, 20),
|
||||
Span(20, 20)
|
||||
],
|
||||
smallvec![],
|
||||
smallvec![],
|
||||
vec![],
|
||||
))
|
||||
);
|
||||
}
|
38
dust-lang/tests/logic/or.rs
Normal file
38
dust-lang/tests/logic/or.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use dust_lang::*;
|
||||
use smallvec::smallvec;
|
||||
|
||||
#[test]
|
||||
fn true_or_false() {
|
||||
let source = "true || false";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Type::Boolean,
|
||||
},
|
||||
smallvec![
|
||||
Instruction::load_boolean(0, true, false),
|
||||
Instruction::test(0, false),
|
||||
Instruction::jump(1, true),
|
||||
Instruction::load_boolean(1, false, false),
|
||||
Instruction::r#return(true),
|
||||
],
|
||||
smallvec![
|
||||
Span(0, 4),
|
||||
Span(5, 7),
|
||||
Span(5, 7),
|
||||
Span(8, 13),
|
||||
Span(13, 13),
|
||||
],
|
||||
smallvec![],
|
||||
smallvec![],
|
||||
vec![]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
|
||||
}
|
@ -11,41 +11,22 @@ fn r#while() {
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Box::new(Type::Integer),
|
||||
return_type: Type::Integer,
|
||||
},
|
||||
vec![
|
||||
(Instruction::load_constant(0, 0, false), Span(12, 13)),
|
||||
(
|
||||
Instruction::load_constant(Destination::Register(0), 0, false),
|
||||
Type::Integer,
|
||||
Span(12, 13)
|
||||
),
|
||||
(
|
||||
Instruction::define_local(0, 0, true),
|
||||
Type::None,
|
||||
Span(8, 9)
|
||||
),
|
||||
(
|
||||
Instruction::less(true, Argument::Local(0), Argument::Constant(2)),
|
||||
Type::None,
|
||||
Instruction::less(0, true, Argument::Register(0), Argument::Constant(2)),
|
||||
Span(23, 24)
|
||||
),
|
||||
(Instruction::jump(2, true), Type::None, Span(41, 42)),
|
||||
(Instruction::jump(2, true), Span(41, 42)),
|
||||
(
|
||||
Instruction::add(
|
||||
Destination::Local(0),
|
||||
Argument::Local(0),
|
||||
Argument::Constant(3)
|
||||
),
|
||||
Type::Integer,
|
||||
Instruction::add(0, Argument::Register(0), Argument::Constant(3)),
|
||||
Span(35, 36)
|
||||
),
|
||||
(Instruction::jump(3, false), Type::None, Span(41, 42)),
|
||||
(
|
||||
Instruction::get_local(Destination::Register(1), 0),
|
||||
Type::Integer,
|
||||
Span(41, 42)
|
||||
),
|
||||
(Instruction::r#return(true), Type::None, Span(42, 42)),
|
||||
(Instruction::jump(3, false), Span(41, 42)),
|
||||
(Instruction::get_local(1, 0), Span(41, 42)),
|
||||
(Instruction::r#return(true), Span(42, 42)),
|
||||
],
|
||||
vec![
|
||||
ConcreteValue::Integer(0),
|
||||
@ -53,7 +34,7 @@ fn r#while() {
|
||||
ConcreteValue::Integer(5),
|
||||
ConcreteValue::Integer(1),
|
||||
],
|
||||
vec![Local::new(1, Type::Integer, true, Scope::default())]
|
||||
vec![Local::new(1, 0, true, Scope::default())]
|
||||
)),
|
||||
);
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user