diff --git a/.gitignore b/.gitignore
index 2f7896d..2c6d904 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
+flamegraph.svg
+perf.data*
target/
diff --git a/Cargo.lock b/Cargo.lock
index 86146ba..6811bcd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 7de1d01..5d89860 100644
--- a/Cargo.toml
+++ b/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
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f288702
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ 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.
+
+
+ Copyright (C)
+
+ 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 .
+
+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:
+
+ Copyright (C)
+ 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
+.
+
+ 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
+.
diff --git a/README.md b/README.md
index 08f7473..b4cdfa4 100644
--- a/README.md
+++ b/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)
diff --git a/bench/addictive_addition/addictive_addition.ds b/bench/addictive_addition/addictive_addition.ds
new file mode 100644
index 0000000..cba1b4c
--- /dev/null
+++ b/bench/addictive_addition/addictive_addition.ds
@@ -0,0 +1,5 @@
+let mut i = 0
+
+while i < 5_000_000 {
+ i += 1
+}
diff --git a/bench/addictive_addition/addictive_addition.java b/bench/addictive_addition/addictive_addition.java
new file mode 100644
index 0000000..b30da65
--- /dev/null
+++ b/bench/addictive_addition/addictive_addition.java
@@ -0,0 +1,10 @@
+class AddictiveAddition {
+
+ public static void main(String[] args) {
+ int i = 0;
+
+ while (i < 5_000_000) {
+ i++;
+ }
+ }
+}
diff --git a/bench/addictive_addition/addictive_addition.js b/bench/addictive_addition/addictive_addition.js
new file mode 100644
index 0000000..19e22c4
--- /dev/null
+++ b/bench/addictive_addition/addictive_addition.js
@@ -0,0 +1,5 @@
+var i = 0;
+
+while (i < 5_000_000) {
+ i++;
+}
diff --git a/bench/addictive_addition/addictive_addition.lua b/bench/addictive_addition/addictive_addition.lua
new file mode 100644
index 0000000..33a8ade
--- /dev/null
+++ b/bench/addictive_addition/addictive_addition.lua
@@ -0,0 +1,5 @@
+local i = 1
+
+while i < 5000000 do
+ i = i + 1
+end
diff --git a/bench/addictive_addition/addictive_addition.py b/bench/addictive_addition/addictive_addition.py
new file mode 100644
index 0000000..5572534
--- /dev/null
+++ b/bench/addictive_addition/addictive_addition.py
@@ -0,0 +1,4 @@
+i = 1
+
+while i < 5_000_000:
+ i += 1
diff --git a/bench/addictive_addition/addictive_addition.rb b/bench/addictive_addition/addictive_addition.rb
new file mode 100644
index 0000000..928e576
--- /dev/null
+++ b/bench/addictive_addition/addictive_addition.rb
@@ -0,0 +1,5 @@
+i = 0
+
+while i < 5_000_000
+ i += 1
+end
diff --git a/bench/addictive_addition/run.sh b/bench/addictive_addition/run.sh
new file mode 100644
index 0000000..fbe3968
--- /dev/null
+++ b/bench/addictive_addition/run.sh
@@ -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'
diff --git a/examples/assets/fibonacci.js b/bench/fibonacci/fibonacci.js
similarity index 100%
rename from examples/assets/fibonacci.js
rename to bench/fibonacci/fibonacci.js
diff --git a/examples/assets/fibonacci.py b/bench/fibonacci/fibonacci.py
similarity index 100%
rename from examples/assets/fibonacci.py
rename to bench/fibonacci/fibonacci.py
diff --git a/bench/fibonacci/run.sh b/bench/fibonacci/run.sh
new file mode 100644
index 0000000..0404ea2
--- /dev/null
+++ b/bench/fibonacci/run.sh
@@ -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'
diff --git a/bench/recursion/recursion.ds b/bench/recursion/recursion.ds
new file mode 100644
index 0000000..257c310
--- /dev/null
+++ b/bench/recursion/recursion.ds
@@ -0,0 +1,9 @@
+fn decrement(i: int) -> str {
+ if i == 0 {
+ return "Done!";
+ }
+
+ decrement(i - 1)
+}
+
+write_line(decrement(10_000))
diff --git a/bench/recursion/recursion.js b/bench/recursion/recursion.js
new file mode 100644
index 0000000..f080f1f
--- /dev/null
+++ b/bench/recursion/recursion.js
@@ -0,0 +1,9 @@
+function decrement(i) {
+ if (i == 0) {
+ return "Done!";
+ }
+
+ return decrement(i - 1);
+}
+
+console.log(decrement(10_000));
diff --git a/bench/recursion/run.sh b/bench/recursion/run.sh
new file mode 100644
index 0000000..3b5a886
--- /dev/null
+++ b/bench/recursion/run.sh
@@ -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'
diff --git a/build.sh b/build.sh
new file mode 100644
index 0000000..0684cfd
--- /dev/null
+++ b/build.sh
@@ -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
diff --git a/dust-cli/Cargo.toml b/dust-cli/Cargo.toml
index 05812f8..2102ef1 100644
--- a/dust-cli/Cargo.toml
+++ b/dust-cli/Cargo.toml
@@ -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"
diff --git a/dust-cli/src/main.rs b/dust-cli/src/main.rs
index 872084b..10a5ca6 100644
--- a/dust-cli/src/main.rs
+++ b/dust-cli/src/main.rs
@@ -1,194 +1,389 @@
-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#"
+Dust CLI
+────────>
+{about}
+
+⚙️ Version:> {version}
+🦀 Author:> {author}
+⚖️ License:> GPL-3.0
+🔬 Repository:> https://git.jeffa.io/jeff/dust
+"#
+);
+
+const PLAIN_ABOUT: &str = r#"
+{about}
+"#;
+
+const USAGE: &str = cstr!(
+ r#"
+Usage:> {usage}
+"#
+);
+
+const SUBCOMMANDS: &str = cstr!(
+ r#"
+Modes:>
+{subcommands}
+"#
+);
+
+const OPTIONS: &str = cstr!(
+ r#"
+Options:>
+{options}
+"#
+);
+
+const CREATE_MAIN_HELP_TEMPLATE: fn() -> String =
+ || cformat!("{ABOUT}{USAGE}{SUBCOMMANDS}{OPTIONS}");
+
+const CREATE_MODE_HELP_TEMPLATE: fn(&str) -> String = |title| {
+ cformat!(
+ "\
+ {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,
#[command(subcommand)]
- mode: Option,
+ mode: Option,
+
+ #[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,
-
- /// 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,
+ /// 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,
}
-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,
- if let Some(level) = log {
- logger.filter_level(level).init();
- } else {
- logger.parse_env("DUST_LOG").init();
+ #[command(flatten)]
+ input: Input,
+}
+
+#[derive(Subcommand)]
+#[clap(subcommand_value_name = "MODE", flatten_help = true)]
+enum Mode {
+ Run(Run),
+
+ /// 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,
+
+ #[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) {
+ 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 let Some(source) = command {
- source
- } else {
- let path = file.expect("Path is required when command is not provided");
+ if input.stdin {
+ let mut source = String::new();
+ io::stdin()
+ .read_to_string(&mut source)
+ .expect("Failed to read from stdin");
- read_to_string(path).expect("Failed to read file")
+ 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,
- no_output,
- } = mode
- {
- let source = set_log_and_get_source(global_arguments, start_time);
- let run_result = run(&source);
+ set_global_default(subscriber).expect("Failed to set tracing subscriber");
- match run_result {
- Ok(Some(value)) => {
- if !no_output {
- println!("{}", value)
+ 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
}
}
- Ok(None) => {}
- Err(error) => {
- eprintln!("{}", error.report());
+ };
+
+ 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 CliMode::Disassemble {
- global_arguments,
- style,
- } = mode
+ if let Mode::Run(Run {
+ time,
+ no_output,
+ name,
+ input,
+ }) = mode
{
- let source = set_log_and_get_source(global_arguments, start_time);
- let chunk = match compile(&source) {
- Ok(chunk) => chunk,
+ 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) => {
- eprintln!("{}", error.report());
+ handle_compile_error(error, &source);
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,
+ match compiler.compile() {
+ Ok(()) => {}
Err(error) => {
- eprintln!("{}", error.report());
+ handle_compile_error(error, &source);
return;
}
- };
- let mut stdout = stdout().lock();
+ }
- write_token_list(&tokens, style, &mut stdout)
+ 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)
+ }
+ }
+
+ 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);
+ }
}
}
+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");
+ }
+ }
+}
+
+fn handle_compile_error(error: CompileError, source: &str) {
+ let dust_error = DustError::compile(error, source);
+ let report = dust_error.report();
+
+ eprintln!("{report}");
+}
+
#[cfg(test)]
mod tests {
use clap::CommandFactory;
diff --git a/dust-lang/Cargo.toml b/dust-lang/Cargo.toml
index c8598d4..bef5fb8 100644
--- a/dust-lang/Cargo.toml
+++ b/dust-lang/Cargo.toml
@@ -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"
diff --git a/dust-lang/benches/addictive_addition.rs b/dust-lang/benches/addictive_addition.rs
new file mode 100644
index 0000000..f6b121e
--- /dev/null
+++ b/dust-lang/benches/addictive_addition.rs
@@ -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);
diff --git a/dust-lang/benches/fibonacci.rs b/dust-lang/benches/fibonacci.rs
new file mode 100644
index 0000000..cd83319
--- /dev/null
+++ b/dust-lang/benches/fibonacci.rs
@@ -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);
diff --git a/dust-lang/src/chunk.rs b/dust-lang/src/chunk.rs
deleted file mode 100644
index 2b643d1..0000000
--- a/dust-lang/src/chunk.rs
+++ /dev/null
@@ -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,
- r#type: FunctionType,
-
- instructions: Vec<(Instruction, Type, Span)>,
- constants: Vec,
- locals: Vec,
-}
-
-impl Chunk {
- pub fn new(name: Option) -> 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,
- r#type: FunctionType,
- instructions: Vec<(Instruction, Type, Span)>,
- constants: Vec,
- locals: Vec,
- ) -> 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 {
- &self.constants
- }
-
- pub fn instructions(&self) -> &Vec<(Instruction, Type, Span)> {
- &self.instructions
- }
-
- pub fn locals(&self) -> &Vec {
- &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,
- }
- }
-}
diff --git a/dust-lang/src/chunk/disassembler.rs b/dust-lang/src/chunk/disassembler.rs
new file mode 100644
index 0000000..ac0b62a
--- /dev/null
+++ b/dust-lang/src/chunk/disassembler.rs
@@ -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::>().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)
+ }
+}
diff --git a/dust-lang/src/chunk/local.rs b/dust-lang/src/chunk/local.rs
new file mode 100644
index 0000000..67f9d68
--- /dev/null
+++ b/dust-lang/src/chunk/local.rs
@@ -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,
+ }
+ }
+}
diff --git a/dust-lang/src/chunk/mod.rs b/dust-lang/src/chunk/mod.rs
new file mode 100644
index 0000000..07c7eae
--- /dev/null
+++ b/dust-lang/src/chunk/mod.rs
@@ -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,
+ pub(crate) r#type: FunctionType,
+
+ pub(crate) instructions: Vec,
+ pub(crate) positions: Vec,
+ pub(crate) constants: Vec,
+ pub(crate) locals: Vec,
+ pub(crate) prototypes: Vec,
+
+ pub(crate) register_count: usize,
+ pub(crate) prototype_index: u8,
+}
+
+impl Chunk {
+ #[cfg(any(test, debug_assertions))]
+ pub fn with_data(
+ name: Option,
+ r#type: FunctionType,
+ instructions: impl Into>,
+ positions: impl Into>,
+ constants: impl Into>,
+ locals: impl Into>,
+ prototypes: Vec,
+ ) -> 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
+ }
+}
diff --git a/dust-lang/src/scope.rs b/dust-lang/src/chunk/scope.rs
similarity index 96%
rename from dust-lang/src/scope.rs
rename to dust-lang/src/chunk/scope.rs
index 4e84988..be1e061 100644
--- a/dust-lang/src/scope.rs
+++ b/dust-lang/src/chunk/scope.rs
@@ -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)
}
}
diff --git a/dust-lang/src/compiler.rs b/dust-lang/src/compiler.rs
deleted file mode 100644
index 58f16eb..0000000
--- a/dust-lang/src/compiler.rs
+++ /dev/null
@@ -1,2163 +0,0 @@
-//! Compilation tools and errors
-//!
-//! This module provides two compilation options:
-//! - [`compile`] borrows a string and returns a chunk, handling the entire compilation process and
-//! turning any resulting [`ComplileError`] into a [`DustError`].
-//! - [`Compiler`] uses a lexer to get tokens and assembles a chunk.
-use std::{
- fmt::{self, Display, Formatter},
- mem::replace,
- num::{ParseFloatError, ParseIntError},
- vec,
-};
-
-use colored::Colorize;
-
-use crate::{
- instruction::{
- Call, CallNative, Close, DefineLocal, GetLocal, Jump, LoadBoolean, LoadConstant, LoadList,
- LoadSelf, Move, Negate, Not, Return, SetLocal, Test,
- },
- optimize_control_flow, optimize_set_local,
- value::ConcreteValue,
- AnnotatedError, Argument, Chunk, Destination, DustError, FunctionType, Instruction, LexError,
- Lexer, Local, NativeFunction, Operation, Scope, Span, Token, TokenKind, TokenOwned, Type,
- TypeConflict,
-};
-
-/// Compiles the input and returns a chunk.
-///
-/// # Example
-///
-/// ```
-/// # use dust_lang::compile;
-/// let source = "40 + 2 == 42";
-/// let chunk = compile(source).unwrap();
-///
-/// assert_eq!(chunk.len(), 6);
-/// ```
-pub fn compile(source: &str) -> Result {
- let lexer = Lexer::new(source);
- let mut compiler =
- Compiler::new(lexer).map_err(|error| DustError::Compile { error, source })?;
-
- compiler
- .compile()
- .map_err(|error| DustError::Compile { error, source })?;
-
- let chunk = compiler.finish(None, None);
-
- Ok(chunk)
-}
-
-/// Tool for compiling the input a token at a time while assembling a chunk.
-///
-/// See the [`compile`] function an example of how to create and use a Compiler.
-#[derive(Debug)]
-pub struct Compiler<'src> {
- self_name: Option,
- instructions: Vec<(Instruction, Type, Span)>,
- constants: Vec,
- locals: Vec,
-
- lexer: Lexer<'src>,
-
- current_token: Token<'src>,
- current_position: Span,
- previous_token: Token<'src>,
- previous_position: Span,
-
- return_type: Option,
- minimum_register: u16,
- block_index: u8,
- current_scope: Scope,
-}
-
-impl<'src> Compiler<'src> {
- pub fn new(mut lexer: Lexer<'src>) -> Result {
- let (current_token, current_position) = lexer.next_token()?;
-
- log::info!(
- "Begin chunk with {} at {}",
- current_token.to_string().bold(),
- current_position.to_string()
- );
-
- Ok(Compiler {
- self_name: None,
- instructions: Vec::new(),
- constants: Vec::new(),
- locals: Vec::new(),
- lexer,
- current_token,
- current_position,
- previous_token: Token::Eof,
- previous_position: Span(0, 0),
- return_type: None,
- minimum_register: 0,
- block_index: 0,
- current_scope: Scope::default(),
- })
- }
-
- pub fn finish(
- self,
- type_parameters: Option>,
- value_parameters: Option>,
- ) -> Chunk {
- log::info!("End chunk");
-
- let r#type = FunctionType {
- type_parameters,
- value_parameters,
- return_type: Box::new(self.return_type.unwrap_or(Type::None)),
- };
-
- Chunk::with_data(
- self.self_name,
- r#type,
- self.instructions,
- self.constants,
- self.locals,
- )
- }
-
- pub fn compile(&mut self) -> Result<(), CompileError> {
- loop {
- self.parse(Precedence::None)?;
-
- if self.is_eof() || self.allow(Token::RightBrace)? {
- self.parse_implicit_return()?;
-
- break;
- }
- }
-
- Ok(())
- }
-
- fn is_eof(&self) -> bool {
- matches!(self.current_token, Token::Eof)
- }
-
- fn next_register(&self) -> u16 {
- self.instructions
- .iter()
- .rev()
- .find_map(|(instruction, _, _)| {
- if instruction.yields_value() {
- Some(instruction.a() + 1)
- } else {
- None
- }
- })
- .unwrap_or(self.minimum_register)
- }
-
- fn advance(&mut self) -> Result<(), CompileError> {
- if self.is_eof() {
- return Ok(());
- }
-
- let (new_token, position) = self.lexer.next_token()?;
-
- log::info!(
- "Parsing {} at {}",
- new_token.to_string().bold(),
- position.to_string()
- );
-
- self.previous_token = replace(&mut self.current_token, new_token);
- self.previous_position = replace(&mut self.current_position, position);
-
- Ok(())
- }
-
- fn get_local(&self, index: u16) -> Result<&Local, CompileError> {
- self.locals
- .get(index as usize)
- .ok_or(CompileError::UndeclaredVariable {
- identifier: format!("#{}", index),
- position: self.current_position,
- })
- }
-
- fn get_local_index(&self, identifier_text: &str) -> Result {
- self.locals
- .iter()
- .enumerate()
- .rev()
- .find_map(|(index, local)| {
- let constant = self.constants.get(local.identifier_index as usize)?;
- let identifier = if let ConcreteValue::String(identifier) = constant {
- identifier
- } else {
- return None;
- };
-
- if identifier == identifier_text {
- Some(index as u16)
- } else {
- None
- }
- })
- .ok_or(CompileError::UndeclaredVariable {
- identifier: identifier_text.to_string(),
- position: self.current_position,
- })
- }
-
- fn declare_local(
- &mut self,
- identifier: &str,
- r#type: Type,
- is_mutable: bool,
- scope: Scope,
- ) -> (u16, u16) {
- log::info!("Declaring local {identifier}");
-
- let identifier = ConcreteValue::string(identifier);
- let identifier_index = self.push_or_get_constant(identifier);
- let local_index = self.locals.len() as u16;
-
- self.locals
- .push(Local::new(identifier_index, r#type, is_mutable, scope));
-
- (local_index, identifier_index)
- }
-
- fn get_identifier(&self, local_index: u16) -> Option {
- self.locals.get(local_index as usize).and_then(|local| {
- self.constants
- .get(local.identifier_index as usize)
- .map(|value| value.to_string())
- })
- }
-
- fn push_or_get_constant(&mut self, value: ConcreteValue) -> u16 {
- if let Some(index) = self
- .constants
- .iter()
- .position(|constant| constant == &value)
- {
- index as u16
- } else {
- let index = self.constants.len() as u16;
-
- self.constants.push(value);
-
- index
- }
- }
-
- fn allow(&mut self, allowed: Token) -> Result {
- if self.current_token == allowed {
- self.advance()?;
-
- Ok(true)
- } else {
- Ok(false)
- }
- }
-
- fn expect(&mut self, expected: Token) -> Result<(), CompileError> {
- if self.current_token == expected {
- self.advance()
- } else {
- Err(CompileError::ExpectedToken {
- expected: expected.kind(),
- found: self.current_token.to_owned(),
- position: self.current_position,
- })
- }
- }
-
- fn pop_last_instruction(&mut self) -> Result<(Instruction, Type, Span), CompileError> {
- self.instructions
- .pop()
- .ok_or_else(|| CompileError::ExpectedExpression {
- found: self.previous_token.to_owned(),
- position: self.previous_position,
- })
- }
-
- fn get_last_operations(&self) -> Option<[Operation; COUNT]> {
- let mut n_operations = [Operation::Return; COUNT];
-
- for (nth, operation) in n_operations.iter_mut().rev().zip(
- self.instructions
- .iter()
- .rev()
- .map(|(instruction, _, _)| instruction.operation()),
- ) {
- *nth = operation;
- }
-
- Some(n_operations)
- }
-
- fn get_last_jumpable_mut_between(
- &mut self,
- minimum: usize,
- maximum: usize,
- ) -> Option<&mut Instruction> {
- self.instructions
- .iter_mut()
- .rev()
- .skip(minimum)
- .take(maximum)
- .find_map(|(instruction, _, _)| {
- if let Operation::LoadBoolean | Operation::LoadConstant = instruction.operation() {
- Some(instruction)
- } else {
- None
- }
- })
- }
-
- fn get_last_instruction_type(&self) -> Type {
- self.instructions
- .last()
- .map(|(_, r#type, _)| r#type.clone())
- .unwrap_or(Type::None)
- }
-
- fn get_register_type(&self, register_index: u16) -> Result {
- for (instruction, r#type, _) in &self.instructions {
- if instruction.a() == register_index {
- if let Operation::LoadList = instruction.operation() {
- let LoadList { start_register, .. } = LoadList::from(instruction);
- let item_type = self.get_register_type(start_register)?;
-
- return Ok(Type::List(Box::new(item_type)));
- }
-
- if let Operation::LoadSelf = instruction.operation() {
- return Ok(Type::SelfChunk);
- }
-
- if instruction.yields_value() {
- return Ok(r#type.clone());
- }
- }
- }
-
- Err(CompileError::CannotResolveRegisterType {
- register_index: register_index as usize,
- position: self.current_position,
- })
- }
-
- /// Updates [Self::return_type] with the given [Type].
- ///
- /// If [Self::return_type] is already set, it will check if the given [Type] is compatible with
- /// it and set it to the least restrictive of the two.
- fn update_return_type(&mut self, new_return_type: Type) -> Result<(), CompileError> {
- if let Some(return_type) = &self.return_type {
- return_type.check(&new_return_type).map_err(|conflict| {
- CompileError::ReturnTypeConflict {
- conflict,
- position: self.current_position,
- }
- })?;
-
- if *return_type != Type::Any {
- self.return_type = Some(new_return_type);
- };
- } else {
- self.return_type = Some(new_return_type);
- }
-
- Ok(())
- }
-
- fn emit_instruction(&mut self, instruction: Instruction, r#type: Type, position: Span) {
- log::debug!(
- "Emitting {} at {}",
- instruction.operation().to_string().bold(),
- position.to_string()
- );
-
- self.instructions.push((instruction, r#type, position));
- }
-
- fn emit_constant(
- &mut self,
- constant: ConcreteValue,
- position: Span,
- ) -> Result<(), CompileError> {
- let r#type = constant.r#type();
- let constant_index = self.push_or_get_constant(constant);
- let destination = Destination::Register(self.next_register());
- let instruction = Instruction::from(LoadConstant {
- destination,
- constant_index,
- jump_next: false,
- });
-
- self.emit_instruction(instruction, r#type, position);
-
- Ok(())
- }
-
- fn parse_boolean(&mut self) -> Result<(), CompileError> {
- let position = self.current_position;
-
- if let Token::Boolean(text) = self.current_token {
- self.advance()?;
-
- let boolean = text.parse::().unwrap();
- let destination = Destination::Register(self.next_register());
- let instruction = Instruction::from(LoadBoolean {
- destination,
- value: boolean,
- jump_next: false,
- });
-
- self.emit_instruction(instruction, Type::Boolean, position);
-
- Ok(())
- } else {
- Err(CompileError::ExpectedToken {
- expected: TokenKind::Boolean,
- found: self.current_token.to_owned(),
- position,
- })
- }
- }
-
- fn parse_byte(&mut self) -> Result<(), CompileError> {
- let position = self.current_position;
-
- if let Token::Byte(text) = self.current_token {
- self.advance()?;
-
- let byte = u8::from_str_radix(&text[2..], 16)
- .map_err(|error| CompileError::ParseIntError { error, position })?;
- let value = ConcreteValue::Byte(byte);
-
- self.emit_constant(value, position)?;
-
- Ok(())
- } else {
- Err(CompileError::ExpectedToken {
- expected: TokenKind::Byte,
- found: self.current_token.to_owned(),
- position,
- })
- }
- }
-
- fn parse_character(&mut self) -> Result<(), CompileError> {
- let position = self.current_position;
-
- if let Token::Character(character) = self.current_token {
- self.advance()?;
-
- let value = ConcreteValue::Character(character);
-
- self.emit_constant(value, position)?;
-
- Ok(())
- } else {
- Err(CompileError::ExpectedToken {
- expected: TokenKind::Character,
- found: self.current_token.to_owned(),
- position,
- })
- }
- }
-
- fn parse_float(&mut self) -> Result<(), CompileError> {
- let position = self.current_position;
-
- if let Token::Float(text) = self.current_token {
- self.advance()?;
-
- let float = text
- .parse::()
- .map_err(|error| CompileError::ParseFloatError {
- error,
- position: self.previous_position,
- })?;
- let value = ConcreteValue::Float(float);
-
- self.emit_constant(value, position)?;
-
- Ok(())
- } else {
- Err(CompileError::ExpectedToken {
- expected: TokenKind::Float,
- found: self.current_token.to_owned(),
- position,
- })
- }
- }
-
- fn parse_integer(&mut self) -> Result<(), CompileError> {
- let position = self.current_position;
-
- if let Token::Integer(text) = self.current_token {
- self.advance()?;
-
- let integer = text
- .parse::()
- .map_err(|error| CompileError::ParseIntError {
- error,
- position: self.previous_position,
- })?;
- let value = ConcreteValue::Integer(integer);
-
- self.emit_constant(value, position)?;
-
- Ok(())
- } else {
- Err(CompileError::ExpectedToken {
- expected: TokenKind::Integer,
- found: self.current_token.to_owned(),
- position,
- })
- }
- }
-
- fn parse_string(&mut self) -> Result<(), CompileError> {
- let position = self.current_position;
-
- if let Token::String(text) = self.current_token {
- self.advance()?;
-
- let value = ConcreteValue::string(text);
-
- self.emit_constant(value, position)?;
-
- Ok(())
- } else {
- Err(CompileError::ExpectedToken {
- expected: TokenKind::String,
- found: self.current_token.to_owned(),
- position,
- })
- }
- }
-
- fn parse_grouped(&mut self) -> Result<(), CompileError> {
- self.allow(Token::LeftParenthesis)?;
- self.parse_expression()?;
- self.expect(Token::RightParenthesis)?;
-
- Ok(())
- }
-
- fn parse_unary(&mut self) -> Result<(), CompileError> {
- let operator = self.current_token;
- let operator_position = self.current_position;
-
- self.advance()?;
- self.parse_expression()?;
-
- let (previous_instruction, previous_type, previous_position) =
- self.pop_last_instruction()?;
- let (argument, push_back) = self.handle_binary_argument(&previous_instruction)?;
-
- if push_back {
- self.instructions.push((
- previous_instruction,
- previous_type.clone(),
- previous_position,
- ))
- }
-
- let destination = Destination::Register(self.next_register());
- let instruction = match operator.kind() {
- TokenKind::Bang => Instruction::from(Not {
- destination,
- argument,
- }),
- TokenKind::Minus => Instruction::from(Negate {
- destination,
- argument,
- }),
- _ => {
- return Err(CompileError::ExpectedTokenMultiple {
- expected: &[TokenKind::Bang, TokenKind::Minus],
- found: operator.to_owned(),
- position: operator_position,
- })
- }
- };
-
- self.emit_instruction(instruction, previous_type, operator_position);
-
- Ok(())
- }
-
- fn handle_binary_argument(
- &mut self,
- instruction: &Instruction,
- ) -> Result<(Argument, bool), CompileError> {
- let argument = instruction.destination_as_argument().ok_or_else(|| {
- CompileError::ExpectedExpression {
- found: self.previous_token.to_owned(),
- position: self.previous_position,
- }
- })?;
- let push_back = matches!(argument, Argument::Register(_));
-
- Ok((argument, push_back))
- }
-
- fn parse_math_binary(&mut self) -> Result<(), CompileError> {
- let (left_instruction, left_type, left_position) =
- self.instructions
- .pop()
- .ok_or_else(|| CompileError::ExpectedExpression {
- found: self.previous_token.to_owned(),
- position: self.previous_position,
- })?;
- let (left, push_back_left) = self.handle_binary_argument(&left_instruction)?;
- let left_is_mutable_local = if let Argument::Local(local_index) = left {
- self.get_local(local_index)?.is_mutable
- } else {
- false
- };
- let operator = self.current_token;
- let operator_position = self.current_position;
- let rule = ParseRule::from(&operator);
- let is_assignment = matches!(
- operator,
- Token::PlusEqual | Token::MinusEqual | Token::StarEqual | Token::SlashEqual
- );
- let r#type = if is_assignment {
- Type::None
- } else {
- left_type.clone()
- };
-
- if is_assignment && !left_is_mutable_local {
- return Err(CompileError::ExpectedMutableVariable {
- found: self.previous_token.to_owned(),
- position: left_position,
- });
- }
-
- if push_back_left {
- self.instructions
- .push((left_instruction, left_type, left_position));
- }
-
- self.advance()?;
- self.parse_sub_expression(&rule.precedence)?;
-
- let (right_instruction, right_type, right_position) = self.pop_last_instruction()?;
- let (right, push_back_right) = self.handle_binary_argument(&right_instruction)?;
-
- if push_back_right {
- self.instructions
- .push((right_instruction, right_type, right_position));
- }
-
- let destination = if is_assignment {
- match left {
- Argument::Register(register) => Destination::Register(register),
- Argument::Local(local_index) => Destination::Local(local_index),
- Argument::Constant(_) => Destination::Register(self.next_register()),
- }
- } else {
- Destination::Register(self.next_register())
- };
- let instruction = match operator {
- Token::Plus | Token::PlusEqual => Instruction::add(destination, left, right),
- Token::Minus | Token::MinusEqual => Instruction::subtract(destination, left, right),
- Token::Star | Token::StarEqual => Instruction::multiply(destination, left, right),
- Token::Slash | Token::SlashEqual => Instruction::divide(destination, left, right),
- Token::Percent | Token::PercentEqual => Instruction::modulo(destination, left, right),
- _ => {
- return Err(CompileError::ExpectedTokenMultiple {
- expected: &[
- TokenKind::Plus,
- TokenKind::PlusEqual,
- TokenKind::Minus,
- TokenKind::MinusEqual,
- TokenKind::Star,
- TokenKind::StarEqual,
- TokenKind::Slash,
- TokenKind::SlashEqual,
- TokenKind::Percent,
- TokenKind::PercentEqual,
- ],
- found: operator.to_owned(),
- position: operator_position,
- })
- }
- };
-
- self.emit_instruction(instruction, r#type, operator_position);
-
- Ok(())
- }
-
- fn parse_comparison_binary(&mut self) -> Result<(), CompileError> {
- if let Some([Operation::Equal | Operation::Less | Operation::LessEqual, _, _]) =
- self.get_last_operations()
- {
- return Err(CompileError::CannotChainComparison {
- position: self.current_position,
- });
- }
-
- let (left_instruction, left_type, left_position) =
- self.instructions
- .pop()
- .ok_or_else(|| CompileError::ExpectedExpression {
- found: self.previous_token.to_owned(),
- position: self.previous_position,
- })?;
- let (left, push_back_left) = self.handle_binary_argument(&left_instruction)?;
- let operator = self.current_token;
- let operator_position = self.current_position;
- let rule = ParseRule::from(&operator);
-
- if push_back_left {
- self.instructions
- .push((left_instruction, left_type, left_position));
- }
-
- self.advance()?;
- self.parse_sub_expression(&rule.precedence)?;
-
- let (right_instruction, right_type, right_position) =
- self.instructions
- .pop()
- .ok_or_else(|| CompileError::ExpectedExpression {
- found: self.previous_token.to_owned(),
- position: self.previous_position,
- })?;
- let (right, push_back_right) = self.handle_binary_argument(&right_instruction)?;
-
- if push_back_right {
- self.instructions
- .push((right_instruction, right_type, right_position));
- }
-
- let comparison = match operator {
- Token::DoubleEqual => Instruction::equal(true, left, right),
- Token::BangEqual => Instruction::equal(false, left, right),
- Token::Less => Instruction::less(true, left, right),
- Token::LessEqual => Instruction::less_equal(true, left, right),
- Token::Greater => Instruction::less_equal(false, left, right),
- Token::GreaterEqual => Instruction::less(false, left, right),
- _ => {
- return Err(CompileError::ExpectedTokenMultiple {
- expected: &[
- TokenKind::DoubleEqual,
- TokenKind::BangEqual,
- TokenKind::Less,
- TokenKind::LessEqual,
- TokenKind::Greater,
- TokenKind::GreaterEqual,
- ],
- found: operator.to_owned(),
- position: operator_position,
- })
- }
- };
- let destination = Destination::Register(self.next_register());
- let jump = Instruction::from(Jump {
- offset: 1,
- is_positive: true,
- });
- let load_true = Instruction::from(LoadBoolean {
- destination,
- value: true,
- jump_next: true,
- });
- let load_false = Instruction::from(LoadBoolean {
- destination,
- value: false,
- jump_next: false,
- });
-
- self.emit_instruction(comparison, Type::None, operator_position);
- self.emit_instruction(jump, Type::None, operator_position);
- self.emit_instruction(load_true, Type::Boolean, operator_position);
- self.emit_instruction(load_false, Type::Boolean, operator_position);
-
- Ok(())
- }
-
- fn parse_logical_binary(&mut self) -> Result<(), CompileError> {
- let (left_instruction, left_type, left_position) = self.pop_last_instruction()?;
-
- if !left_instruction.yields_value() {
- return Err(CompileError::ExpectedExpression {
- found: self.previous_token.to_owned(),
- position: self.previous_position,
- });
- }
-
- if let Some([Operation::Test, Operation::Jump]) = self.get_last_operations() {}
-
- let (argument, push_back) = self.handle_binary_argument(&left_instruction)?;
- let is_local = matches!(argument, Argument::Local(_));
-
- if push_back || is_local {
- self.instructions
- .push((left_instruction, left_type.clone(), left_position));
- }
-
- let operator = self.current_token;
- let operator_position = self.current_position;
- let rule = ParseRule::from(&operator);
- let test_boolean = match operator {
- Token::DoubleAmpersand => true,
- Token::DoublePipe => false,
- _ => {
- return Err(CompileError::ExpectedTokenMultiple {
- expected: &[TokenKind::DoubleAmpersand, TokenKind::DoublePipe],
- found: operator.to_owned(),
- position: operator_position,
- })
- }
- };
- let test = Instruction::from(Test {
- argument,
- test_value: test_boolean,
- });
- let jump = Instruction::from(Jump {
- offset: 1,
- is_positive: true,
- });
-
- self.advance()?;
- self.emit_instruction(test, Type::None, operator_position);
- self.emit_instruction(jump, Type::None, operator_position);
- self.parse_sub_expression(&rule.precedence)?;
-
- Ok(())
- }
-
- fn parse_variable(&mut self) -> Result<(), CompileError> {
- let start_position = self.current_position;
- let identifier = if let Token::Identifier(text) = self.current_token {
- self.advance()?;
-
- text
- } else {
- return Err(CompileError::ExpectedToken {
- expected: TokenKind::Identifier,
- found: self.current_token.to_owned(),
- position: start_position,
- });
- };
- let local_index = if let Ok(local_index) = self.get_local_index(identifier) {
- local_index
- } else if let Some(native_function) = NativeFunction::from_str(identifier) {
- return self.parse_native_call(native_function);
- } else if self.self_name.as_deref() == Some(identifier) {
- let destination = Destination::Register(self.next_register());
- let load_self = Instruction::from(LoadSelf { destination });
-
- self.emit_instruction(load_self, Type::SelfChunk, start_position);
-
- return Ok(());
- } else {
- return Err(CompileError::UndeclaredVariable {
- identifier: identifier.to_string(),
- position: start_position,
- });
- };
-
- let local = self.get_local(local_index)?;
- let is_mutable = local.is_mutable;
-
- if !self.current_scope.contains(&local.scope) {
- return Err(CompileError::VariableOutOfScope {
- identifier: self.get_identifier(local_index).unwrap(),
- position: start_position,
- variable_scope: local.scope,
- access_scope: self.current_scope,
- });
- }
-
- if self.allow(Token::Equal)? {
- if !is_mutable {
- return Err(CompileError::CannotMutateImmutableVariable {
- identifier: self.get_identifier(local_index).unwrap(),
- position: start_position,
- });
- }
-
- self.parse_expression()?;
-
- let register = self.next_register() - 1;
- let set_local = Instruction::from(SetLocal {
- register,
- local_index,
- });
-
- self.emit_instruction(set_local, Type::None, start_position);
- optimize_set_local(&mut self.instructions);
-
- return Ok(());
- }
-
- let destination = Destination::Register(self.next_register());
- let get_local = Instruction::from(GetLocal {
- destination,
- local_index,
- });
- let r#type = self.get_local(local_index)?.r#type.clone();
-
- self.emit_instruction(get_local, r#type, self.previous_position);
-
- Ok(())
- }
-
- fn parse_type_from(&mut self, token: Token, position: Span) -> Result {
- match token {
- Token::Bool => Ok(Type::Boolean),
- Token::FloatKeyword => Ok(Type::Float),
- Token::Int => Ok(Type::Integer),
- Token::Str => Ok(Type::String),
- _ => Err(CompileError::ExpectedTokenMultiple {
- expected: &[
- TokenKind::Bool,
- TokenKind::FloatKeyword,
- TokenKind::Int,
- TokenKind::Str,
- ],
- found: self.current_token.to_owned(),
- position,
- }),
- }
- }
-
- fn parse_block(&mut self) -> Result<(), CompileError> {
- self.advance()?;
-
- let starting_block = self.current_scope.block_index;
-
- self.block_index += 1;
- self.current_scope.begin(self.block_index);
-
- while !self.allow(Token::RightBrace)? && !self.is_eof() {
- self.parse(Precedence::None)?;
- }
-
- self.current_scope.end(starting_block);
-
- Ok(())
- }
-
- fn parse_list(&mut self) -> Result<(), CompileError> {
- let start = self.current_position.0;
-
- self.advance()?;
-
- let start_register = self.next_register();
- let mut item_type = Type::Any;
-
- while !self.allow(Token::RightBracket)? && !self.is_eof() {
- let expected_register = self.next_register();
-
- self.parse_expression()?;
-
- let actual_register = self.next_register() - 1;
-
- if item_type == Type::Any {
- item_type = self.get_last_instruction_type();
- }
-
- if expected_register < actual_register {
- let close = Instruction::from(Close {
- from: expected_register,
- to: actual_register,
- });
-
- self.emit_instruction(close, Type::None, self.current_position);
- }
-
- self.allow(Token::Comma)?;
- }
-
- let destination = Destination::Register(self.next_register());
- let end = self.current_position.1;
- let load_list = Instruction::from(LoadList {
- destination,
- start_register,
- });
-
- self.emit_instruction(load_list, Type::List(Box::new(item_type)), Span(start, end));
-
- Ok(())
- }
-
- fn parse_if(&mut self) -> Result<(), CompileError> {
- self.advance()?;
- self.parse_expression()?;
-
- if matches!(
- self.get_last_operations(),
- Some([
- Operation::Equal | Operation::Less | Operation::LessEqual,
- Operation::Jump,
- Operation::LoadBoolean,
- Operation::LoadBoolean,
- ])
- ) {
- self.instructions.pop();
- self.instructions.pop();
- self.instructions.pop();
- } else if let Some((instruction, _, _)) = self.instructions.last() {
- let test_register = instruction.a();
- let test = Instruction::from(Test {
- argument: Argument::Register(test_register),
- test_value: true,
- });
-
- self.emit_instruction(test, Type::None, self.current_position)
- }
-
- let if_block_start = self.instructions.len();
- let if_block_start_position = self.current_position;
-
- if let Token::LeftBrace = self.current_token {
- self.parse_block()?;
- } else {
- return Err(CompileError::ExpectedToken {
- expected: TokenKind::LeftBrace,
- found: self.current_token.to_owned(),
- position: self.current_position,
- });
- }
-
- let if_block_end = self.instructions.len();
- let mut if_block_distance = (if_block_end - if_block_start) as u16;
- let if_block_type = self.get_last_instruction_type();
- let if_last_register = self.next_register().saturating_sub(1);
-
- if let Token::Else = self.current_token {
- self.advance()?;
-
- if let Token::LeftBrace = self.current_token {
- self.parse_block()?;
- } else {
- return Err(CompileError::ExpectedTokenMultiple {
- expected: &[TokenKind::If, TokenKind::LeftBrace],
- found: self.current_token.to_owned(),
- position: self.current_position,
- });
- }
-
- true
- } else if if_block_type != Type::None {
- return Err(CompileError::IfMissingElse {
- position: Span(if_block_start_position.0, self.current_position.1),
- });
- } else {
- false
- };
-
- let else_block_end = self.instructions.len();
- let else_block_distance = (else_block_end - if_block_end) as u16;
- let else_block_type = self.get_last_instruction_type();
-
- if let Err(conflict) = if_block_type.check(&else_block_type) {
- return Err(CompileError::IfElseBranchMismatch {
- conflict,
- position: Span(if_block_start_position.0, self.current_position.1),
- });
- }
-
- match else_block_distance {
- 0 => {}
- 1 => {
- if let Some(skippable) =
- self.get_last_jumpable_mut_between(1, if_block_distance as usize)
- {
- skippable.set_c_to_boolean(true);
- } else {
- if_block_distance += 1;
- let jump = Instruction::from(Jump {
- offset: else_block_distance,
- is_positive: true,
- });
-
- self.instructions
- .insert(if_block_end, (jump, Type::None, self.current_position));
- }
- }
- 2.. => {
- if_block_distance += 1;
- let jump = Instruction::from(Jump {
- offset: else_block_distance,
- is_positive: true,
- });
-
- self.instructions
- .insert(if_block_end, (jump, Type::None, self.current_position));
- }
- }
-
- let jump = Instruction::from(Jump {
- offset: if_block_distance,
- is_positive: true,
- });
-
- self.instructions
- .insert(if_block_start, (jump, Type::None, if_block_start_position));
-
- if self.instructions.len() >= 4 {
- optimize_control_flow(&mut self.instructions);
- }
-
- let else_last_register = self.next_register().saturating_sub(1);
- let r#move = Instruction::from(Move {
- from: else_last_register,
- to: if_last_register,
- });
-
- if if_last_register < else_last_register {
- self.emit_instruction(r#move, Type::None, self.current_position);
- }
-
- Ok(())
- }
-
- fn parse_while(&mut self) -> Result<(), CompileError> {
- self.advance()?;
-
- let expression_start = self.instructions.len() as u16;
-
- self.parse_expression()?;
-
- if matches!(
- self.get_last_operations(),
- Some([
- Operation::Equal | Operation::Less | Operation::LessEqual,
- Operation::Jump,
- Operation::LoadBoolean,
- Operation::LoadBoolean,
- ],)
- ) {
- self.instructions.pop();
- self.instructions.pop();
- self.instructions.pop();
- }
-
- let block_start = self.instructions.len();
-
- self.parse_block()?;
-
- let block_end = self.instructions.len() as u16;
- let jump_distance = block_end - block_start as u16 + 1;
- let jump = Instruction::from(Jump {
- offset: jump_distance,
- is_positive: true,
- });
-
- self.instructions
- .insert(block_start, (jump, Type::None, self.current_position));
-
- let jump_back_distance = block_end - expression_start + 1;
- let jump_back = Instruction::from(Jump {
- offset: jump_back_distance,
- is_positive: false,
- });
-
- self.emit_instruction(jump_back, Type::None, self.current_position);
-
- Ok(())
- }
-
- fn parse_native_call(&mut self, function: NativeFunction) -> Result<(), CompileError> {
- let start = self.previous_position.0;
- let start_register = self.next_register();
-
- self.expect(Token::LeftParenthesis)?;
-
- while !self.allow(Token::RightParenthesis)? {
- let expected_register = self.next_register();
-
- self.parse_expression()?;
-
- let actual_register = self.next_register() - 1;
-
- if expected_register < actual_register {
- let close = Instruction::from(Close {
- from: expected_register,
- to: actual_register,
- });
-
- self.emit_instruction(close, Type::None, self.current_position);
- }
-
- self.allow(Token::Comma)?;
- }
-
- let end = self.previous_position.1;
- let destination = self.next_register();
- let argument_count = destination - start_register;
- let return_type = *function.r#type().return_type;
- let call_native = Instruction::from(CallNative {
- destination: Destination::Register(destination),
- function,
- argument_count,
- });
-
- self.emit_instruction(call_native, return_type, Span(start, end));
-
- Ok(())
- }
-
- fn parse_semicolon(&mut self) -> Result<(), CompileError> {
- self.advance()?;
-
- Ok(())
- }
-
- fn parse_expression(&mut self) -> Result<(), CompileError> {
- self.parse(Precedence::None)?;
-
- let expression_type = self.get_last_instruction_type();
-
- if expression_type == Type::None || self.instructions.is_empty() {
- return Err(CompileError::ExpectedExpression {
- found: self.previous_token.to_owned(),
- position: self.current_position,
- });
- }
-
- Ok(())
- }
-
- fn parse_sub_expression(&mut self, precedence: &Precedence) -> Result<(), CompileError> {
- self.parse(precedence.increment())
- }
-
- fn parse_return_statement(&mut self) -> Result<(), CompileError> {
- let start = self.current_position.0;
-
- self.advance()?;
-
- let should_return_value =
- if matches!(self.current_token, Token::Semicolon | Token::RightBrace) {
- self.update_return_type(Type::None)?;
-
- false
- } else {
- self.parse_expression()?;
-
- let expression_type = self.get_last_instruction_type();
-
- self.update_return_type(expression_type)?;
-
- true
- };
- let end = self.current_position.1;
- let r#return = Instruction::from(Return {
- should_return_value,
- });
-
- self.emit_instruction(r#return, Type::None, Span(start, end));
-
- Ok(())
- }
-
- fn parse_implicit_return(&mut self) -> Result<(), CompileError> {
- if self.allow(Token::Semicolon)? {
- let r#return = Instruction::from(Return {
- should_return_value: false,
- });
-
- self.emit_instruction(r#return, Type::None, self.current_position);
- } else {
- let previous_expression_type = self.get_last_instruction_type();
- let should_return_value = previous_expression_type != Type::None;
- let r#return = Instruction::from(Return {
- should_return_value,
- });
-
- self.update_return_type(previous_expression_type)?;
- self.emit_instruction(r#return, Type::None, self.current_position);
- }
-
- Ok(())
- }
-
- fn parse_let_statement(&mut self) -> Result<(), CompileError> {
- self.advance()?;
-
- let is_mutable = self.allow(Token::Mut)?;
- let position = self.current_position;
- let identifier = if let Token::Identifier(text) = self.current_token {
- self.advance()?;
-
- text
- } else {
- return Err(CompileError::ExpectedToken {
- expected: TokenKind::Identifier,
- found: self.current_token.to_owned(),
- position,
- });
- };
- let explicit_type = if self.allow(Token::Colon)? {
- self.advance()?;
-
- let r#type = self.parse_type_from(self.current_token, self.current_position)?;
-
- Some(r#type)
- } else {
- None
- };
-
- self.expect(Token::Equal)?;
- self.parse_expression()?;
-
- let register = self.next_register() - 1;
- let r#type = if let Some(r#type) = explicit_type {
- r#type
- } else {
- self.get_register_type(register)?
- };
- let (local_index, _) =
- self.declare_local(identifier, r#type, is_mutable, self.current_scope);
- let define_local = Instruction::from(DefineLocal {
- local_index,
- register,
- is_mutable,
- });
-
- self.emit_instruction(define_local, Type::None, position);
-
- Ok(())
- }
-
- fn parse_function(&mut self) -> Result<(), CompileError> {
- let function_start = self.current_position.0;
- let mut function_compiler = Compiler::new(self.lexer)?;
- let identifier_info = if let Token::Identifier(text) = function_compiler.current_token {
- let position = function_compiler.current_position;
-
- function_compiler.advance()?;
-
- function_compiler.self_name = Some(text.to_string());
-
- Some((text, position))
- } else {
- None
- };
-
- function_compiler.expect(Token::LeftParenthesis)?;
-
- let mut value_parameters: Option> = None;
-
- while function_compiler.current_token != Token::RightParenthesis {
- let is_mutable = function_compiler.allow(Token::Mut)?;
- let parameter = if let Token::Identifier(text) = function_compiler.current_token {
- function_compiler.advance()?;
-
- text
- } else {
- return Err(CompileError::ExpectedToken {
- expected: TokenKind::Identifier,
- found: function_compiler.current_token.to_owned(),
- position: function_compiler.current_position,
- });
- };
-
- function_compiler.expect(Token::Colon)?;
-
- let r#type = function_compiler.parse_type_from(
- function_compiler.current_token,
- function_compiler.current_position,
- )?;
-
- function_compiler.advance()?;
-
- let (_, identifier_index) = function_compiler.declare_local(
- parameter,
- r#type.clone(),
- is_mutable,
- function_compiler.current_scope,
- );
-
- if let Some(value_parameters) = value_parameters.as_mut() {
- value_parameters.push((identifier_index, r#type));
- } else {
- value_parameters = Some(vec![(identifier_index, r#type)]);
- };
-
- function_compiler.minimum_register += 1;
-
- function_compiler.allow(Token::Comma)?;
- }
-
- function_compiler.advance()?;
-
- let return_type = if function_compiler.allow(Token::ArrowThin)? {
- let r#type = function_compiler.parse_type_from(
- function_compiler.current_token,
- function_compiler.current_position,
- )?;
-
- function_compiler.advance()?;
-
- Box::new(r#type)
- } else {
- Box::new(Type::None)
- };
-
- function_compiler.return_type = Some((*return_type).clone());
-
- function_compiler.expect(Token::LeftBrace)?;
- function_compiler.compile()?;
-
- self.previous_token = function_compiler.previous_token;
- self.previous_position = function_compiler.previous_position;
- self.current_token = function_compiler.current_token;
- self.current_position = function_compiler.current_position;
-
- let function =
- ConcreteValue::Function(function_compiler.finish(None, value_parameters.clone()));
- let constant_index = self.push_or_get_constant(function);
- let function_end = self.current_position.1;
- let register = self.next_register();
- let function_type = FunctionType {
- type_parameters: None,
- value_parameters,
- return_type,
- };
-
- self.lexer.skip_to(function_end);
-
- if let Some((identifier, position)) = identifier_info {
- let (local_index, _) = self.declare_local(
- identifier,
- Type::Function(function_type.clone()),
- false,
- self.current_scope,
- );
- let load_constant = Instruction::from(LoadConstant {
- destination: Destination::Register(register),
- constant_index,
- jump_next: false,
- });
- let define_local = Instruction::from(DefineLocal {
- local_index,
- register,
- is_mutable: false,
- });
-
- self.emit_instruction(
- load_constant,
- Type::Function(function_type),
- Span(function_start, function_end),
- );
- self.emit_instruction(define_local, Type::None, position);
- } else {
- let load_constant = Instruction::from(LoadConstant {
- destination: Destination::Register(register),
- constant_index,
- jump_next: false,
- });
-
- self.emit_instruction(
- load_constant,
- Type::Function(function_type),
- Span(function_start, function_end),
- );
- }
-
- Ok(())
- }
-
- fn parse_call(&mut self) -> Result<(), CompileError> {
- let (last_instruction, _, _) =
- self.instructions
- .last()
- .ok_or_else(|| CompileError::ExpectedExpression {
- found: self.previous_token.to_owned(),
- position: self.previous_position,
- })?;
-
- if !last_instruction.yields_value() {
- return Err(CompileError::ExpectedExpression {
- found: self.previous_token.to_owned(),
- position: self.previous_position,
- });
- }
-
- let function = last_instruction.destination_as_argument().ok_or_else(|| {
- CompileError::ExpectedExpression {
- found: self.previous_token.to_owned(),
- position: self.previous_position,
- }
- })?;
- let register_type = self.get_register_type(function.index())?;
- let function_return_type = match register_type {
- Type::Function(function_type) => *function_type.return_type,
- Type::SelfChunk => self.return_type.clone().unwrap_or(Type::None),
- _ => {
- return Err(CompileError::ExpectedFunction {
- found: self.previous_token.to_owned(),
- actual_type: register_type,
- position: self.previous_position,
- });
- }
- };
- let start = self.current_position.0;
-
- self.advance()?;
-
- let mut argument_count = 0;
-
- while !self.allow(Token::RightParenthesis)? {
- let expected_register = self.next_register();
-
- self.parse_expression()?;
-
- let actual_register = self.next_register() - 1;
- let registers_to_close = actual_register - expected_register;
-
- if registers_to_close > 0 {
- let close = Instruction::from(Close {
- from: expected_register,
- to: actual_register,
- });
-
- self.emit_instruction(close, Type::None, self.current_position);
- }
-
- argument_count += registers_to_close + 1;
-
- self.allow(Token::Comma)?;
- }
-
- let end = self.current_position.1;
- let register = self.next_register();
- let call = Instruction::from(Call {
- destination: Destination::Register(register),
- function,
- argument_count,
- });
-
- self.emit_instruction(call, function_return_type, Span(start, end));
-
- Ok(())
- }
-
- fn expect_expression(&mut self) -> Result<(), CompileError> {
- Err(CompileError::ExpectedExpression {
- found: self.current_token.to_owned(),
- position: self.current_position,
- })
- }
-
- fn parse(&mut self, precedence: Precedence) -> Result<(), CompileError> {
- if let Some(prefix_parser) = ParseRule::from(&self.current_token).prefix {
- log::debug!(
- "{} is prefix with precedence {precedence}",
- self.current_token.to_string().bold(),
- );
-
- prefix_parser(self)?;
- }
-
- let mut infix_rule = ParseRule::from(&self.current_token);
-
- while precedence <= infix_rule.precedence {
- if let Some(infix_parser) = infix_rule.infix {
- log::debug!(
- "{} is infix with precedence {precedence}",
- self.current_token.to_string().bold(),
- );
-
- if self.current_token == Token::Equal {
- return Err(CompileError::InvalidAssignmentTarget {
- found: self.current_token.to_owned(),
- position: self.current_position,
- });
- }
-
- infix_parser(self)?;
- } else {
- break;
- }
-
- infix_rule = ParseRule::from(&self.current_token);
- }
-
- Ok(())
- }
-}
-
-/// Operator precedence levels.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
-pub enum Precedence {
- None,
- Assignment,
- Conditional,
- LogicalOr,
- LogicalAnd,
- Equality,
- Comparison,
- Term,
- Factor,
- Unary,
- Call,
- Primary,
-}
-
-impl Precedence {
- fn increment(&self) -> Self {
- match self {
- Precedence::None => Precedence::Assignment,
- Precedence::Assignment => Precedence::Conditional,
- Precedence::Conditional => Precedence::LogicalOr,
- Precedence::LogicalOr => Precedence::LogicalAnd,
- Precedence::LogicalAnd => Precedence::Equality,
- Precedence::Equality => 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)
- }
-}
-
-type Parser<'a> = fn(&mut Compiler<'a>) -> Result<(), CompileError>;
-
-/// Rule that defines how to parse a token.
-#[derive(Debug, Clone, Copy)]
-struct ParseRule<'a> {
- pub prefix: Option>,
- pub infix: Option>,
- 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::Equality,
- },
- 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::Equality,
- },
- 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,
- },
- }
- }
-}
-
-/// 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
- CannotChainComparison {
- 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
- 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 AnnotatedError for CompileError {
- fn title() -> &'static str {
- "Compilation Error"
- }
-
- fn description(&self) -> &'static str {
- match self {
- Self::CannotChainComparison { .. } => "Cannot chain comparison operations",
- Self::CannotMutateImmutableVariable { .. } => "Cannot mutate immutable variable",
- Self::CannotResolveRegisterType { .. } => "Cannot resolve register type",
- Self::CannotResolveVariableType { .. } => "Cannot resolve type",
- Self::ConstantIndexOutOfBounds { .. } => "Constant index out of bounds",
- 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 details(&self) -> Option {
- match self {
- Self::CannotMutateImmutableVariable { identifier, .. } => {
- Some(format!("{identifier} is immutable"))
- }
- Self::ExpectedExpression { found, .. } => Some(format!("Found {found}")),
- Self::ExpectedFunction { found, actual_type, .. } => {
- Some(format!("Expected \"{found}\" to be a function but it has type {actual_type}"))
- }
- Self::ExpectedFunctionType { found, .. } => {
- Some(format!("Expected a function type but found {found}"))
- }
- Self::ExpectedToken {
- expected, found, ..
- } => Some(format!("Expected {expected} but found {found}")),
- Self::ExpectedTokenMultiple {
- expected, found, ..
- } => {
- let mut details = String::from("Expected");
-
- for (index, token) in expected.iter().enumerate() {
- details.push_str(&format!(" {token}"));
-
- if index < expected.len() - 2 {
- details.push_str(", ");
- }
-
- if index == expected.len() - 2 {
- details.push_str(" or");
- }
- }
-
- details.push_str(&format!(" but found {found}"));
-
- Some(details)
- }
- Self::ExpectedMutableVariable { found, .. } => Some(format!("Found {found}")),
- Self::IfElseBranchMismatch {
- conflict: TypeConflict { expected, actual },
- ..
- } => Some(
- format!("This if block evaluates to type \"{expected}\" but the else block evaluates to \"{actual}\"")
- ),
- Self::IfMissingElse { .. } => Some(
- "This \"if\" expression evaluates to a value but is missing an else block"
- .to_string(),
- ),
- Self::InvalidAssignmentTarget { found, .. } => {
- Some(format!("Cannot assign to {found}"))
- }
- Self::Lex(error) => error.details(),
- Self::ParseFloatError { error, .. } => Some(error.to_string()),
- Self::ParseIntError { error, .. } => Some(error.to_string()),
- Self::ReturnTypeConflict {
- conflict: TypeConflict { expected, actual },
- ..
- } => Some(format!(
- "Expected return type \"{expected}\" but found \"{actual}\""
- )),
- Self::UndeclaredVariable { identifier, .. } => {
- Some(format!("{identifier} has not been declared"))
- }
- Self::UnexpectedReturn { .. } => None,
- Self::VariableOutOfScope { identifier, .. } => {
- Some(format!("{identifier} is out of scope"))
- }
- _ => None,
- }
- }
-
- fn position(&self) -> Span {
- match self {
- Self::CannotChainComparison { position } => *position,
- Self::CannotMutateImmutableVariable { position, .. } => *position,
- Self::CannotResolveRegisterType { position, .. } => *position,
- Self::CannotResolveVariableType { position, .. } => *position,
- Self::ConstantIndexOutOfBounds { position, .. } => *position,
- Self::ExpectedExpression { position, .. } => *position,
- Self::ExpectedFunction { position, .. } => *position,
- Self::ExpectedFunctionType { position, .. } => *position,
- Self::ExpectedMutableVariable { position, .. } => *position,
- Self::ExpectedToken { position, .. } => *position,
- Self::ExpectedTokenMultiple { position, .. } => *position,
- Self::IfElseBranchMismatch { position, .. } => *position,
- Self::IfMissingElse { position } => *position,
- Self::InstructionIndexOutOfBounds { position, .. } => *position,
- Self::InvalidAssignmentTarget { position, .. } => *position,
- Self::Lex(error) => error.position(),
- Self::ListItemTypeConflict { position, .. } => *position,
- Self::LocalIndexOutOfBounds { position, .. } => *position,
- Self::ParseFloatError { position, .. } => *position,
- Self::ParseIntError { position, .. } => *position,
- Self::ReturnTypeConflict { position, .. } => *position,
- Self::UndeclaredVariable { position, .. } => *position,
- Self::UnexpectedReturn { position } => *position,
- Self::VariableOutOfScope { position, .. } => *position,
- }
- }
-}
-
-impl From for CompileError {
- fn from(error: LexError) -> Self {
- Self::Lex(error)
- }
-}
diff --git a/dust-lang/src/compiler/error.rs b/dust-lang/src/compiler/error.rs
new file mode 100644
index 0000000..63aee4f
--- /dev/null
+++ b/dust-lang/src/compiler/error.rs
@@ -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 for CompileError {
+ fn from(error: LexError) -> Self {
+ Self::Lex(error)
+ }
+}
diff --git a/dust-lang/src/compiler/mod.rs b/dust-lang/src/compiler/mod.rs
new file mode 100644
index 0000000..edbb040
--- /dev/null
+++ b/dust-lang/src/compiler/mod.rs
@@ -0,0 +1,1785 @@
+//! The Dust compiler and its accessories.
+//!
+//! This module provides two compilation options:
+//! - [`compile`] is a simple function that borrows a string and returns a chunk, handling
+//! compilation and turning any resulting error into a [`DustError`], which can easily display a
+//! detailed report. The main chunk will be named "main".
+//! - [`Compiler`] is created with a [`Lexer`] and protentially emits a [`CompileError`] or
+//! [`LexError`] if the input is invalid. Allows passing a name for the main chunk when
+//! [`Compiler::finish`] is called.
+//!
+//! # Errors
+//!
+//! The compiler can return errors due to:
+//! - Lexing errors
+//! - Parsing errors
+//! - Type conflicts
+//!
+//! It is a logic error to call [`Compiler::finish`] on a compiler that has emitted an error and
+//! pass that chunk to the VM. Otherwise, if the compiler gives no errors and the VM encounters a
+//! runtime error, it is the compiler's fault and the error should be fixed here.
+mod error;
+mod optimize;
+mod parse_rule;
+mod type_checks;
+
+pub use error::CompileError;
+use parse_rule::{ParseRule, Precedence};
+use tracing::{Level, debug, info, span};
+use type_checks::{check_math_type, check_math_types};
+
+use std::mem::replace;
+
+use optimize::control_flow_register_consolidation;
+
+use crate::{
+ Argument, Chunk, ConcreteValue, DustError, DustString, FunctionType, Instruction, Lexer, Local,
+ NativeFunction, Operation, Scope, Span, Token, TokenKind, Type, Value,
+ instruction::{CallNative, Close, GetLocal, Jump, LoadList, Negate, Not, Return, SetLocal},
+};
+
+/// Compiles the input and returns a chunk.
+///
+/// # Example
+///
+/// ```
+/// # use dust_lang::compile;
+/// let source = "40 + 2 == 42";
+/// let chunk = compile(source).unwrap();
+///
+/// assert_eq!(chunk.instructions().len(), 3);
+/// ```
+pub fn compile(source: &str) -> Result {
+ let lexer = Lexer::new(source);
+ let mut compiler =
+ Compiler::new(lexer, None, true).map_err(|error| DustError::compile(error, source))?;
+
+ compiler
+ .compile()
+ .map_err(|error| DustError::compile(error, source))?;
+
+ let chunk = compiler.finish();
+
+ Ok(chunk)
+}
+
+/// The Dust compiler assembles a [`Chunk`] for the Dust VM. Any unrecognized symbols, disallowed
+/// syntax or conflicting type usage will result in an error.
+///
+/// See the [`compile`] function an example of how to create and use a Compiler.
+#[derive(Debug)]
+pub struct Compiler<'src> {
+ /// Used to get tokens for the compiler.
+ lexer: Lexer<'src>,
+
+ /// Name of the function being compiled. This is used to identify recursive calls, so it should
+ /// be `None` for the main chunk. The main chunk can still be named by passing a name to
+ /// [`Compiler::finish`], which will override this value.
+ function_name: Option,
+
+ /// Type of the function being compiled. This is assigned to the chunk when [`Compiler::finish`]
+ /// is called.
+ r#type: FunctionType,
+
+ /// Instructions, along with their types and positions, that have been compiled. The
+ /// instructions and positions are assigned to the chunk when [`Compiler::finish`] is called.
+ /// The types are discarded after compilation.
+ instructions: Vec<(Instruction, Type, Span)>,
+
+ /// Constants that have been compiled. These are assigned to the chunk when [`Compiler::finish`]
+ /// is called.
+ constants: Vec,
+
+ /// Block-local variables and their types. The locals are assigned to the chunk when
+ /// [`Compiler::finish`] is called. The types are discarded after compilation.
+ locals: Vec<(Local, Type)>,
+
+ /// Prototypes that have been compiled. These are assigned to the chunk when
+ /// [`Compiler::finish`] is called.
+ prototypes: Vec,
+
+ /// Maximum stack size required by the chunk. This is assigned to the chunk when
+ /// [`Compiler::finish`] is called.
+ stack_size: usize,
+
+ /// The first register index that the compiler should use. This is used to avoid reusing the
+ /// registers that are used for the function's arguments.
+ minimum_register: u8,
+
+ /// Index of the current block. This is used to determine the scope of locals and is incremented
+ /// when a new block is entered.
+ block_index: u8,
+
+ /// The current block scope of the compiler. This is used to test if a variable is in scope.
+ current_scope: Scope,
+
+ /// Index of the Chunk in its parent's prototype list. This is set to 0 for the main chunk but
+ /// that value is never read because the main chunk is not a callable function.
+ prototype_index: u8,
+
+ /// Whether the chunk is the program's main chunk. This is used to prevent recursive calls to
+ /// the main chunk.
+ is_main: bool,
+
+ current_token: Token<'src>,
+ current_position: Span,
+ previous_token: Token<'src>,
+ previous_position: Span,
+}
+
+impl<'src> Compiler<'src> {
+ /// Creates a new compiler.
+ pub fn new(
+ mut lexer: Lexer<'src>,
+ function_name: Option,
+ is_main: bool,
+ ) -> Result {
+ let (current_token, current_position) = lexer.next_token()?;
+
+ Ok(Compiler {
+ function_name,
+ r#type: FunctionType {
+ type_parameters: Vec::with_capacity(0),
+ value_parameters: Vec::with_capacity(0),
+ return_type: Type::None,
+ },
+ instructions: Vec::new(),
+ constants: Vec::new(),
+ locals: Vec::new(),
+ prototypes: Vec::new(),
+ stack_size: 0,
+ lexer,
+ minimum_register: 0,
+ block_index: 0,
+ current_scope: Scope::default(),
+ prototype_index: 0,
+ is_main,
+ current_token,
+ current_position,
+ previous_token: Token::Eof,
+ previous_position: Span(0, 0),
+ })
+ }
+
+ /// Compiles the source (which is in the lexer) while checking for errors and returning a
+ /// [`CompileError`] if any are found. After calling this function, check its return value for
+ /// an error, then call [`Compiler::finish`] to get the compiled chunk.
+ pub fn compile(&mut self) -> Result<(), CompileError> {
+ let span = span!(Level::INFO, "Compile");
+ let _enter = span.enter();
+
+ info!(
+ "Begin chunk with {} at {}",
+ self.current_token.to_string(),
+ self.current_position.to_string()
+ );
+
+ loop {
+ self.parse(Precedence::None)?;
+
+ if matches!(self.current_token, Token::Eof | Token::RightBrace) {
+ if self.get_last_operation() == Some(Operation::RETURN) {
+ break;
+ }
+
+ self.parse_implicit_return()?;
+
+ break;
+ }
+ }
+
+ info!("End chunk");
+
+ Ok(())
+ }
+
+ /// Creates a new chunk with the compiled data, optionally assigning a name to the chunk.
+ ///
+ /// Note for maintainers: Do not give a name when compiling functions, only the main chunk. This
+ /// will allow [`Compiler::function_name`] to be both the name used for recursive calls and the
+ /// name of the function when it is compiled. The name can later be seen in the VM's call stack.
+ pub fn finish(self) -> Chunk {
+ let (instructions, positions): (Vec, Vec) = self
+ .instructions
+ .into_iter()
+ .map(|(instruction, _, position)| (instruction, position))
+ .unzip();
+ let locals = self
+ .locals
+ .into_iter()
+ .map(|(local, _)| local)
+ .collect::>();
+
+ Chunk {
+ name: self.function_name,
+ r#type: self.r#type,
+ instructions,
+ positions,
+ constants: self.constants.to_vec(),
+ locals,
+ prototypes: self.prototypes,
+ register_count: self.stack_size,
+ prototype_index: self.prototype_index,
+ }
+ }
+
+ fn is_eof(&self) -> bool {
+ matches!(self.current_token, Token::Eof)
+ }
+
+ fn next_register(&self) -> u8 {
+ self.instructions
+ .iter()
+ .rev()
+ .find_map(|(instruction, _, _)| {
+ if instruction.yields_value() {
+ Some(instruction.a_field() + 1)
+ } else {
+ None
+ }
+ })
+ .unwrap_or(self.minimum_register)
+ }
+
+ fn advance(&mut self) -> Result<(), CompileError> {
+ if self.is_eof() {
+ return Ok(());
+ }
+
+ let (new_token, position) = self.lexer.next_token()?;
+
+ info!(
+ "Parsing {} at {}",
+ new_token.to_string(),
+ position.to_string()
+ );
+
+ self.previous_token = replace(&mut self.current_token, new_token);
+ self.previous_position = replace(&mut self.current_position, position);
+
+ Ok(())
+ }
+
+ fn get_local(&self, index: u8) -> Result<&(Local, Type), CompileError> {
+ self.locals
+ .get(index as usize)
+ .ok_or(CompileError::UndeclaredVariable {
+ identifier: format!("#{}", index),
+ position: self.current_position,
+ })
+ }
+
+ fn get_local_index(&self, identifier_text: &str) -> Result {
+ self.locals
+ .iter()
+ .enumerate()
+ .rev()
+ .find_map(|(index, (local, _))| {
+ let constant = self.constants.get(local.identifier_index as usize)?;
+ let identifier =
+ if let Value::Concrete(ConcreteValue::String(identifier)) = constant {
+ identifier
+ } else {
+ return None;
+ };
+
+ if identifier == identifier_text {
+ Some(index as u8)
+ } else {
+ None
+ }
+ })
+ .ok_or(CompileError::UndeclaredVariable {
+ identifier: identifier_text.to_string(),
+ position: self.current_position,
+ })
+ }
+
+ fn declare_local(
+ &mut self,
+ identifier: &str,
+ register_index: u8,
+ r#type: Type,
+ is_mutable: bool,
+ scope: Scope,
+ ) -> (u8, u8) {
+ info!("Declaring local {identifier}");
+
+ let identifier = Value::Concrete(ConcreteValue::string(identifier));
+ let identifier_index = self.push_or_get_constant(identifier);
+ let local_index = self.locals.len() as u8;
+
+ self.locals.push((
+ Local::new(identifier_index, register_index, is_mutable, scope),
+ r#type,
+ ));
+
+ (local_index, identifier_index)
+ }
+
+ fn get_identifier(&self, local_index: u8) -> Option {
+ self.locals
+ .get(local_index as usize)
+ .and_then(|(local, _)| {
+ self.constants
+ .get(local.identifier_index as usize)
+ .map(|value| value.to_string())
+ })
+ }
+
+ fn push_or_get_constant(&mut self, value: Value) -> u8 {
+ if let Some(index) = self
+ .constants
+ .iter()
+ .position(|constant| constant == &value)
+ {
+ index as u8
+ } else {
+ let index = self.constants.len() as u8;
+
+ self.constants.push(value);
+
+ index
+ }
+ }
+
+ fn allow(&mut self, allowed: Token) -> Result {
+ if self.current_token == allowed {
+ self.advance()?;
+
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+ }
+
+ fn expect(&mut self, expected: Token) -> Result<(), CompileError> {
+ if self.current_token == expected {
+ self.advance()
+ } else {
+ Err(CompileError::ExpectedToken {
+ expected: expected.kind(),
+ found: self.current_token.to_owned(),
+ position: self.current_position,
+ })
+ }
+ }
+
+ fn get_last_operation(&self) -> Option {
+ self.instructions
+ .last()
+ .map(|(instruction, _, _)| instruction.operation())
+ }
+
+ fn get_last_operations(&self) -> Option<[Operation; COUNT]> {
+ let mut n_operations = [Operation::RETURN; COUNT];
+
+ for (nth, operation) in n_operations.iter_mut().rev().zip(
+ self.instructions
+ .iter()
+ .rev()
+ .map(|(instruction, _, _)| instruction.operation()),
+ ) {
+ *nth = operation;
+ }
+
+ Some(n_operations)
+ }
+
+ fn get_last_instruction_type(&self) -> Type {
+ self.instructions
+ .last()
+ .map(|(_, r#type, _)| r#type.clone())
+ .unwrap_or(Type::None)
+ }
+
+ fn get_register_type(&self, register_index: u8) -> Result {
+ if let Some((_, r#type)) = self
+ .locals
+ .iter()
+ .find(|(local, _)| local.register_index == register_index)
+ {
+ return Ok(r#type.clone());
+ }
+
+ for (instruction, r#type, _) in &self.instructions {
+ if !instruction.yields_value() {
+ continue;
+ }
+
+ let operation = instruction.operation();
+
+ if let Operation::LOAD_LIST = operation {
+ let LoadList { start_register, .. } = LoadList::from(*instruction);
+ let item_type = self.get_register_type(start_register)?;
+
+ return Ok(Type::List(Box::new(item_type)));
+ }
+
+ if let Operation::LOAD_SELF = operation {
+ return Ok(Type::SelfFunction);
+ }
+
+ if instruction.yields_value() {
+ return Ok(r#type.clone());
+ }
+ }
+
+ Err(CompileError::CannotResolveRegisterType {
+ register_index: register_index as usize,
+ position: self.current_position,
+ })
+ }
+
+ /// Updates [`Self::type`] with the given [Type] as `return_type`.
+ ///
+ /// If [`Self::type`] is already set, it will check if the given [Type] is compatible.
+ fn update_return_type(&mut self, new_return_type: Type) -> Result<(), CompileError> {
+ if self.r#type.return_type != Type::None {
+ self.r#type
+ .return_type
+ .check(&new_return_type)
+ .map_err(|conflict| CompileError::ReturnTypeConflict {
+ conflict,
+ position: self.previous_position,
+ })?;
+ }
+
+ self.r#type.return_type = new_return_type;
+
+ Ok(())
+ }
+
+ fn emit_instruction(&mut self, instruction: Instruction, r#type: Type, position: Span) {
+ debug!(
+ "Emitting {} at {}",
+ instruction.operation().to_string(),
+ position.to_string()
+ );
+
+ if instruction.yields_value() {
+ let destination = instruction.a_field() as usize;
+
+ self.stack_size = (destination + 1).max(self.stack_size);
+ }
+
+ self.instructions.push((instruction, r#type, position));
+ }
+
+ fn emit_constant(
+ &mut self,
+ constant: ConcreteValue,
+ position: Span,
+ ) -> Result<(), CompileError> {
+ let r#type = constant.r#type();
+ let constant_index = self.push_or_get_constant(Value::Concrete(constant));
+ let destination = self.next_register();
+ let load_constant = Instruction::load_constant(destination, constant_index, false);
+
+ self.emit_instruction(load_constant, r#type, position);
+
+ Ok(())
+ }
+
+ fn parse_boolean(&mut self) -> Result<(), CompileError> {
+ let position = self.current_position;
+
+ if let Token::Boolean(text) = self.current_token {
+ self.advance()?;
+
+ let boolean = text.parse::().unwrap();
+ let destination = self.next_register();
+ let load_boolean = Instruction::load_boolean(destination, boolean, false);
+
+ self.emit_instruction(load_boolean, Type::Boolean, position);
+
+ Ok(())
+ } else {
+ Err(CompileError::ExpectedToken {
+ expected: TokenKind::Boolean,
+ found: self.current_token.to_owned(),
+ position,
+ })
+ }
+ }
+
+ fn parse_byte(&mut self) -> Result<(), CompileError> {
+ let position = self.current_position;
+
+ if let Token::Byte(text) = self.current_token {
+ self.advance()?;
+
+ let byte = u8::from_str_radix(&text[2..], 16)
+ .map_err(|error| CompileError::ParseIntError { error, position })?;
+ let value = ConcreteValue::Byte(byte);
+
+ self.emit_constant(value, position)?;
+
+ Ok(())
+ } else {
+ Err(CompileError::ExpectedToken {
+ expected: TokenKind::Byte,
+ found: self.current_token.to_owned(),
+ position,
+ })
+ }
+ }
+
+ fn parse_character(&mut self) -> Result<(), CompileError> {
+ let position = self.current_position;
+
+ if let Token::Character(character) = self.current_token {
+ self.advance()?;
+
+ let value = ConcreteValue::Character(character);
+
+ self.emit_constant(value, position)?;
+
+ Ok(())
+ } else {
+ Err(CompileError::ExpectedToken {
+ expected: TokenKind::Character,
+ found: self.current_token.to_owned(),
+ position,
+ })
+ }
+ }
+
+ fn parse_float(&mut self) -> Result<(), CompileError> {
+ let position = self.current_position;
+
+ if let Token::Float(text) = self.current_token {
+ self.advance()?;
+
+ let float = text
+ .parse::()
+ .map_err(|error| CompileError::ParseFloatError {
+ error,
+ position: self.previous_position,
+ })?;
+ let value = ConcreteValue::Float(float);
+
+ self.emit_constant(value, position)?;
+
+ Ok(())
+ } else {
+ Err(CompileError::ExpectedToken {
+ expected: TokenKind::Float,
+ found: self.current_token.to_owned(),
+ position,
+ })
+ }
+ }
+
+ fn parse_integer(&mut self) -> Result<(), CompileError> {
+ let position = self.current_position;
+
+ if let Token::Integer(text) = self.current_token {
+ self.advance()?;
+
+ let mut integer_value = 0_i64;
+
+ for digit in text.chars() {
+ let digit = if let Some(digit) = digit.to_digit(10) {
+ digit as i64
+ } else {
+ continue;
+ };
+
+ integer_value = integer_value * 10 + digit;
+ }
+
+ let value = ConcreteValue::Integer(integer_value);
+
+ self.emit_constant(value, position)?;
+
+ Ok(())
+ } else {
+ Err(CompileError::ExpectedToken {
+ expected: TokenKind::Integer,
+ found: self.current_token.to_owned(),
+ position,
+ })
+ }
+ }
+
+ fn parse_string(&mut self) -> Result<(), CompileError> {
+ let position = self.current_position;
+
+ if let Token::String(text) = self.current_token {
+ self.advance()?;
+
+ let value = ConcreteValue::string(text);
+
+ self.emit_constant(value, position)?;
+
+ Ok(())
+ } else {
+ Err(CompileError::ExpectedToken {
+ expected: TokenKind::String,
+ found: self.current_token.to_owned(),
+ position,
+ })
+ }
+ }
+
+ fn parse_grouped(&mut self) -> Result<(), CompileError> {
+ self.allow(Token::LeftParenthesis)?;
+ self.parse_expression()?;
+ self.expect(Token::RightParenthesis)?;
+
+ Ok(())
+ }
+
+ fn parse_unary(&mut self) -> Result<(), CompileError> {
+ let operator = self.current_token;
+ let operator_position = self.current_position;
+
+ self.advance()?;
+ self.parse_expression()?;
+
+ let (previous_instruction, previous_type, previous_position) =
+ self.instructions.pop().unwrap();
+ let (argument, push_back) = self.handle_binary_argument(&previous_instruction)?;
+
+ if push_back {
+ self.instructions.push((
+ previous_instruction,
+ previous_type.clone(),
+ previous_position,
+ ))
+ }
+
+ let destination = self.next_register();
+ let instruction = match operator.kind() {
+ TokenKind::Bang => Instruction::from(Not {
+ destination,
+ argument,
+ }),
+ TokenKind::Minus => Instruction::from(Negate {
+ destination,
+ argument,
+ }),
+ _ => {
+ return Err(CompileError::ExpectedTokenMultiple {
+ expected: &[TokenKind::Bang, TokenKind::Minus],
+ found: operator.to_owned(),
+ position: operator_position,
+ });
+ }
+ };
+
+ self.emit_instruction(instruction, previous_type, operator_position);
+
+ Ok(())
+ }
+
+ fn handle_binary_argument(
+ &mut self,
+ instruction: &Instruction,
+ ) -> Result<(Argument, bool), CompileError> {
+ let (argument, push_back) = match instruction.operation() {
+ Operation::LOAD_CONSTANT => (Argument::Constant(instruction.b_field()), false),
+ Operation::GET_LOCAL => {
+ let local_index = instruction.b_field();
+ let (local, _) = self.get_local(local_index)?;
+
+ (Argument::Register(local.register_index), false)
+ }
+ Operation::LOAD_BOOLEAN
+ | Operation::LOAD_LIST
+ | Operation::LOAD_SELF
+ | Operation::ADD
+ | Operation::SUBTRACT
+ | Operation::MULTIPLY
+ | Operation::DIVIDE
+ | Operation::MODULO
+ | Operation::EQUAL
+ | Operation::LESS
+ | Operation::LESS_EQUAL
+ | Operation::NEGATE
+ | Operation::NOT
+ | Operation::CALL => (Argument::Register(instruction.a_field()), true),
+ Operation::CALL_NATIVE => {
+ let function = NativeFunction::from(instruction.b_field());
+
+ if function.returns_value() {
+ (Argument::Register(instruction.a_field()), true)
+ } else {
+ return Err(CompileError::ExpectedExpression {
+ found: self.previous_token.to_owned(),
+ position: self.previous_position,
+ });
+ }
+ }
+ _ => {
+ return Err(CompileError::ExpectedExpression {
+ found: self.previous_token.to_owned(),
+ position: self.previous_position,
+ });
+ }
+ };
+
+ Ok((argument, push_back))
+ }
+
+ fn parse_math_binary(&mut self) -> Result<(), CompileError> {
+ let (left_instruction, left_type, left_position) =
+ self.instructions
+ .pop()
+ .ok_or_else(|| CompileError::ExpectedExpression {
+ found: self.previous_token.to_owned(),
+ position: self.previous_position,
+ })?;
+ let (left, push_back_left) = self.handle_binary_argument(&left_instruction)?;
+ let left_is_mutable_local = if let Operation::GET_LOCAL = left_instruction.operation() {
+ let GetLocal { local_index, .. } = GetLocal::from(left_instruction);
+
+ self.locals
+ .get(local_index as usize)
+ .map(|(local, _)| local.is_mutable)
+ .unwrap_or(false)
+ } else {
+ false
+ };
+
+ if push_back_left {
+ self.instructions
+ .push((left_instruction, left_type.clone(), left_position));
+ }
+
+ let operator = self.current_token;
+ let operator_position = self.current_position;
+ let rule = ParseRule::from(&operator);
+ let is_assignment = matches!(
+ operator,
+ Token::PlusEqual
+ | Token::MinusEqual
+ | Token::StarEqual
+ | Token::SlashEqual
+ | Token::PercentEqual
+ );
+
+ check_math_type(&left_type, operator, &left_position)?;
+
+ if is_assignment && !left_is_mutable_local {
+ return Err(CompileError::ExpectedMutableVariable {
+ found: self.previous_token.to_owned(),
+ position: left_position,
+ });
+ }
+
+ self.advance()?;
+ self.parse_sub_expression(&rule.precedence)?;
+
+ let (right_instruction, right_type, right_position) = self.instructions.pop().unwrap();
+ let (right, push_back_right) = self.handle_binary_argument(&right_instruction)?;
+
+ check_math_type(&right_type, operator, &right_position)?;
+ check_math_types(
+ &left_type,
+ &left_position,
+ operator,
+ &right_type,
+ &right_position,
+ )?;
+
+ if push_back_right {
+ self.instructions
+ .push((right_instruction, right_type, right_position));
+ }
+
+ let r#type = if is_assignment {
+ Type::None
+ } else if left_type == Type::Character {
+ Type::String
+ } else {
+ left_type.clone()
+ };
+ let destination = if is_assignment {
+ match left {
+ Argument::Register(register) => register,
+ Argument::Constant(_) => self.next_register(),
+ }
+ } else {
+ self.next_register()
+ };
+ let instruction = match operator {
+ Token::Plus | Token::PlusEqual => Instruction::add(destination, left, right),
+ Token::Minus | Token::MinusEqual => Instruction::subtract(destination, left, right),
+ Token::Star | Token::StarEqual => Instruction::multiply(destination, left, right),
+ Token::Slash | Token::SlashEqual => Instruction::divide(destination, left, right),
+ Token::Percent | Token::PercentEqual => Instruction::modulo(destination, left, right),
+ _ => {
+ return Err(CompileError::ExpectedTokenMultiple {
+ expected: &[
+ TokenKind::Plus,
+ TokenKind::PlusEqual,
+ TokenKind::Minus,
+ TokenKind::MinusEqual,
+ TokenKind::Star,
+ TokenKind::StarEqual,
+ TokenKind::Slash,
+ TokenKind::SlashEqual,
+ TokenKind::Percent,
+ TokenKind::PercentEqual,
+ ],
+ found: operator.to_owned(),
+ position: operator_position,
+ });
+ }
+ };
+
+ self.emit_instruction(instruction, r#type, operator_position);
+
+ Ok(())
+ }
+
+ fn parse_comparison_binary(&mut self) -> Result<(), CompileError> {
+ if let Some(
+ [
+ Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL,
+ _,
+ _,
+ ],
+ ) = self.get_last_operations()
+ {
+ return Err(CompileError::ComparisonChain {
+ position: self.current_position,
+ });
+ }
+
+ let (left_instruction, left_type, left_position) =
+ self.instructions
+ .pop()
+ .ok_or_else(|| CompileError::ExpectedExpression {
+ found: self.previous_token.to_owned(),
+ position: self.previous_position,
+ })?;
+ let (left, push_back_left) = self.handle_binary_argument(&left_instruction)?;
+ let operator = self.current_token;
+ let operator_position = self.current_position;
+ let rule = ParseRule::from(&operator);
+
+ if push_back_left {
+ self.instructions
+ .push((left_instruction, left_type, left_position));
+ }
+
+ self.advance()?;
+ self.parse_sub_expression(&rule.precedence)?;
+
+ let (right_instruction, right_type, right_position) =
+ self.instructions
+ .pop()
+ .ok_or_else(|| CompileError::ExpectedExpression {
+ found: self.previous_token.to_owned(),
+ position: self.previous_position,
+ })?;
+ let (right, push_back_right) = self.handle_binary_argument(&right_instruction)?;
+
+ if push_back_right {
+ self.instructions
+ .push((right_instruction, right_type, right_position));
+ }
+
+ let destination = self.next_register();
+ let comparison = match operator {
+ Token::DoubleEqual => Instruction::equal(true, left, right),
+ Token::BangEqual => Instruction::equal(false, left, right),
+ Token::Less => Instruction::less(true, left, right),
+ Token::LessEqual => Instruction::less_equal(true, left, right),
+ Token::Greater => Instruction::less_equal(false, left, right),
+ Token::GreaterEqual => Instruction::less(false, left, right),
+ _ => {
+ return Err(CompileError::ExpectedTokenMultiple {
+ expected: &[
+ TokenKind::DoubleEqual,
+ TokenKind::BangEqual,
+ TokenKind::Less,
+ TokenKind::LessEqual,
+ TokenKind::Greater,
+ TokenKind::GreaterEqual,
+ ],
+ found: operator.to_owned(),
+ position: operator_position,
+ });
+ }
+ };
+ let jump = Instruction::jump(1, true);
+ let load_true = Instruction::load_boolean(destination, true, true);
+ let load_false = Instruction::load_boolean(destination, false, false);
+
+ self.emit_instruction(comparison, Type::Boolean, operator_position);
+ self.emit_instruction(jump, Type::None, operator_position);
+ self.emit_instruction(load_true, Type::Boolean, operator_position);
+ self.emit_instruction(load_false, Type::Boolean, operator_position);
+
+ Ok(())
+ }
+
+ fn parse_logical_binary(&mut self) -> Result<(), CompileError> {
+ let (last_instruction, last_type, last_position) =
+ self.instructions
+ .pop()
+ .ok_or_else(|| CompileError::ExpectedExpression {
+ found: self.previous_token.to_owned(),
+ position: self.previous_position,
+ })?;
+ let operand_register = if last_instruction.operation() == Operation::GET_LOCAL {
+ let (local, _) = self.get_local(last_instruction.b_field())?;
+
+ local.register_index
+ } else if last_instruction.yields_value() {
+ let register = last_instruction.a_field();
+
+ self.instructions
+ .push((last_instruction, last_type, last_position));
+
+ register
+ } else {
+ self.instructions
+ .push((last_instruction, last_type, last_position));
+
+ self.next_register().saturating_sub(1)
+ };
+ let operator = self.current_token;
+ let operator_position = self.current_position;
+ let rule = ParseRule::from(&operator);
+ let test_boolean = match operator {
+ Token::DoubleAmpersand => true,
+ Token::DoublePipe => false,
+ _ => {
+ return Err(CompileError::ExpectedTokenMultiple {
+ expected: &[TokenKind::DoubleAmpersand, TokenKind::DoublePipe],
+ found: operator.to_owned(),
+ position: operator_position,
+ });
+ }
+ };
+ let test = Instruction::test(operand_register, test_boolean);
+ let jump = Instruction::jump(1, true);
+
+ self.emit_instruction(test, Type::None, operator_position);
+ self.emit_instruction(jump, Type::None, operator_position);
+ self.advance()?;
+ self.parse_sub_expression(&rule.precedence)?;
+
+ let instructions_length = self.instructions.len();
+
+ for (group_index, instructions) in self.instructions.rchunks_mut(3).enumerate().rev() {
+ if instructions.len() < 3 {
+ continue;
+ }
+
+ if !matches!(
+ instructions[0].0.operation(),
+ Operation::TEST | Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL
+ ) || !matches!(instructions[1].0.operation(), Operation::JUMP)
+ {
+ continue;
+ }
+
+ let old_jump = &mut instructions[1].0;
+ let jump_index = instructions_length - group_index * 3 - 1;
+ let short_circuit_distance = (instructions_length - jump_index) as u8;
+
+ *old_jump = Instruction::jump(short_circuit_distance, true);
+ }
+
+ Ok(())
+ }
+
+ fn parse_variable(&mut self) -> Result<(), CompileError> {
+ let start_position = self.current_position;
+ let identifier = if let Token::Identifier(text) = self.current_token {
+ self.advance()?;
+
+ text
+ } else {
+ return Err(CompileError::ExpectedToken {
+ expected: TokenKind::Identifier,
+ found: self.current_token.to_owned(),
+ position: start_position,
+ });
+ };
+ let local_index = if let Ok(local_index) = self.get_local_index(identifier) {
+ local_index
+ } else if let Some(native_function) = NativeFunction::from_str(identifier) {
+ return self.parse_call_native(native_function);
+ } else if self.function_name.as_deref() == Some(identifier) && !self.is_main {
+ let destination = self.next_register();
+ let load_self = Instruction::load_self(destination);
+
+ self.emit_instruction(load_self, Type::SelfFunction, start_position);
+
+ return Ok(());
+ } else {
+ return Err(CompileError::UndeclaredVariable {
+ identifier: identifier.to_string(),
+ position: start_position,
+ });
+ };
+
+ let (local, r#type) = self
+ .get_local(local_index)
+ .map(|(local, r#type)| (local, r#type.clone()))?;
+ let is_mutable = local.is_mutable;
+ let local_register_index = local.register_index;
+
+ if !self.current_scope.contains(&local.scope) {
+ return Err(CompileError::VariableOutOfScope {
+ identifier: self.get_identifier(local_index).unwrap(),
+ position: start_position,
+ variable_scope: local.scope,
+ access_scope: self.current_scope,
+ });
+ }
+
+ if self.allow(Token::Equal)? {
+ if !is_mutable {
+ return Err(CompileError::CannotMutateImmutableVariable {
+ identifier: self.get_identifier(local_index).unwrap(),
+ position: start_position,
+ });
+ }
+
+ self.parse_expression()?;
+
+ if self
+ .instructions
+ .last()
+ .is_some_and(|(instruction, _, _)| instruction.is_math())
+ {
+ let (math_instruction, _, _) = self.instructions.last_mut().unwrap();
+
+ math_instruction.set_a_field(local_register_index);
+ } else {
+ let register = self.next_register() - 1;
+ let set_local = Instruction::from(SetLocal {
+ register_index: register,
+ local_index,
+ });
+
+ self.emit_instruction(set_local, Type::None, start_position);
+ }
+
+ return Ok(());
+ }
+
+ let destination = self.next_register();
+ let get_local = Instruction::from(GetLocal {
+ destination,
+ local_index,
+ });
+
+ self.emit_instruction(get_local, r#type, self.previous_position);
+
+ Ok(())
+ }
+
+ fn parse_type_from(&mut self, token: Token, position: Span) -> Result {
+ match token {
+ Token::Bool => Ok(Type::Boolean),
+ Token::FloatKeyword => Ok(Type::Float),
+ Token::Int => Ok(Type::Integer),
+ Token::Str => Ok(Type::String),
+ _ => Err(CompileError::ExpectedTokenMultiple {
+ expected: &[
+ TokenKind::Bool,
+ TokenKind::FloatKeyword,
+ TokenKind::Int,
+ TokenKind::Str,
+ ],
+ found: self.current_token.to_owned(),
+ position,
+ }),
+ }
+ }
+
+ fn parse_block(&mut self) -> Result<(), CompileError> {
+ self.advance()?;
+
+ let starting_block = self.current_scope.block_index;
+
+ self.block_index += 1;
+ self.current_scope.begin(self.block_index);
+
+ while !self.allow(Token::RightBrace)? && !self.is_eof() {
+ self.parse(Precedence::None)?;
+ }
+
+ self.current_scope.end(starting_block);
+
+ Ok(())
+ }
+
+ fn parse_list(&mut self) -> Result<(), CompileError> {
+ let start = self.current_position.0;
+
+ self.advance()?;
+
+ let start_register = self.next_register();
+ let mut item_type = Type::Any;
+
+ while !self.allow(Token::RightBracket)? && !self.is_eof() {
+ let expected_register = self.next_register();
+
+ self.parse_expression()?;
+
+ let actual_register = self.next_register() - 1;
+
+ if item_type == Type::Any {
+ item_type = self.get_last_instruction_type();
+ }
+
+ if expected_register < actual_register {
+ let close = Instruction::from(Close {
+ from: expected_register,
+ to: actual_register,
+ });
+
+ self.emit_instruction(close, Type::None, self.current_position);
+ }
+
+ self.allow(Token::Comma)?;
+ }
+
+ let destination = self.next_register();
+ let end = self.previous_position.1;
+ let load_list = Instruction::from(LoadList {
+ destination,
+ start_register,
+ });
+
+ self.emit_instruction(load_list, Type::List(Box::new(item_type)), Span(start, end));
+
+ Ok(())
+ }
+
+ fn parse_if(&mut self) -> Result<(), CompileError> {
+ self.advance()?;
+ self.parse_expression()?;
+
+ if matches!(
+ self.get_last_operations(),
+ Some([
+ Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL,
+ Operation::JUMP,
+ Operation::LOAD_BOOLEAN,
+ Operation::LOAD_BOOLEAN
+ ]),
+ ) {
+ self.instructions.pop();
+ self.instructions.pop();
+ self.instructions.pop();
+ } else {
+ let operand_register = self.next_register() - 1;
+ let test = Instruction::test(operand_register, true);
+
+ self.emit_instruction(test, Type::None, self.current_position);
+ }
+
+ let if_block_start = self.instructions.len();
+ let if_block_start_position = self.current_position;
+
+ if let Token::LeftBrace = self.current_token {
+ self.parse_block()?;
+ } else {
+ return Err(CompileError::ExpectedTokenMultiple {
+ expected: &[TokenKind::If, TokenKind::LeftBrace],
+ found: self.current_token.to_owned(),
+ position: self.current_position,
+ });
+ }
+
+ let if_block_end = self.instructions.len();
+ let mut if_block_distance = (if_block_end - if_block_start) as u8;
+ let if_block_type = self.get_last_instruction_type();
+
+ if let Token::Else = self.current_token {
+ self.advance()?;
+
+ if let Token::LeftBrace = self.current_token {
+ self.parse_block()?;
+ } else {
+ return Err(CompileError::ExpectedTokenMultiple {
+ expected: &[TokenKind::If, TokenKind::LeftBrace],
+ found: self.current_token.to_owned(),
+ position: self.current_position,
+ });
+ }
+ } else if if_block_type != Type::None {
+ return Err(CompileError::IfMissingElse {
+ position: Span(if_block_start_position.0, self.current_position.1),
+ });
+ }
+
+ let else_block_end = self.instructions.len();
+ let else_block_distance = (else_block_end - if_block_end) as u8;
+ let else_block_type = self.get_last_instruction_type();
+
+ if let Err(conflict) = if_block_type.check(&else_block_type) {
+ return Err(CompileError::IfElseBranchMismatch {
+ conflict,
+ position: Span(if_block_start_position.0, self.current_position.1),
+ });
+ }
+
+ match else_block_distance {
+ 0 => {}
+ 1 => {
+ if let Some(Operation::LOAD_BOOLEAN | Operation::LOAD_CONSTANT) =
+ self.get_last_operation()
+ {
+ let (loader, _, _) = self.instructions.last_mut().unwrap();
+
+ loader.set_c_field(true as u8);
+ } else {
+ if_block_distance += 1;
+ let jump = Instruction::from(Jump {
+ offset: else_block_distance,
+ is_positive: true,
+ });
+
+ self.instructions
+ .insert(if_block_end, (jump, Type::None, self.current_position));
+ }
+ }
+ 2.. => {
+ if_block_distance += 1;
+ let jump = Instruction::from(Jump {
+ offset: else_block_distance,
+ is_positive: true,
+ });
+
+ self.instructions
+ .insert(if_block_end, (jump, Type::None, self.current_position));
+ }
+ }
+
+ let jump = Instruction::jump(if_block_distance, true);
+
+ self.instructions
+ .insert(if_block_start, (jump, Type::None, if_block_start_position));
+ control_flow_register_consolidation(self);
+
+ Ok(())
+ }
+
+ fn parse_while(&mut self) -> Result<(), CompileError> {
+ self.advance()?;
+
+ let expression_start = self.instructions.len() as u8;
+
+ self.parse_expression()?;
+
+ if matches!(
+ self.get_last_operations(),
+ Some([
+ Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL,
+ Operation::JUMP,
+ Operation::LOAD_BOOLEAN,
+ Operation::LOAD_BOOLEAN
+ ]),
+ ) {
+ self.instructions.pop();
+ self.instructions.pop();
+ self.instructions.pop();
+ } else {
+ let operand_register = self.next_register() - 1;
+ let test = Instruction::test(operand_register, true);
+
+ self.emit_instruction(test, Type::None, self.current_position);
+ }
+
+ let block_start = self.instructions.len();
+
+ self.parse_block()?;
+
+ let block_end = self.instructions.len() as u8;
+ let jump_distance = block_end - block_start as u8 + 1;
+ let jump = Instruction::from(Jump {
+ offset: jump_distance,
+ is_positive: true,
+ });
+
+ self.instructions
+ .insert(block_start, (jump, Type::None, self.current_position));
+
+ let jump_back_distance = block_end - expression_start + 1;
+ let jump_back = Instruction::from(Jump {
+ offset: jump_back_distance,
+ is_positive: false,
+ });
+
+ self.emit_instruction(jump_back, Type::None, self.current_position);
+
+ Ok(())
+ }
+
+ fn parse_call_native(&mut self, function: NativeFunction) -> Result<(), CompileError> {
+ let start = self.previous_position.0;
+ let start_register = self.next_register();
+
+ self.expect(Token::LeftParenthesis)?;
+
+ while !self.allow(Token::RightParenthesis)? {
+ let expected_register = self.next_register();
+
+ self.parse_expression()?;
+
+ let actual_register = self.next_register() - 1;
+ let registers_to_close = actual_register - expected_register;
+
+ if registers_to_close > 0 {
+ let close = Instruction::from(Close {
+ from: expected_register,
+ to: actual_register,
+ });
+
+ self.emit_instruction(close, Type::None, self.current_position);
+ }
+
+ self.allow(Token::Comma)?;
+ }
+
+ let end = self.previous_position.1;
+ let destination = self.next_register();
+ let argument_count = destination - start_register;
+ let return_type = function.r#type().return_type;
+ let call_native = Instruction::from(CallNative {
+ destination,
+ function,
+ argument_count,
+ });
+
+ self.emit_instruction(call_native, return_type, Span(start, end));
+
+ Ok(())
+ }
+
+ fn parse_semicolon(&mut self) -> Result<(), CompileError> {
+ self.advance()?;
+ self.parse(Precedence::None)
+ }
+
+ fn parse_expression(&mut self) -> Result<(), CompileError> {
+ self.parse(Precedence::None)?;
+
+ let expression_type = self.get_last_instruction_type();
+
+ if expression_type == Type::None || self.instructions.is_empty() {
+ return Err(CompileError::ExpectedExpression {
+ found: self.previous_token.to_owned(),
+ position: self.current_position,
+ });
+ }
+
+ Ok(())
+ }
+
+ fn parse_sub_expression(&mut self, precedence: &Precedence) -> Result<(), CompileError> {
+ self.parse(precedence.increment())?;
+
+ let expression_type = self.get_last_instruction_type();
+
+ if expression_type == Type::None || self.instructions.is_empty() {
+ return Err(CompileError::ExpectedExpression {
+ found: self.previous_token.to_owned(),
+ position: self.current_position,
+ });
+ }
+
+ Ok(())
+ }
+
+ fn parse_return_statement(&mut self) -> Result<(), CompileError> {
+ let start = self.current_position.0;
+
+ self.advance()?;
+
+ let should_return_value =
+ if matches!(self.current_token, Token::Semicolon | Token::RightBrace) {
+ self.update_return_type(Type::None)?;
+
+ false
+ } else {
+ self.parse_expression()?;
+
+ let expression_type = self.get_last_instruction_type();
+
+ self.update_return_type(expression_type)?;
+
+ true
+ };
+ let end = self.current_position.1;
+ let return_register = self.next_register() - 1;
+ let r#return = Instruction::from(Return {
+ should_return_value,
+ return_register,
+ });
+
+ self.emit_instruction(r#return, Type::None, Span(start, end));
+
+ let instruction_length = self.instructions.len();
+
+ for (index, (instruction, _, _)) in self.instructions.iter_mut().enumerate() {
+ if instruction.operation() == Operation::JUMP {
+ let Jump {
+ offset,
+ is_positive,
+ } = Jump::from(*instruction);
+ let offset = offset as usize;
+
+ if is_positive && offset + index == instruction_length - 1 {
+ *instruction = Instruction::jump((offset + 1) as u8, true);
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ fn parse_implicit_return(&mut self) -> Result<(), CompileError> {
+ if matches!(self.get_last_operation(), Some(Operation::RETURN))
+ || matches!(
+ self.get_last_operations(),
+ Some([Operation::RETURN, Operation::JUMP])
+ )
+ {
+ // Do nothing if the last instruction is a return or a return followed by a jump
+ } else if self.allow(Token::Semicolon)? {
+ let r#return = Instruction::r#return(false, 0);
+
+ self.emit_instruction(r#return, Type::None, self.current_position);
+ } else {
+ let (previous_expression_type, previous_register) = self
+ .instructions
+ .last()
+ .map(|(instruction, r#type, _)| {
+ if instruction.yields_value() {
+ (r#type.clone(), instruction.a_field())
+ } else {
+ (Type::None, 0)
+ }
+ })
+ .ok_or_else(|| CompileError::ExpectedExpression {
+ found: self.previous_token.to_owned(),
+ position: self.previous_position,
+ })?;
+
+ let should_return_value = previous_expression_type != Type::None;
+ let r#return = Instruction::r#return(should_return_value, previous_register);
+
+ self.update_return_type(previous_expression_type.clone())?;
+ self.emit_instruction(r#return, Type::None, self.current_position);
+ }
+
+ let instruction_length = self.instructions.len();
+
+ for (index, (instruction, _, _)) in self.instructions.iter_mut().enumerate() {
+ if instruction.operation() == Operation::JUMP {
+ let Jump {
+ offset,
+ is_positive,
+ } = Jump::from(*instruction);
+ let offset = offset as usize;
+
+ if is_positive && offset + index == instruction_length - 1 {
+ *instruction = Instruction::jump((offset + 1) as u8, true);
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ fn parse_let_statement(&mut self) -> Result<(), CompileError> {
+ self.advance()?;
+
+ let is_mutable = self.allow(Token::Mut)?;
+ let position = self.current_position;
+ let identifier = if let Token::Identifier(text) = self.current_token {
+ self.advance()?;
+
+ text
+ } else {
+ return Err(CompileError::ExpectedToken {
+ expected: TokenKind::Identifier,
+ found: self.current_token.to_owned(),
+ position,
+ });
+ };
+ let explicit_type = if self.allow(Token::Colon)? {
+ self.advance()?;
+
+ let r#type = self.parse_type_from(self.current_token, self.current_position)?;
+
+ Some(r#type)
+ } else {
+ None
+ };
+
+ self.expect(Token::Equal)?;
+ self.parse_expression()?;
+
+ let register_index = self.next_register() - 1;
+ let r#type = if let Some(r#type) = explicit_type {
+ r#type
+ } else {
+ self.get_register_type(register_index)?
+ };
+
+ self.declare_local(
+ identifier,
+ register_index,
+ r#type,
+ is_mutable,
+ self.current_scope,
+ );
+
+ Ok(())
+ }
+
+ fn parse_function(&mut self) -> Result<(), CompileError> {
+ let function_start = self.current_position.0;
+
+ self.advance()?;
+
+ let identifier = if let Token::Identifier(text) = self.current_token {
+ self.advance()?;
+
+ Some(text)
+ } else {
+ None
+ };
+
+ let mut function_compiler = if self.current_token == Token::LeftParenthesis {
+ let function_name = identifier.map(DustString::from);
+
+ Compiler::new(self.lexer, function_name, false)? // This will consume the parenthesis
+ } else {
+ return Err(CompileError::ExpectedToken {
+ expected: TokenKind::LeftParenthesis,
+ found: self.current_token.to_owned(),
+ position: self.current_position,
+ });
+ };
+
+ function_compiler.prototype_index = self.prototypes.len() as u8;
+
+ let mut value_parameters: Vec<(u8, Type)> = Vec::with_capacity(3);
+
+ while !function_compiler.allow(Token::RightParenthesis)? {
+ let is_mutable = function_compiler.allow(Token::Mut)?;
+ let parameter = if let Token::Identifier(text) = function_compiler.current_token {
+ function_compiler.advance()?;
+
+ text
+ } else {
+ return Err(CompileError::ExpectedToken {
+ expected: TokenKind::Identifier,
+ found: function_compiler.current_token.to_owned(),
+ position: function_compiler.current_position,
+ });
+ };
+
+ function_compiler.expect(Token::Colon)?;
+
+ let r#type = function_compiler.parse_type_from(
+ function_compiler.current_token,
+ function_compiler.current_position,
+ )?;
+
+ function_compiler.advance()?;
+
+ let local_register_index = function_compiler.next_register();
+ let (_, identifier_index) = function_compiler.declare_local(
+ parameter,
+ local_register_index,
+ r#type.clone(),
+ is_mutable,
+ function_compiler.current_scope,
+ );
+
+ value_parameters.push((identifier_index, r#type));
+ function_compiler.allow(Token::Comma)?;
+
+ function_compiler.minimum_register += 1;
+ }
+
+ let return_type = if function_compiler.allow(Token::ArrowThin)? {
+ let r#type = function_compiler.parse_type_from(
+ function_compiler.current_token,
+ function_compiler.current_position,
+ )?;
+
+ function_compiler.advance()?;
+
+ r#type
+ } else {
+ Type::None
+ };
+ let function_type = FunctionType {
+ type_parameters: Vec::with_capacity(0),
+ value_parameters,
+ return_type,
+ };
+
+ function_compiler.r#type = function_type.clone();
+
+ function_compiler.expect(Token::LeftBrace)?;
+ function_compiler.compile()?;
+ function_compiler.expect(Token::RightBrace)?;
+
+ self.previous_token = function_compiler.previous_token;
+ self.previous_position = function_compiler.previous_position;
+ self.current_token = function_compiler.current_token;
+ self.current_position = function_compiler.current_position;
+
+ self.lexer.skip_to(self.current_position.1);
+
+ let function_end = function_compiler.previous_position.1;
+ let prototype_index = function_compiler.prototype_index;
+ let chunk = function_compiler.finish();
+ let destination = self.next_register();
+
+ self.prototypes.push(chunk);
+
+ if let Some(identifier) = identifier {
+ self.declare_local(
+ identifier,
+ destination,
+ Type::function(function_type.clone()),
+ false,
+ self.current_scope,
+ );
+ }
+
+ let load_function = Instruction::load_function(destination, prototype_index);
+
+ self.emit_instruction(
+ load_function,
+ Type::function(function_type),
+ Span(function_start, function_end),
+ );
+
+ Ok(())
+ }
+
+ fn parse_call(&mut self) -> Result<(), CompileError> {
+ let start = self.current_position.0;
+
+ self.advance()?;
+
+ let (last_instruction, last_instruction_type, _) =
+ self.instructions
+ .last()
+ .ok_or_else(|| CompileError::ExpectedExpression {
+ found: self.previous_token.to_owned(),
+ position: self.previous_position,
+ })?;
+
+ if !matches!(
+ last_instruction_type,
+ Type::Function(_) | Type::SelfFunction
+ ) {
+ return Err(CompileError::ExpectedFunction {
+ found: self.previous_token.to_owned(),
+ actual_type: last_instruction_type.clone(),
+ position: self.previous_position,
+ });
+ }
+
+ let function_register = last_instruction.a_field();
+ let function_return_type = match last_instruction_type {
+ Type::Function(function_type) => function_type.return_type.clone(),
+ Type::SelfFunction => self.r#type.return_type.clone(),
+ _ => {
+ return Err(CompileError::ExpectedFunction {
+ found: self.previous_token.to_owned(),
+ actual_type: last_instruction_type.clone(),
+ position: self.previous_position,
+ });
+ }
+ };
+ let is_recursive = last_instruction_type == &Type::SelfFunction;
+
+ let mut argument_count = 0;
+
+ while !self.allow(Token::RightParenthesis)? {
+ let expected_register = self.next_register();
+
+ self.parse_expression()?;
+
+ let actual_register = self.next_register() - 1;
+ let registers_to_close = (actual_register - expected_register).saturating_sub(1);
+
+ if registers_to_close > 0 {
+ let close = Instruction::from(Close {
+ from: expected_register,
+ to: actual_register,
+ });
+
+ self.emit_instruction(close, Type::None, self.current_position);
+ }
+
+ argument_count += registers_to_close + 1;
+
+ self.allow(Token::Comma)?;
+ }
+
+ let end = self.current_position.1;
+ let destination = self.next_register();
+ let call = Instruction::call(destination, function_register, argument_count, is_recursive);
+
+ self.emit_instruction(call, function_return_type, Span(start, end));
+
+ Ok(())
+ }
+
+ fn expect_expression(&mut self) -> Result<(), CompileError> {
+ Err(CompileError::ExpectedExpression {
+ found: self.current_token.to_owned(),
+ position: self.current_position,
+ })
+ }
+
+ fn parse(&mut self, precedence: Precedence) -> Result<(), CompileError> {
+ if let Some(prefix_parser) = ParseRule::from(&self.current_token).prefix {
+ debug!(
+ "{} is prefix with precedence {precedence}",
+ self.current_token.to_string(),
+ );
+
+ prefix_parser(self)?;
+ }
+
+ let mut infix_rule = ParseRule::from(&self.current_token);
+
+ while precedence <= infix_rule.precedence {
+ if let Some(infix_parser) = infix_rule.infix {
+ debug!(
+ "{} is infix with precedence {precedence}",
+ self.current_token.to_string(),
+ );
+
+ if self.current_token == Token::Equal {
+ return Err(CompileError::InvalidAssignmentTarget {
+ found: self.current_token.to_owned(),
+ position: self.current_position,
+ });
+ }
+
+ infix_parser(self)?;
+ } else {
+ break;
+ }
+
+ infix_rule = ParseRule::from(&self.current_token);
+ }
+
+ Ok(())
+ }
+}
diff --git a/dust-lang/src/compiler/optimize.rs b/dust-lang/src/compiler/optimize.rs
new file mode 100644
index 0000000..83b529c
--- /dev/null
+++ b/dust-lang/src/compiler/optimize.rs
@@ -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,
+ );
+}
diff --git a/dust-lang/src/compiler/parse_rule.rs b/dust-lang/src/compiler/parse_rule.rs
new file mode 100644
index 0000000..90261c5
--- /dev/null
+++ b/dust-lang/src/compiler/parse_rule.rs
@@ -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>,
+ pub infix: Option>,
+ 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)
+ }
+}
diff --git a/dust-lang/src/compiler/type_checks.rs b/dust-lang/src/compiler/type_checks.rs
new file mode 100644
index 0000000..a8f94bd
--- /dev/null
+++ b/dust-lang/src/compiler/type_checks.rs
@@ -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),
+ })
+ }
+}
diff --git a/dust-lang/src/disassembler.rs b/dust-lang/src/disassembler.rs
deleted file mode 100644
index 664bf86..0000000
--- a/dust-lang/src/disassembler.rs
+++ /dev/null
@@ -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::>();
- 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::();
-
- 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::().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::>().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()
- }
-}
diff --git a/dust-lang/src/dust_error.rs b/dust-lang/src/dust_error.rs
index cd70acc..da94b40 100644
--- a/dust-lang/src/dust_error.rs
+++ b/dust-lang/src/dust_error.rs
@@ -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.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;
- fn position(&self) -> Span;
+ fn detail_snippets(&self) -> Vec<(String, Span)>;
+ fn help_snippets(&self) -> Vec<(String, Span)>;
}
diff --git a/dust-lang/src/instruction/add.rs b/dust-lang/src/instruction/add.rs
index f6da751..d8648c2 100644
--- a/dust-lang/src/instruction/add.rs
+++ b/dust-lang/src/instruction/add.rs
@@ -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 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 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)
}
}
diff --git a/dust-lang/src/instruction/call.rs b/dust-lang/src/instruction/call.rs
index 7fa1247..dff0679 100644
--- a/dust-lang/src/instruction/call.rs
+++ b/dust-lang/src/instruction/call.rs
@@ -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 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 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)
}
}
diff --git a/dust-lang/src/instruction/call_native.rs b/dust-lang/src/instruction/call_native.rs
index e35266b..d44a637 100644
--- a/dust-lang/src/instruction/call_native.rs
+++ b/dust-lang/src/instruction/call_native.rs
@@ -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 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 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)
}
}
diff --git a/dust-lang/src/instruction/close.rs b/dust-lang/src/instruction/close.rs
index 7428a1b..304d88c 100644
--- a/dust-lang/src/instruction/close.rs
+++ b/dust-lang/src/instruction/close.rs
@@ -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 for Close {
+ fn from(instruction: Instruction) -> Self {
Close {
- from: instruction.b(),
- to: instruction.c(),
+ from: instruction.b_field(),
+ to: instruction.c_field(),
}
}
}
impl From 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)
}
}
diff --git a/dust-lang/src/instruction/define_local.rs b/dust-lang/src/instruction/define_local.rs
deleted file mode 100644
index 021fae9..0000000
--- a/dust-lang/src/instruction/define_local.rs
+++ /dev/null
@@ -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 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)
- }
-}
diff --git a/dust-lang/src/instruction/divide.rs b/dust-lang/src/instruction/divide.rs
index 85243a4..55a3263 100644
--- a/dust-lang/src/instruction/divide.rs
+++ b/dust-lang/src/instruction/divide.rs
@@ -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 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 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)
}
}
diff --git a/dust-lang/src/instruction/equal.rs b/dust-lang/src/instruction/equal.rs
index 2400d87..3b42c2e 100644
--- a/dust-lang/src/instruction/equal.rs
+++ b/dust-lang/src/instruction/equal.rs
@@ -6,27 +6,22 @@ pub struct Equal {
pub right: Argument,
}
-impl From<&Instruction> for Equal {
- fn from(instruction: &Instruction) -> Self {
+impl From 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 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)
}
}
diff --git a/dust-lang/src/instruction/get_local.rs b/dust-lang/src/instruction/get_local.rs
index a2fc817..ec8be9d 100644
--- a/dust-lang/src/instruction/get_local.rs
+++ b/dust-lang/src/instruction/get_local.rs
@@ -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 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 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)
}
}
diff --git a/dust-lang/src/instruction/jump.rs b/dust-lang/src/instruction/jump.rs
index 0bd866c..8a4c150 100644
--- a/dust-lang/src/instruction/jump.rs
+++ b/dust-lang/src/instruction/jump.rs
@@ -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 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 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)
}
}
diff --git a/dust-lang/src/instruction/less.rs b/dust-lang/src/instruction/less.rs
index d2e5463..f8a868e 100644
--- a/dust-lang/src/instruction/less.rs
+++ b/dust-lang/src/instruction/less.rs
@@ -6,27 +6,22 @@ pub struct Less {
pub right: Argument,
}
-impl From<&Instruction> for Less {
- fn from(instruction: &Instruction) -> Self {
+impl From 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 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)
}
}
diff --git a/dust-lang/src/instruction/less_equal.rs b/dust-lang/src/instruction/less_equal.rs
index 698ef5d..6707804 100644
--- a/dust-lang/src/instruction/less_equal.rs
+++ b/dust-lang/src/instruction/less_equal.rs
@@ -6,27 +6,22 @@ pub struct LessEqual {
pub right: Argument,
}
-impl From<&Instruction> for LessEqual {
- fn from(instruction: &Instruction) -> Self {
+impl From 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 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)
}
}
diff --git a/dust-lang/src/instruction/load_boolean.rs b/dust-lang/src/instruction/load_boolean.rs
index e3675de..9742d70 100644
--- a/dust-lang/src/instruction/load_boolean.rs
+++ b/dust-lang/src/instruction/load_boolean.rs
@@ -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 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 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)
}
}
diff --git a/dust-lang/src/instruction/load_constant.rs b/dust-lang/src/instruction/load_constant.rs
index b6f0f2d..2940a6c 100644
--- a/dust-lang/src/instruction/load_constant.rs
+++ b/dust-lang/src/instruction/load_constant.rs
@@ -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 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 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(())
+ }
}
}
diff --git a/dust-lang/src/instruction/load_function.rs b/dust-lang/src/instruction/load_function.rs
new file mode 100644
index 0000000..d1ec669
--- /dev/null
+++ b/dust-lang/src/instruction/load_function.rs
@@ -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 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 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)
+ }
+}
diff --git a/dust-lang/src/instruction/load_list.rs b/dust-lang/src/instruction/load_list.rs
index 3e6b987..2812cb4 100644
--- a/dust-lang/src/instruction/load_list.rs
+++ b/dust-lang/src/instruction/load_list.rs
@@ -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 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 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)
}
}
diff --git a/dust-lang/src/instruction/load_self.rs b/dust-lang/src/instruction/load_self.rs
index 66cf872..9e5b999 100644
--- a/dust-lang/src/instruction/load_self.rs
+++ b/dust-lang/src/instruction/load_self.rs
@@ -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 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 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)
}
}
diff --git a/dust-lang/src/instruction/mod.rs b/dust-lang/src/instruction/mod.rs
index 600d1a9..8cb0234 100644
--- a/dust-lang/src/instruction/mod.rs
+++ b/dust-lang/src/instruction/mod.rs
@@ -1,29 +1,32 @@
-//! An operation and its arguments for the Dust virtual machine.
+//! Instructions for the Dust virtual machine.
//!
-//! Each instruction is a 64-bit unsigned integer that is divided into nine fields:
+//! Each instruction is 32 bits and uses up to seven distinct fields:
//!
//! Bit | Description
//! ----- | -----------
-//! 0-8 | The operation code.
-//! 9 | Boolean flag indicating whether the B argument is a constant.
-//! 10 | Boolean flag indicating whether the C argument is a constant.
-//! 11 | Boolean flag indicating whether the A argument is a local.
-//! 12 | Boolean flag indicating whether the B argument is a local.
-//! 13 | Boolean flag indicating whether the C argument is a local.
-//! 17-32 | The A argument,
-//! 33-48 | The B argument.
-//! 49-63 | The C argument.
+//! 0-4 | Operation code
+//! 5 | Flag indicating if the B field is a constant
+//! 6 | Flag indicating if the C field is a constant
+//! 7 | D field (boolean)
+//! 8-15 | A field (unsigned 8-bit integer)
+//! 16-23 | B field (unsigned 8-bit integer)
+//! 24-31 | C field (unsigned 8-bit integer)
//!
-//! Be careful when working with instructions directly. When modifying an instruction, be sure to
-//! account for the fact that setting the A, B, or C arguments to 0 will have no effect. It is
-//! usually best to remove instructions and insert new ones in their place instead of mutating them.
+//! **Be careful when working with instructions directly**. When modifying an instruction's fields,
+//! you may also need to modify its flags. It is usually best to remove instructions and insert new
+//! ones in their place instead of mutating them.
+//!
+//! # Examples
+//!
+//! ## Creating Instructions
//!
//! For each operation, there are two ways to create an instruction:
//!
//! - Use the associated function on `Instruction`
//! - Use the corresponding struct and call `Instruction::from`
//!
-//! # Examples
+//! Both produce the same result, but the first is more concise. The structs are more useful when
+//! reading instructions, as shown below.
//!
//! ```
//! # use dust_lang::instruction::{Instruction, Move};
@@ -33,29 +36,63 @@
//! assert_eq!(move_1, move_2);
//! ```
//!
-//! Use the `Destination` and `Argument` enums to create instructions. This is easier to read and
-//! enforces consistency in how the `Instruction` methods are called.
+//! Use the [`Argument`][] type when creating instructions. In addition to being easy to read and
+//! write, this ensures that the instruction has the correct flags to represent the arguments.
//!
//! ```
-//! # use dust_lang::instruction::{Instruction, Add, Destination, Argument};
+//! # use dust_lang::instruction::{Instruction, Add, Argument};
//! let add_1 = Instruction::add(
-//! Destination::Register(0),
-//! Argument::Local(1),
+//! 0,
+//! Argument::Register(1),
//! Argument::Constant(2)
//! );
//! let add_2 = Instruction::from(Add {
-//! destination: Destination::Register(0),
-//! left: Argument::Local(1),
+//! destination: 0,
+//! left: Argument::Register(1),
//! right: Argument::Constant(2),
//! });
//!
//! assert_eq!(add_1, add_2);
//! ```
+//!
+//! ## Reading Instructions
+//!
+//! To read an instruction, check its operation with [`Instruction::operation`], then convert the
+//! instruction to the struct that corresponds to that operation. Like the example above, this
+//! removes the burden of dealing with the options directly and automatically casts the A, B, C and
+//! D fields as `u8`, `bool` or `Argument` values.
+//!
+//! ```
+//! # use dust_lang::instruction::{Instruction, Add, Argument, Operation};
+//! # let mystery_instruction = Instruction::add(
+//! # 1,
+//! # Argument::Register(1),
+//! # Argument::Constant(2)
+//! # );
+//! // Let's read an instruction and see if it performs addition-assignment,
+//! // like in one of the following examples:
+//! // - `a += 2`
+//! // - `a = a + 2`
+//! // - `a = 2 + a`
+//!
+//! let operation = mystery_instruction.operation();
+//! let is_add_assign = match operation {
+//! Operation::Add => {
+//! let Add { destination, left, right } = Add::from(&mystery_instruction);
+//!
+//! left == Argument::Register(destination)
+//! || right == Argument::Register(destination);
+//!
+//! }
+//! _ => false,
+//! };
+//!
+//! assert!(is_add_assign);
+//! ```
mod add;
mod call;
mod call_native;
mod close;
-mod define_local;
mod divide;
mod equal;
mod get_local;
@@ -64,26 +101,25 @@ mod less;
mod less_equal;
mod load_boolean;
mod load_constant;
+mod load_function;
mod load_list;
mod load_self;
mod modulo;
-mod r#move;
mod multiply;
mod negate;
mod not;
+mod operation;
+mod point;
mod r#return;
mod set_local;
mod subtract;
mod test;
mod test_set;
-use std::fmt::{self, Debug, Display, Formatter};
-
pub use add::Add;
pub use call::Call;
pub use call_native::CallNative;
pub use close::Close;
-pub use define_local::DefineLocal;
pub use divide::Divide;
pub use equal::Equal;
pub use get_local::GetLocal;
@@ -92,13 +128,15 @@ pub use less::Less;
pub use less_equal::LessEqual;
pub use load_boolean::LoadBoolean;
pub use load_constant::LoadConstant;
+pub use load_function::LoadFunction;
pub use load_list::LoadList;
pub use load_self::LoadSelf;
pub use modulo::Modulo;
pub use multiply::Multiply;
pub use negate::Negate;
pub use not::Not;
-pub use r#move::Move;
+pub use operation::Operation;
+pub use point::Point;
pub use r#return::Return;
pub use set_local::SetLocal;
pub use subtract::Subtract;
@@ -106,29 +144,102 @@ pub use test::Test;
pub use test_set::TestSet;
use serde::{Deserialize, Serialize};
+use std::fmt::{self, Debug, Display, Formatter};
-use crate::{NativeFunction, Operation};
+use crate::NativeFunction;
/// An operation and its arguments for the Dust virtual machine.
///
/// See the [module-level documentation](index.html) for more information.
-#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
-pub struct Instruction(u64);
+#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
+pub struct Instruction(u32);
impl Instruction {
- pub fn new(operation: Operation) -> Instruction {
- Instruction(operation as u64)
+ pub fn new(
+ operation: Operation,
+ a: u8,
+ b: u8,
+ c: u8,
+ b_is_constant: bool,
+ c_is_constant: bool,
+ d: bool,
+ ) -> Instruction {
+ let bits = operation.0 as u32
+ | ((b_is_constant as u32) << 5)
+ | ((c_is_constant as u32) << 6)
+ | ((d as u32) << 7)
+ | ((a as u32) << 8)
+ | ((b as u32) << 16)
+ | ((c as u32) << 24);
+
+ Instruction(bits)
}
- pub fn r#move(from: u16, to: u16) -> Instruction {
- Instruction::from(Move { from, to })
+ pub fn operation(&self) -> Operation {
+ let operation_bits = self.0 & 0b0001_1111;
+
+ Operation(operation_bits as u8)
}
- pub fn close(from: u16, to: u16) -> Instruction {
+ pub fn b_is_constant(&self) -> bool {
+ (self.0 >> 5) & 1 == 1
+ }
+
+ pub fn c_is_constant(&self) -> bool {
+ (self.0 >> 6) & 1 == 1
+ }
+
+ pub fn d_field(&self) -> bool {
+ (self.0 >> 7) & 1 == 1
+ }
+
+ pub fn a_field(&self) -> u8 {
+ (self.0 >> 8) as u8
+ }
+
+ pub fn b_field(&self) -> u8 {
+ (self.0 >> 16) as u8
+ }
+
+ pub fn c_field(&self) -> u8 {
+ (self.0 >> 24) as u8
+ }
+
+ pub fn set_a_field(&mut self, bits: u8) {
+ self.0 = (self.0 & 0xFFFF00FF) | ((bits as u32) << 8);
+ }
+
+ pub fn set_b_field(&mut self, bits: u8) {
+ self.0 = (self.0 & 0xFFFF00FF) | ((bits as u32) << 16);
+ }
+
+ pub fn set_c_field(&mut self, bits: u8) {
+ self.0 = (self.0 & 0xFF00FFFF) | ((bits as u32) << 24);
+ }
+
+ pub fn decode(self) -> (Operation, InstructionData) {
+ (
+ self.operation(),
+ InstructionData {
+ a_field: self.a_field(),
+ b_field: self.b_field(),
+ c_field: self.c_field(),
+ b_is_constant: self.b_is_constant(),
+ c_is_constant: self.c_is_constant(),
+ d_field: self.d_field(),
+ },
+ )
+ }
+
+ pub fn point(from: u8, to: u8) -> Instruction {
+ Instruction::from(Point { from, to })
+ }
+
+ pub fn close(from: u8, to: u8) -> Instruction {
Instruction::from(Close { from, to })
}
- pub fn load_boolean(destination: Destination, value: bool, jump_next: bool) -> Instruction {
+ pub fn load_boolean(destination: u8, value: bool, jump_next: bool) -> Instruction {
Instruction::from(LoadBoolean {
destination,
value,
@@ -136,11 +247,7 @@ impl Instruction {
})
}
- pub fn load_constant(
- destination: Destination,
- constant_index: u16,
- jump_next: bool,
- ) -> Instruction {
+ pub fn load_constant(destination: u8, constant_index: u8, jump_next: bool) -> Instruction {
Instruction::from(LoadConstant {
destination,
constant_index,
@@ -148,40 +255,39 @@ impl Instruction {
})
}
- pub fn load_list(destination: Destination, start_register: u16) -> Instruction {
+ pub fn load_function(destination: u8, prototype_index: u8) -> Instruction {
+ Instruction::from(LoadFunction {
+ destination,
+ prototype_index,
+ })
+ }
+
+ pub fn load_list(destination: u8, start_register: u8) -> Instruction {
Instruction::from(LoadList {
destination,
start_register,
})
}
- pub fn load_self(destination: Destination) -> Instruction {
+ pub fn load_self(destination: u8) -> Instruction {
Instruction::from(LoadSelf { destination })
}
- pub fn define_local(register: u16, local_index: u16, is_mutable: bool) -> Instruction {
- Instruction::from(DefineLocal {
- local_index,
- register,
- is_mutable,
- })
- }
-
- pub fn get_local(destination: Destination, local_index: u16) -> Instruction {
+ pub fn get_local(destination: u8, local_index: u8) -> Instruction {
Instruction::from(GetLocal {
destination,
local_index,
})
}
- pub fn set_local(register: u16, local_index: u16) -> Instruction {
+ pub fn set_local(register: u8, local_index: u8) -> Instruction {
Instruction::from(SetLocal {
local_index,
- register,
+ register_index: register,
})
}
- pub fn add(destination: Destination, left: Argument, right: Argument) -> Instruction {
+ pub fn add(destination: u8, left: Argument, right: Argument) -> Instruction {
Instruction::from(Add {
destination,
left,
@@ -189,7 +295,7 @@ impl Instruction {
})
}
- pub fn subtract(destination: Destination, left: Argument, right: Argument) -> Instruction {
+ pub fn subtract(destination: u8, left: Argument, right: Argument) -> Instruction {
Instruction::from(Subtract {
destination,
left,
@@ -197,7 +303,7 @@ impl Instruction {
})
}
- pub fn multiply(destination: Destination, left: Argument, right: Argument) -> Instruction {
+ pub fn multiply(destination: u8, left: Argument, right: Argument) -> Instruction {
Instruction::from(Multiply {
destination,
left,
@@ -205,7 +311,7 @@ impl Instruction {
})
}
- pub fn divide(destination: Destination, left: Argument, right: Argument) -> Instruction {
+ pub fn divide(destination: u8, left: Argument, right: Argument) -> Instruction {
Instruction::from(Divide {
destination,
left,
@@ -213,7 +319,7 @@ impl Instruction {
})
}
- pub fn modulo(destination: Destination, left: Argument, right: Argument) -> Instruction {
+ pub fn modulo(destination: u8, left: Argument, right: Argument) -> Instruction {
Instruction::from(Modulo {
destination,
left,
@@ -221,14 +327,14 @@ impl Instruction {
})
}
- pub fn test(argument: Argument, value: bool) -> Instruction {
+ pub fn test(operand_register: u8, value: bool) -> Instruction {
Instruction::from(Test {
- argument,
+ operand_register,
test_value: value,
})
}
- pub fn test_set(destination: Destination, argument: Argument, value: bool) -> Instruction {
+ pub fn test_set(destination: u8, argument: Argument, value: bool) -> Instruction {
Instruction::from(TestSet {
destination,
argument,
@@ -248,39 +354,45 @@ impl Instruction {
Instruction::from(LessEqual { value, left, right })
}
- pub fn negate(destination: Destination, argument: Argument) -> Instruction {
+ pub fn negate(destination: u8, argument: Argument) -> Instruction {
Instruction::from(Negate {
destination,
argument,
})
}
- pub fn not(destination: Destination, argument: Argument) -> Instruction {
+ pub fn not(destination: u8, argument: Argument) -> Instruction {
Instruction::from(Not {
destination,
argument,
})
}
- pub fn jump(offset: u16, is_positive: bool) -> Instruction {
+ pub fn jump(offset: u8, is_positive: bool) -> Instruction {
Instruction::from(Jump {
offset,
is_positive,
})
}
- pub fn call(destination: Destination, function: Argument, argument_count: u16) -> Instruction {
+ pub fn call(
+ destination: u8,
+ function_register: u8,
+ argument_count: u8,
+ is_recursive: bool,
+ ) -> Instruction {
Instruction::from(Call {
destination,
- function,
+ function_register,
argument_count,
+ is_recursive,
})
}
pub fn call_native(
- destination: Destination,
+ destination: u8,
function: NativeFunction,
- argument_count: u16,
+ argument_count: u8,
) -> Instruction {
Instruction::from(CallNative {
destination,
@@ -289,393 +401,290 @@ impl Instruction {
})
}
- pub fn r#return(should_return_value: bool) -> Instruction {
+ pub fn r#return(should_return_value: bool, return_register: u8) -> Instruction {
Instruction::from(Return {
should_return_value,
+ return_register,
})
}
- pub fn destination_as_argument(&self) -> Option {
+ pub fn is_math(&self) -> bool {
+ matches!(
+ self.operation(),
+ Operation::ADD
+ | Operation::SUBTRACT
+ | Operation::MULTIPLY
+ | Operation::DIVIDE
+ | Operation::MODULO
+ )
+ }
+
+ pub fn is_comparison(&self) -> bool {
+ matches!(
+ self.operation(),
+ Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL
+ )
+ }
+
+ pub fn as_argument(&self) -> Option {
match self.operation() {
- Operation::LoadConstant => Some(Argument::Constant(self.b())),
- Operation::GetLocal => Some(Argument::Local(self.b())),
- Operation::LoadBoolean
- | Operation::LoadList
- | Operation::LoadSelf
- | Operation::Add
- | Operation::Subtract
- | Operation::Multiply
- | Operation::Divide
- | Operation::Modulo
- | Operation::Negate
- | Operation::Not
- | Operation::Call
- | Operation::CallNative => Some(Argument::Register(self.a())),
+ Operation::LOAD_CONSTANT => Some(Argument::Constant(self.b_field())),
+ Operation::LOAD_BOOLEAN
+ | Operation::LOAD_LIST
+ | Operation::LOAD_SELF
+ | Operation::GET_LOCAL
+ | Operation::ADD
+ | Operation::SUBTRACT
+ | Operation::MULTIPLY
+ | Operation::DIVIDE
+ | Operation::MODULO
+ | Operation::EQUAL
+ | Operation::LESS
+ | Operation::LESS_EQUAL
+ | Operation::NEGATE
+ | Operation::NOT
+ | Operation::CALL => Some(Argument::Register(self.a_field())),
+ Operation::CALL_NATIVE => {
+ let function = NativeFunction::from(self.b_field());
+
+ if function.returns_value() {
+ Some(Argument::Register(self.a_field()))
+ } else {
+ None
+ }
+ }
_ => None,
}
}
pub fn b_as_argument(&self) -> Argument {
if self.b_is_constant() {
- Argument::Constant(self.b())
- } else if self.b_is_local() {
- Argument::Local(self.b())
+ Argument::Constant(self.b_field())
} else {
- Argument::Register(self.b())
+ Argument::Register(self.b_field())
}
}
pub fn b_and_c_as_arguments(&self) -> (Argument, Argument) {
let left = if self.b_is_constant() {
- Argument::Constant(self.b())
- } else if self.b_is_local() {
- Argument::Local(self.b())
+ Argument::Constant(self.b_field())
} else {
- Argument::Register(self.b())
+ Argument::Register(self.b_field())
};
let right = if self.c_is_constant() {
- Argument::Constant(self.c())
- } else if self.c_is_local() {
- Argument::Local(self.c())
+ Argument::Constant(self.c_field())
} else {
- Argument::Register(self.c())
+ Argument::Register(self.c_field())
};
(left, right)
}
- pub fn operation(&self) -> Operation {
- Operation::from((self.0 & 0b11111111) as u8)
- }
-
- pub fn set_b_is_constant(&mut self, boolean: bool) -> &mut Self {
- self.0 = (self.0 & !(1 << 9)) | ((boolean as u64) << 9);
-
- self
- }
-
- pub fn set_c_is_constant(&mut self, boolean: bool) -> &mut Self {
- self.0 = (self.0 & !(1 << 10)) | ((boolean as u64) << 10);
-
- self
- }
-
- pub fn set_a_is_local(&mut self, boolean: bool) -> &mut Self {
- self.0 = (self.0 & !(1 << 11)) | ((boolean as u64) << 11);
-
- self
- }
-
- pub fn set_b_is_local(&mut self, boolean: bool) -> &mut Self {
- self.0 = (self.0 & !(1 << 12)) | ((boolean as u64) << 12);
-
- self
- }
-
- pub fn set_c_is_local(&mut self, boolean: bool) -> &mut Self {
- self.0 = (self.0 & !(1 << 13)) | ((boolean as u64) << 13);
-
- self
- }
-
- pub fn a(&self) -> u16 {
- ((self.0 >> 16) & 0b1111111111111111) as u16
- }
-
- pub fn a_as_boolean(&self) -> bool {
- self.a() != 0
- }
-
- pub fn set_a(&mut self, a: u16) -> &mut Self {
- self.0 = (self.0 & !(0b1111111111111111 << 16)) | ((a as u64) << 16);
-
- self
- }
-
- pub fn set_a_to_boolean(&mut self, boolean: bool) -> &mut Self {
- self.0 = (self.0 & !(0b1111111111111111 << 16)) | ((boolean as u64) << 16);
-
- self
- }
-
- pub fn b(&self) -> u16 {
- ((self.0 >> 32) & 0b1111111111111111) as u16
- }
-
- pub fn b_as_boolean(&self) -> bool {
- self.b() != 0
- }
-
- pub fn set_b(&mut self, b: u16) -> &mut Self {
- self.0 = (self.0 & !(0b1111111111111111 << 32)) | ((b as u64) << 32);
-
- self
- }
-
- pub fn set_b_to_boolean(&mut self, boolean: bool) -> &mut Self {
- self.0 = (self.0 & !(0b1111111111111111 << 32)) | ((boolean as u64) << 32);
-
- self
- }
-
- pub fn c(&self) -> u16 {
- ((self.0 >> 48) & 0b1111111111111111) as u16
- }
-
- pub fn c_as_boolean(&self) -> bool {
- self.c() != 0
- }
-
- pub fn set_c(&mut self, c: u16) -> &mut Self {
- self.0 = (self.0 & !(0b1111111111111111 << 48)) | ((c as u64) << 48);
-
- self
- }
-
- pub fn set_c_to_boolean(&mut self, boolean: bool) -> &mut Self {
- self.0 = (self.0 & !(0b1111111111111111 << 48)) | ((boolean as u64) << 48);
-
- self
- }
-
- pub fn b_is_constant(&self) -> bool {
- (self.0 >> 9) & 1 == 1
- }
-
- pub fn c_is_constant(&self) -> bool {
- (self.0 >> 10) & 1 == 1
- }
-
- pub fn a_is_local(&self) -> bool {
- (self.0 >> 11) & 1 == 1
- }
-
- pub fn b_is_local(&self) -> bool {
- (self.0 >> 12) & 1 == 1
- }
-
- pub fn c_is_local(&self) -> bool {
- (self.0 >> 13) & 1 == 1
- }
-
pub fn yields_value(&self) -> bool {
match self.operation() {
- Operation::LoadBoolean
- | Operation::LoadConstant
- | Operation::LoadList
- | Operation::LoadSelf
- | Operation::GetLocal
- | Operation::Add
- | Operation::Subtract
- | Operation::Multiply
- | Operation::Divide
- | Operation::Modulo
- | Operation::Negate
- | Operation::Not
- | Operation::Call => true,
- Operation::Move
- | Operation::Close
- | Operation::DefineLocal
- | Operation::SetLocal
- | Operation::Equal
- | Operation::Less
- | Operation::LessEqual
- | Operation::Test
- | Operation::TestSet
- | Operation::Jump
- | Operation::Return => false,
- Operation::CallNative => {
- let function = NativeFunction::from(self.b());
+ Operation::POINT
+ | Operation::LOAD_BOOLEAN
+ | Operation::LOAD_CONSTANT
+ | Operation::LOAD_FUNCTION
+ | Operation::LOAD_LIST
+ | Operation::LOAD_SELF
+ | Operation::GET_LOCAL
+ | Operation::ADD
+ | Operation::SUBTRACT
+ | Operation::MULTIPLY
+ | Operation::DIVIDE
+ | Operation::MODULO
+ | Operation::NEGATE
+ | Operation::NOT
+ | Operation::CALL => true,
+ Operation::CALL_NATIVE => {
+ let function = NativeFunction::from(self.b_field());
function.returns_value()
}
+ Operation::CLOSE
+ | Operation::SET_LOCAL
+ | Operation::EQUAL
+ | Operation::LESS
+ | Operation::LESS_EQUAL
+ | Operation::TEST
+ | Operation::TEST_SET
+ | Operation::JUMP
+ | Operation::RETURN => false,
+ _ => Operation::panic_from_unknown_code(self.operation().0),
}
}
pub fn disassembly_info(&self) -> String {
- match self.operation() {
- Operation::Move => {
- let Move { from, to } = Move::from(self);
+ let operation = self.operation();
- format!("R{to} = R{from}")
- }
- Operation::Close => {
- let Close { from, to } = Close::from(self);
+ match operation {
+ Operation::POINT => Point::from(*self).to_string(),
+ Operation::CLOSE => {
+ let Close { from, to } = Close::from(*self);
format!("R{from}..R{to}")
}
- Operation::LoadBoolean => {
+ Operation::LOAD_BOOLEAN => {
let LoadBoolean {
destination,
value,
jump_next,
- } = LoadBoolean::from(self);
+ } = LoadBoolean::from(*self);
if jump_next {
- format!("{destination} = {value} && JUMP +1")
+ format!("R{destination} = {value} && JUMP +1")
} else {
- format!("{destination} = {value}")
+ format!("R{destination} = {value}")
}
}
- Operation::LoadConstant => {
+ Operation::LOAD_CONSTANT => {
let LoadConstant {
destination,
constant_index,
jump_next,
- } = LoadConstant::from(self);
+ } = LoadConstant::from(*self);
if jump_next {
- format!("{destination} = C{constant_index} JUMP +1")
+ format!("R{destination} = C{constant_index} JUMP +1")
} else {
- format!("{destination} = C{constant_index}")
+ format!("R{destination} = C{constant_index}")
}
}
- Operation::LoadList => {
+ Operation::LOAD_FUNCTION => LoadFunction::from(*self).to_string(),
+ Operation::LOAD_LIST => {
let LoadList {
destination,
start_register,
- } = LoadList::from(self);
- let end_register = destination.index().saturating_sub(1);
+ } = LoadList::from(*self);
+ let end_register = destination.saturating_sub(1);
- format!("{destination} = [R{start_register}..=R{end_register}]",)
+ format!("R{destination} = [R{start_register}..=R{end_register}]",)
}
- Operation::LoadSelf => {
- let LoadSelf { destination } = LoadSelf::from(self);
+ Operation::LOAD_SELF => {
+ let LoadSelf { destination } = LoadSelf::from(*self);
- format!("{destination} = self")
+ format!("R{destination} = self")
}
- Operation::DefineLocal => {
- let DefineLocal {
- register,
- local_index,
- is_mutable,
- } = DefineLocal::from(self);
-
- if is_mutable {
- format!("mut L{local_index} = R{register}")
- } else {
- format!("L{local_index} = R{register}")
- }
- }
- Operation::GetLocal => {
+ Operation::GET_LOCAL => {
let GetLocal {
destination,
local_index,
- } = GetLocal::from(self);
+ } = GetLocal::from(*self);
- format!("{destination} = L{local_index}")
+ format!("R{destination} = L{local_index}")
}
- Operation::SetLocal => {
+ Operation::SET_LOCAL => {
let SetLocal {
- register,
+ register_index,
local_index,
- } = SetLocal::from(self);
+ } = SetLocal::from(*self);
- format!("L{local_index} = R{register}")
+ format!("L{local_index} = R{register_index}")
}
- Operation::Add => {
+ Operation::ADD => {
let Add {
destination,
left,
right,
- } = Add::from(self);
+ } = Add::from(*self);
- format!("{destination} = {left} + {right}")
+ format!("R{destination} = {left} + {right}")
}
- Operation::Subtract => {
+ Operation::SUBTRACT => {
let Subtract {
destination,
left,
right,
- } = Subtract::from(self);
+ } = Subtract::from(*self);
- format!("{destination} = {left} - {right}")
+ format!("R{destination} = {left} - {right}")
}
- Operation::Multiply => {
+ Operation::MULTIPLY => {
let Multiply {
destination,
left,
right,
- } = Multiply::from(self);
+ } = Multiply::from(*self);
- format!("{destination} = {left} * {right}")
+ format!("R{destination} = {left} * {right}")
}
- Operation::Divide => {
+ Operation::DIVIDE => {
let Divide {
destination,
left,
right,
- } = Divide::from(self);
+ } = Divide::from(*self);
- format!("{destination} = {left} / {right}")
+ format!("R{destination} = {left} / {right}")
}
- Operation::Modulo => {
+ Operation::MODULO => {
let Modulo {
destination,
left,
right,
- } = Modulo::from(self);
+ } = Modulo::from(*self);
- format!("{destination} = {left} % {right}")
+ format!("R{destination} = {left} % {right}")
}
- Operation::Test => {
+ Operation::TEST => {
let Test {
- argument,
- test_value: value,
- } = Test::from(self);
- let bang = if value { "" } else { "!" };
+ operand_register,
+ test_value,
+ } = Test::from(*self);
+ let bang = if test_value { "" } else { "!" };
- format!("if {bang}{argument} {{ JUMP +1 }}",)
+ format!("if {bang}R{operand_register} {{ JUMP +1 }}",)
}
- Operation::TestSet => {
+ Operation::TEST_SET => {
let TestSet {
destination,
argument,
- test_value: value,
- } = TestSet::from(self);
- let bang = if value { "" } else { "!" };
+ test_value,
+ } = TestSet::from(*self);
+ let bang = if test_value { "" } else { "!" };
- format!("if {bang}{argument} {{ JUMP +1 }} else {{ {destination} = {argument} }}")
+ format!("if {bang}{argument} {{ JUMP +1 }} else {{ R{destination} = {argument} }}")
}
- Operation::Equal => {
- let Equal { value, left, right } = Equal::from(self);
+ Operation::EQUAL => {
+ let Equal { value, left, right } = Equal::from(*self);
let comparison_symbol = if value { "==" } else { "!=" };
format!("if {left} {comparison_symbol} {right} {{ JUMP +1 }}")
}
- Operation::Less => {
- let Less { value, left, right } = Less::from(self);
+ Operation::LESS => {
+ let Less { value, left, right } = Less::from(*self);
let comparison_symbol = if value { "<" } else { ">=" };
format!("if {left} {comparison_symbol} {right} {{ JUMP +1 }}")
}
- Operation::LessEqual => {
- let LessEqual { value, left, right } = LessEqual::from(self);
+ Operation::LESS_EQUAL => {
+ let LessEqual { value, left, right } = LessEqual::from(*self);
let comparison_symbol = if value { "<=" } else { ">" };
format!("if {left} {comparison_symbol} {right} {{ JUMP +1 }}")
}
- Operation::Negate => {
+ Operation::NEGATE => {
let Negate {
destination,
argument,
- } = Negate::from(self);
+ } = Negate::from(*self);
- format!("{destination} = -{argument}")
+ format!("R{destination} = -{argument}")
}
- Operation::Not => {
+ Operation::NOT => {
let Not {
destination,
argument,
- } = Not::from(self);
+ } = Not::from(*self);
- format!("{destination} = !{argument}")
+ format!("R{destination} = !{argument}")
}
- Operation::Jump => {
+ Operation::JUMP => {
let Jump {
offset,
is_positive,
- } = Jump::from(self);
+ } = Jump::from(*self);
if is_positive {
format!("JUMP +{offset}")
@@ -683,103 +692,112 @@ impl Instruction {
format!("JUMP -{offset}")
}
}
- Operation::Call => {
+ Operation::CALL => {
let Call {
destination,
- function,
+ function_register,
argument_count,
- } = Call::from(self);
- let arguments_start = destination.index().saturating_sub(argument_count);
- let arguments_end = arguments_start + argument_count;
+ ..
+ } = Call::from(*self);
+ let arguments_start = destination.saturating_sub(argument_count);
- format!("{destination} = {function}(R{arguments_start}..R{arguments_end})")
+ match argument_count {
+ 0 => format!("R{destination} = R{function_register}()"),
+ 1 => format!("R{destination} = R{function_register}(R{arguments_start})"),
+ _ => {
+ format!(
+ "R{destination} = R{function_register}(R{arguments_start}..R{destination})"
+ )
+ }
+ }
}
- Operation::CallNative => {
+ Operation::CALL_NATIVE => {
let CallNative {
destination,
function,
argument_count,
- } = CallNative::from(self);
- let arguments_start = destination.index().saturating_sub(argument_count);
+ } = CallNative::from(*self);
+ let arguments_start = destination.saturating_sub(argument_count);
let arguments_end = arguments_start + argument_count;
-
- if function.returns_value() {
- format!("{destination} = {function}(R{arguments_start}..R{arguments_end})")
+ let mut info_string = if function.returns_value() {
+ format!("R{destination} = ")
} else {
- format!("{function}(R{arguments_start}..R{arguments_end})")
+ String::new()
+ };
+
+ match argument_count {
+ 0 => {
+ info_string.push_str(function.as_str());
+ info_string.push_str("()");
+ }
+ 1 => info_string.push_str(&format!("{function}(R{arguments_start})")),
+ _ => info_string
+ .push_str(&format!("{function}(R{arguments_start}..R{arguments_end})")),
}
+
+ info_string
}
- Operation::Return => {
+ Operation::RETURN => {
let Return {
should_return_value,
- } = Return::from(self);
+ return_register,
+ } = Return::from(*self);
if should_return_value {
- "RETURN".to_string()
+ format!("RETURN R{return_register}")
} else {
- "".to_string()
+ String::new()
+ }
+ }
+ _ => {
+ if cfg!(debug_assertions) {
+ panic!("Unknown operation {}", self.operation());
+ } else {
+ "RETURN".to_string()
}
}
}
}
}
-impl From<&Instruction> for u64 {
- fn from(instruction: &Instruction) -> Self {
- instruction.0
- }
-}
-
impl Debug for Instruction {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
- write!(f, "{} {}", self.operation(), self.disassembly_info())
+ write!(f, "{self}")
}
}
-#[derive(Debug, Clone, Copy)]
-pub enum Destination {
- Local(u16),
- Register(u16),
-}
-
-impl Destination {
- pub fn index(&self) -> u16 {
- match self {
- Destination::Local(index) => *index,
- Destination::Register(index) => *index,
- }
- }
-
- pub fn is_local(&self) -> bool {
- matches!(self, Destination::Local(_))
- }
-
- pub fn is_register(&self) -> bool {
- matches!(self, Destination::Register(_))
- }
-}
-
-impl Display for Destination {
+impl Display for Instruction {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- match self {
- Destination::Local(index) => write!(f, "L{index}"),
- Destination::Register(index) => write!(f, "R{index}"),
- }
+ write!(f, "{} | {}", self.operation(), self.disassembly_info())
}
}
-#[derive(Debug, Clone, Copy)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
+pub struct InstructionData {
+ pub a_field: u8,
+ pub b_field: u8,
+ pub c_field: u8,
+ pub d_field: bool,
+ pub b_is_constant: bool,
+ pub c_is_constant: bool,
+}
+
+impl Display for InstructionData {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "{self:?}")
+ }
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum Argument {
- Constant(u16),
- Local(u16),
- Register(u16),
+ Constant(u8),
+ Register(u8),
}
impl Argument {
- pub fn index(&self) -> u16 {
+ pub fn index(&self) -> u8 {
match self {
Argument::Constant(index) => *index,
- Argument::Local(index) => *index,
Argument::Register(index) => *index,
}
}
@@ -788,20 +806,22 @@ impl Argument {
matches!(self, Argument::Constant(_))
}
- pub fn is_local(&self) -> bool {
- matches!(self, Argument::Local(_))
- }
-
pub fn is_register(&self) -> bool {
matches!(self, Argument::Register(_))
}
+
+ pub fn as_index_and_constant_flag(&self) -> (u8, bool) {
+ match self {
+ Argument::Constant(index) => (*index, true),
+ Argument::Register(index) => (*index, false),
+ }
+ }
}
impl Display for Argument {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Argument::Constant(index) => write!(f, "C{index}"),
- Argument::Local(index) => write!(f, "L{index}"),
Argument::Register(index) => write!(f, "R{index}"),
}
}
diff --git a/dust-lang/src/instruction/modulo.rs b/dust-lang/src/instruction/modulo.rs
index c50205d..9ba0f5b 100644
--- a/dust-lang/src/instruction/modulo.rs
+++ b/dust-lang/src/instruction/modulo.rs
@@ -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 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 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)
}
}
diff --git a/dust-lang/src/instruction/move.rs b/dust-lang/src/instruction/move.rs
deleted file mode 100644
index fe00943..0000000
--- a/dust-lang/src/instruction/move.rs
+++ /dev/null
@@ -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 for Instruction {
- fn from(r#move: Move) -> Self {
- *Instruction::new(Operation::Move)
- .set_b(r#move.from)
- .set_c(r#move.to)
- }
-}
diff --git a/dust-lang/src/instruction/multiply.rs b/dust-lang/src/instruction/multiply.rs
index 023a4d3..fd73ff3 100644
--- a/dust-lang/src/instruction/multiply.rs
+++ b/dust-lang/src/instruction/multiply.rs
@@ -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 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 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)
}
}
diff --git a/dust-lang/src/instruction/negate.rs b/dust-lang/src/instruction/negate.rs
index 1340c9a..405db8d 100644
--- a/dust-lang/src/instruction/negate.rs
+++ b/dust-lang/src/instruction/negate.rs
@@ -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 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 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)
}
}
diff --git a/dust-lang/src/instruction/not.rs b/dust-lang/src/instruction/not.rs
index 805e7d1..70d7dd5 100644
--- a/dust-lang/src/instruction/not.rs
+++ b/dust-lang/src/instruction/not.rs
@@ -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 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 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)
}
}
diff --git a/dust-lang/src/instruction/operation.rs b/dust-lang/src/instruction/operation.rs
new file mode 100644
index 0000000..0ab3851
--- /dev/null
+++ b/dust-lang/src/instruction/operation.rs
@@ -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())
+ }
+}
diff --git a/dust-lang/src/instruction/point.rs b/dust-lang/src/instruction/point.rs
new file mode 100644
index 0000000..fe4d43b
--- /dev/null
+++ b/dust-lang/src/instruction/point.rs
@@ -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 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 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)
+ }
+}
diff --git a/dust-lang/src/instruction/return.rs b/dust-lang/src/instruction/return.rs
index 557d045..b3c52c5 100644
--- a/dust-lang/src/instruction/return.rs
+++ b/dust-lang/src/instruction/return.rs
@@ -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 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 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)
}
}
diff --git a/dust-lang/src/instruction/set_local.rs b/dust-lang/src/instruction/set_local.rs
index 6f1c747..6e6ebf1 100644
--- a/dust-lang/src/instruction/set_local.rs
+++ b/dust-lang/src/instruction/set_local.rs
@@ -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 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 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)
}
}
diff --git a/dust-lang/src/instruction/subtract.rs b/dust-lang/src/instruction/subtract.rs
index 482ce66..3923719 100644
--- a/dust-lang/src/instruction/subtract.rs
+++ b/dust-lang/src/instruction/subtract.rs
@@ -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 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 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)
}
}
diff --git a/dust-lang/src/instruction/test.rs b/dust-lang/src/instruction/test.rs
index 08bf025..138483a 100644
--- a/dust-lang/src/instruction/test.rs
+++ b/dust-lang/src/instruction/test.rs
@@ -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 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 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,
+ )
}
}
diff --git a/dust-lang/src/instruction/test_set.rs b/dust-lang/src/instruction/test_set.rs
index a9a6faa..27a15e5 100644
--- a/dust-lang/src/instruction/test_set.rs
+++ b/dust-lang/src/instruction/test_set.rs
@@ -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 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 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)
}
}
diff --git a/dust-lang/src/lexer.rs b/dust-lang/src/lexer.rs
index 604b907..0c09983 100644
--- a/dust-lang/src/lexer.rs
+++ b/dust-lang/src/lexer.rs
@@ -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, 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, 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 {
- 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);
- }
-
- 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 detail_snippets(&self) -> Vec<(String, Span)> {
+ Vec::with_capacity(0)
}
- 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)
}
}
diff --git a/dust-lang/src/lib.rs b/dust-lang/src/lib.rs
index 4bea669..f1e8dbd 100644
--- a/dust-lang/src/lib.rs
+++ b/dust-lang/src/lib.rs
@@ -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;
diff --git a/dust-lang/src/native_function/assert.rs b/dust-lang/src/native_function/assert.rs
new file mode 100644
index 0000000..b2704a4
--- /dev/null
+++ b/dust-lang/src/native_function/assert.rs
@@ -0,0 +1,22 @@
+use std::{ops::Range, panic};
+
+use crate::vm::ThreadData;
+
+pub fn panic(data: &mut ThreadData, _: Option, argument_range: Range) -> 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)
+}
diff --git a/dust-lang/src/native_function/io.rs b/dust-lang/src/native_function/io.rs
new file mode 100644
index 0000000..0267009
--- /dev/null
+++ b/dust-lang/src/native_function/io.rs
@@ -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,
+ _argument_range: Range,
+) -> 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, argument_range: Range) -> 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,
+ argument_range: Range,
+) -> 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
+}
diff --git a/dust-lang/src/native_function/logic.rs b/dust-lang/src/native_function/logic.rs
deleted file mode 100644
index 1dae66d..0000000
--- a/dust-lang/src/native_function/logic.rs
+++ /dev/null
@@ -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