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, VmError> { - let argument_count = instruction.c(); - let message = if argument_count == 0 { - None - } else { - let mut message = String::new(); - - for argument_index in 0..argument_count { - if argument_index != 0 { - message.push(' '); - } - - let argument = if let Some(value) = vm.open_register_allow_empty(argument_index)? { - value - } else { - continue; - }; - let argument_string = argument.display(vm)?; - - message.push_str(&argument_string); - } - - Some(message) - }; - - Err(VmError::NativeFunction(NativeFunctionError::Panic { - message, - position: vm.current_position(), - })) -} - -pub fn to_string<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result, VmError> { - let argument_count = instruction.c(); - - if argument_count != 1 { - return Err(VmError::NativeFunction( - NativeFunctionError::ExpectedArgumentCount { - expected: 1, - found: argument_count as usize, - position: vm.current_position(), - }, - )); - } - - let mut string = String::new(); - - for argument_index in 0..argument_count { - let argument = if let Some(value) = vm.open_register_allow_empty(argument_index)? { - value - } else { - continue; - }; - let argument_string = argument.display(vm)?; - - string.push_str(&argument_string); - } - - Ok(Some(Value::Concrete(ConcreteValue::String(string)))) -} - -pub fn read_line<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result, VmError> { - let argument_count = instruction.c(); - - if argument_count != 0 { - return Err(VmError::NativeFunction( - NativeFunctionError::ExpectedArgumentCount { - expected: 0, - found: argument_count as usize, - position: vm.current_position(), - }, - )); - } - - let mut buffer = String::new(); - - match io::stdin().read_line(&mut buffer) { - Ok(_) => { - buffer = buffer.trim_end_matches('\n').to_string(); - - Ok(Some(Value::Concrete(ConcreteValue::String(buffer)))) - } - Err(error) => Err(VmError::NativeFunction(NativeFunctionError::Io { - error: error.kind(), - position: vm.current_position(), - })), - } -} - -pub fn write<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result, VmError> { - let to_register = instruction.a(); - let argument_count = instruction.c(); - let mut stdout = stdout(); - let map_err = |io_error: io::Error| { - VmError::NativeFunction(NativeFunctionError::Io { - error: io_error.kind(), - position: vm.current_position(), - }) - }; - - let first_argument = to_register.saturating_sub(argument_count); - - for argument_index in first_argument..to_register { - let argument = if let Some(value) = vm.open_register_allow_empty(argument_index)? { - value - } else { - continue; - }; - let argument_string = argument.display(vm)?; - - stdout - .write_all(argument_string.as_bytes()) - .map_err(map_err)?; - } - - Ok(None) -} - -pub fn write_line<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result, VmError> { - let to_register = instruction.a(); - let argument_count = instruction.c(); - let mut stdout = stdout(); - let map_err = |io_error: io::Error| { - VmError::NativeFunction(NativeFunctionError::Io { - error: io_error.kind(), - position: vm.current_position(), - }) - }; - - let first_argument = to_register.saturating_sub(argument_count); - - for argument_index in first_argument..to_register { - let argument = if let Some(value) = vm.open_register_allow_empty(argument_index)? { - value - } else { - continue; - }; - let argument_string = argument.display(vm)?; - - stdout - .write_all(argument_string.as_bytes()) - .map_err(map_err)?; - } - - stdout.write(b"\n").map_err(map_err)?; - - Ok(None) -} diff --git a/dust-lang/src/native_function/mod.rs b/dust-lang/src/native_function/mod.rs index 2ba0aef..e56f54a 100644 --- a/dust-lang/src/native_function/mod.rs +++ b/dust-lang/src/native_function/mod.rs @@ -2,17 +2,20 @@ //! //! Native functions are used to implement features that are not possible to implement in Dust //! itself or that are more efficient to implement in Rust. -mod logic; +mod assert; +mod io; +mod string; use std::{ fmt::{self, Display, Formatter}, - io::{self}, - string::{self}, + io::ErrorKind as IoErrorKind, + ops::Range, + string::ParseError, }; use serde::{Deserialize, Serialize}; -use crate::{AnnotatedError, FunctionType, Instruction, Span, Type, Value, Vm, VmError}; +use crate::{vm::ThreadData, AnnotatedError, FunctionType, Span, Type}; macro_rules! define_native_function { ($(($name:ident, $bytes:literal, $str:expr, $type:expr, $function:expr)),*) => { @@ -29,12 +32,13 @@ macro_rules! define_native_function { impl NativeFunction { pub fn call( &self, - vm: &mut Vm, - instruction: Instruction, - ) -> Result, VmError> { + data: &mut ThreadData, + destination: Option, + argument_range: Range, + ) -> bool { match self { $( - NativeFunction::$name => $function(vm, instruction), + NativeFunction::$name => $function(data, destination, argument_range), )* } } @@ -68,14 +72,14 @@ macro_rules! define_native_function { pub fn returns_value(&self) -> bool { match self { $( - NativeFunction::$name => *$type.return_type != Type::None, + NativeFunction::$name => $type.return_type != Type::None, )* } } } - impl From for NativeFunction { - fn from(bytes: u16) -> Self { + impl From for NativeFunction { + fn from(bytes: u8) -> Self { match bytes { $( $bytes => NativeFunction::$name, @@ -129,11 +133,11 @@ define_native_function! { 3, "panic", FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::None) + type_parameters: Vec::with_capacity(0), + value_parameters: Vec::with_capacity(0), + return_type: Type::None }, - logic::panic + assert::panic ), // // Type conversion @@ -146,11 +150,11 @@ define_native_function! { 8, "to_string", FunctionType { - type_parameters: None, - value_parameters: Some(vec![(0, Type::Any)]), - return_type: Box::new(Type::String) + type_parameters: Vec::with_capacity(0), + value_parameters: vec![(0, Type::Any)], + return_type: Type::String }, - logic::to_string + string::to_string ), // // List and string @@ -207,11 +211,11 @@ define_native_function! { 50, "read_line", FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::String) + type_parameters: Vec::with_capacity(0), + value_parameters: Vec::with_capacity(0), + return_type: Type::String }, - logic::read_line + io::read_line ), // (ReadTo, 51_u8, "read_to", false), // (ReadUntil, 52_u8, "read_until", true), @@ -223,11 +227,11 @@ define_native_function! { 55, "write", FunctionType { - type_parameters: None, - value_parameters: Some(vec![(0, Type::String)]), - return_type: Box::new(Type::None) + type_parameters: Vec::with_capacity(0), + value_parameters: vec![(0, Type::String)], + return_type: Type::None }, - logic::write + io::write ), // (WriteFile, 56_u8, "write_file", false), ( @@ -235,11 +239,11 @@ define_native_function! { 57, "write_line", FunctionType { - type_parameters: None, - value_parameters: Some(vec![(0, Type::String)]), - return_type: Box::new(Type::None) + type_parameters: Vec::with_capacity(0), + value_parameters: vec![(0, Type::String)], + return_type: Type::None }, - logic::write_line + io::write_line ) // // Random @@ -255,15 +259,15 @@ pub enum NativeFunctionError { position: Span, }, Panic { - message: Option, + message: String, position: Span, }, Parse { - error: string::ParseError, + error: ParseError, position: Span, }, Io { - error: io::ErrorKind, + error: IoErrorKind, position: Span, }, } @@ -284,23 +288,29 @@ impl AnnotatedError for NativeFunctionError { } } - fn details(&self) -> Option { + fn detail_snippets(&self) -> Vec<(String, Span)> { match self { NativeFunctionError::ExpectedArgumentCount { - expected, found, .. - } => Some(format!("Expected {} arguments, found {}", expected, found)), - NativeFunctionError::Panic { message, .. } => message.clone(), - NativeFunctionError::Parse { error, .. } => Some(format!("{}", error)), - NativeFunctionError::Io { error, .. } => Some(format!("{}", error)), + expected, + found, + position, + } => vec![( + format!("Expected {expected} arguments, found {found}"), + *position, + )], + NativeFunctionError::Panic { message, position } => { + vec![(format!("Dust panic!\n{message}"), *position)] + } + NativeFunctionError::Parse { error, position } => { + vec![(format!("{error}"), *position)] + } + NativeFunctionError::Io { error, position } => { + vec![(format!("{error}"), *position)] + } } } - fn position(&self) -> Span { - match self { - NativeFunctionError::ExpectedArgumentCount { position, .. } => *position, - NativeFunctionError::Panic { position, .. } => *position, - NativeFunctionError::Parse { position, .. } => *position, - NativeFunctionError::Io { position, .. } => *position, - } + fn help_snippets(&self) -> Vec<(String, Span)> { + Vec::with_capacity(0) } } diff --git a/dust-lang/src/native_function/string.rs b/dust-lang/src/native_function/string.rs new file mode 100644 index 0000000..f368185 --- /dev/null +++ b/dust-lang/src/native_function/string.rs @@ -0,0 +1,23 @@ +use std::ops::Range; + +use crate::{ + vm::{get_next_action, Register, ThreadData}, + ConcreteValue, Value, +}; + +pub fn to_string( + data: &mut ThreadData, + destination: Option, + argument_range: Range, +) -> bool { + let argument_value = data.open_register_unchecked(argument_range.start); + let argument_string = argument_value.display(data); + let destination = destination.unwrap(); + let register = Register::Value(Value::Concrete(ConcreteValue::string(argument_string))); + + data.set_register(destination, register); + + data.next_action = get_next_action(data); + + false +} diff --git a/dust-lang/src/operation.rs b/dust-lang/src/operation.rs deleted file mode 100644 index 1b35e71..0000000 --- a/dust-lang/src/operation.rs +++ /dev/null @@ -1,112 +0,0 @@ -//! Part of an [Instruction][crate::Instruction] that is encoded as a single byte. - -use std::fmt::{self, Display, Formatter}; - -macro_rules! define_operation { - ($(($name:ident, $byte:literal, $str:expr)),*) => { - /// Part of an [Instruction][crate::Instruction] that is encoded as a single byte. - #[derive(Clone, Copy, Debug, PartialEq)] - pub enum Operation { - $( - $name = $byte as isize, - )* - } - - impl From for Operation { - fn from(byte: u8) -> Self { - match byte { - $( - $byte => Operation::$name, - )* - _ => { - if cfg!(test) { - panic!("Invalid operation byte: {}", byte) - } else { - Operation::Return - } - } - } - } - } - - impl From for u8 { - fn from(operation: Operation) -> Self { - match operation { - $( - Operation::$name => $byte, - )* - } - } - } - - impl Display for Operation { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - $( - Operation::$name => write!(f, "{}", $str), - )* - } - } - } - } -} - -define_operation! { - (Move, 0, "MOVE"), - (Close, 1, "CLOSE"), - - (LoadBoolean, 2, "LOAD_BOOLEAN"), - (LoadConstant, 3, "LOAD_CONSTANT"), - (LoadList, 4, "LOAD_LIST"), - (LoadSelf, 5, "LOAD_SELF"), - - (DefineLocal, 6, "DEFINE_LOCAL"), - (GetLocal, 7, "GET_LOCAL"), - (SetLocal, 8, "SET_LOCAL"), - - (Add, 9, "ADD"), - (Subtract, 10, "SUBTRACT"), - (Multiply, 11, "MULTIPLY"), - (Divide, 12, "DIVIDE"), - (Modulo, 13, "MODULO"), - - (Test, 14, "TEST"), - (TestSet, 15, "TEST_SET"), - - (Equal, 16, "EQUAL"), - (Less, 17, "LESS"), - (LessEqual, 18, "LESS_EQUAL"), - - (Negate, 19, "NEGATE"), - (Not, 20, "NOT"), - - (Call, 21, "CALL"), - (CallNative, 22, "CALL_NATIVE"), - - (Jump, 23, "JUMP"), - (Return, 24, "RETURN") -} - -impl Operation { - pub fn is_math(&self) -> bool { - matches!( - self, - Operation::Add - | Operation::Subtract - | Operation::Multiply - | Operation::Divide - | Operation::Modulo - ) - } - - pub fn is_comparison(&self) -> bool { - matches!( - self, - Operation::Equal | Operation::Less | Operation::LessEqual - ) - } - - pub fn is_test(&self) -> bool { - matches!(self, Operation::Test | Operation::TestSet) - } -} diff --git a/dust-lang/src/optimize.rs b/dust-lang/src/optimize.rs deleted file mode 100644 index ddfc609..0000000 --- a/dust-lang/src/optimize.rs +++ /dev/null @@ -1,111 +0,0 @@ -//! Tools used by the compiler to optimize a chunk's bytecode. - -use crate::{instruction::SetLocal, Instruction, Operation, Span, Type}; - -fn get_last_operations( - instructions: &[(Instruction, Type, Span)], -) -> Option<[Operation; COUNT]> { - let mut n_operations = [Operation::Return; COUNT]; - - for (nth, operation) in n_operations.iter_mut().rev().zip( - instructions - .iter() - .rev() - .map(|(instruction, _, _)| instruction.operation()), - ) { - *nth = operation; - } - - Some(n_operations) -} - -/// Optimizes a short control flow pattern. -/// -/// Comparison and test instructions (which are always followed by a JUMP) can be optimized when -/// the next instructions are two constant or boolean loaders. The first loader is set to skip -/// an instruction if it is run while the second loader is modified to use the first's register. -/// This makes the following two code snippets compile to the same bytecode: -/// -/// ```dust -/// 4 == 4 -/// ``` -/// -/// ```dust -/// if 4 == 4 { true } else { false } -/// ``` -/// -/// The instructions must be in the following order: -/// - `Equal`, `Less`, `LessEqual` or `Test` -/// - `Jump` -/// - `LoadBoolean` or `LoadConstant` -/// - `LoadBoolean` or `LoadConstant` -pub fn optimize_control_flow(instructions: &mut [(Instruction, Type, Span)]) { - if !matches!( - get_last_operations(instructions), - Some([ - Operation::Equal | Operation::Less | Operation::LessEqual | Operation::Test, - Operation::Jump, - Operation::LoadBoolean | Operation::LoadConstant, - Operation::LoadBoolean | Operation::LoadConstant, - ]) - ) { - return; - } - - log::debug!("Consolidating registers for control flow optimization"); - - let first_loader = &mut instructions.iter_mut().nth_back(1).unwrap().0; - - first_loader.set_c_to_boolean(true); - - let first_loader_register = first_loader.a(); - let second_loader = &mut instructions.last_mut().unwrap().0; - let second_loader_new = *second_loader.clone().set_a(first_loader_register); - - *second_loader = second_loader_new; -} - -/// Optimizes a math instruction followed by a SetLocal instruction. -/// -/// The SetLocal instruction is removed and the math instruction is modified to use the local as -/// its destination. This makes the following two code snippets compile to the same bytecode: -/// -/// ```dust -/// let a = 0; -/// a = a + 1; -/// ``` -/// -/// ```dust -/// let a = 0; -/// a += 1; -/// ``` -/// -/// The instructions must be in the following order: -/// - `Add`, `Subtract`, `Multiply`, `Divide` or `Modulo` -/// - `SetLocal` -pub fn optimize_set_local(instructions: &mut Vec<(Instruction, Type, Span)>) { - if !matches!( - get_last_operations(instructions), - Some([ - Operation::Add - | Operation::Subtract - | Operation::Multiply - | Operation::Divide - | Operation::Modulo, - Operation::SetLocal, - ]) - ) { - return; - } - - log::debug!("Condensing math and SetLocal to math instruction"); - - let set_local = SetLocal::from(&instructions.pop().unwrap().0); - let math_instruction = instructions.last_mut().unwrap().0; - let math_instruction_new = *math_instruction - .clone() - .set_a(set_local.local_index) - .set_a_is_local(true); - - instructions.last_mut().unwrap().0 = math_instruction_new; -} diff --git a/dust-lang/src/token.rs b/dust-lang/src/token.rs index 0bf75e5..feb6226 100644 --- a/dust-lang/src/token.rs +++ b/dust-lang/src/token.rs @@ -1,32 +1,8 @@ //! Token, TokenOwned and TokenKind types. -use std::{ - fmt::{self, Display, Formatter}, - io::Write, -}; +use std::fmt::{self, Display, Formatter}; -use colored::Colorize; use serde::{Deserialize, Serialize}; -use crate::Span; - -pub fn write_token_list(tokens: &[(Token, Span)], styled: bool, writer: &mut W) { - const HEADER: [&str; 2] = [" TOKEN POSITION ", "------------- ----------"]; - - writeln!(writer, "{}", HEADER[0]).unwrap(); - writeln!(writer, "{}", HEADER[1]).unwrap(); - - for (token, position) in tokens { - let token = if styled { - format!("{:^13}", token.as_str().bold()) - } else { - token.to_string() - }; - let position = position.to_string(); - - writeln!(writer, "{token:^13} {position:^10}").unwrap(); - } -} - macro_rules! define_tokens { ($($variant:ident $(($data_type:ty))?),+) => { /// Source token. @@ -116,7 +92,7 @@ define_tokens! { StarEqual } -impl<'src> Token<'src> { +impl Token<'_> { #[allow(clippy::len_without_is_empty)] pub fn len(&self) -> usize { match self { @@ -405,7 +381,7 @@ impl<'src> Token<'src> { } } -impl<'src> Display for Token<'src> { +impl Display for Token<'_> { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Token::ArrowThin => write!(f, "->"), @@ -603,63 +579,6 @@ impl Display for TokenOwned { impl Display for TokenKind { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - TokenKind::ArrowThin => Token::ArrowThin.fmt(f), - TokenKind::Async => Token::Async.fmt(f), - TokenKind::Bang => Token::Bang.fmt(f), - TokenKind::BangEqual => Token::BangEqual.fmt(f), - TokenKind::Bool => Token::Bool.fmt(f), - TokenKind::Boolean => write!(f, "boolean"), - TokenKind::Break => Token::Break.fmt(f), - TokenKind::Byte => write!(f, "byte"), - TokenKind::Character => write!(f, "character"), - TokenKind::Colon => Token::Colon.fmt(f), - TokenKind::Comma => Token::Comma.fmt(f), - TokenKind::Dot => Token::Dot.fmt(f), - TokenKind::DoubleAmpersand => Token::DoubleAmpersand.fmt(f), - TokenKind::DoubleDot => Token::DoubleDot.fmt(f), - TokenKind::DoubleEqual => Token::DoubleEqual.fmt(f), - TokenKind::DoublePipe => Token::DoublePipe.fmt(f), - TokenKind::Else => Token::Else.fmt(f), - TokenKind::Eof => Token::Eof.fmt(f), - TokenKind::Equal => Token::Equal.fmt(f), - TokenKind::Float => write!(f, "float"), - TokenKind::FloatKeyword => Token::FloatKeyword.fmt(f), - TokenKind::Fn => Token::Fn.fmt(f), - TokenKind::Greater => Token::Greater.fmt(f), - TokenKind::GreaterEqual => Token::GreaterEqual.fmt(f), - TokenKind::Identifier => write!(f, "identifier"), - TokenKind::If => Token::If.fmt(f), - TokenKind::Int => Token::Int.fmt(f), - TokenKind::Integer => write!(f, "integer"), - TokenKind::LeftBrace => Token::LeftBrace.fmt(f), - TokenKind::LeftParenthesis => Token::LeftParenthesis.fmt(f), - TokenKind::LeftBracket => Token::LeftBracket.fmt(f), - TokenKind::Let => Token::Let.fmt(f), - TokenKind::Less => Token::Less.fmt(f), - TokenKind::LessEqual => Token::LessEqual.fmt(f), - TokenKind::Loop => Token::Loop.fmt(f), - TokenKind::Map => Token::Map.fmt(f), - TokenKind::Minus => Token::Minus.fmt(f), - TokenKind::MinusEqual => Token::MinusEqual.fmt(f), - TokenKind::Mut => Token::Mut.fmt(f), - TokenKind::Percent => Token::Percent.fmt(f), - TokenKind::PercentEqual => Token::PercentEqual.fmt(f), - TokenKind::Plus => Token::Plus.fmt(f), - TokenKind::PlusEqual => Token::PlusEqual.fmt(f), - TokenKind::Return => Token::Return.fmt(f), - TokenKind::RightBrace => Token::RightBrace.fmt(f), - TokenKind::RightParenthesis => Token::RightParenthesis.fmt(f), - TokenKind::RightBracket => Token::RightBracket.fmt(f), - TokenKind::Semicolon => Token::Semicolon.fmt(f), - TokenKind::Star => Token::Star.fmt(f), - TokenKind::StarEqual => Token::StarEqual.fmt(f), - TokenKind::Str => Token::Str.fmt(f), - TokenKind::Slash => Token::Slash.fmt(f), - TokenKind::SlashEqual => Token::SlashEqual.fmt(f), - TokenKind::String => write!(f, "string"), - TokenKind::Struct => Token::Struct.fmt(f), - TokenKind::While => Token::While.fmt(f), - } + write!(f, "{self:?}") } } diff --git a/dust-lang/src/type.rs b/dust-lang/src/type.rs index 5fbd53d..e6264c4 100644 --- a/dust-lang/src/type.rs +++ b/dust-lang/src/type.rs @@ -16,7 +16,7 @@ pub enum Type { Character, Enum(EnumType), Float, - Function(FunctionType), + Function(Box), Generic { identifier_index: u8, concrete_type: Option>, @@ -24,21 +24,29 @@ pub enum Type { Integer, List(Box), Map { - pairs: HashMap, + pairs: Vec<(u8, Type)>, }, None, Range { r#type: Box, }, - SelfChunk, + SelfFunction, String, Struct(StructType), Tuple { - fields: Option>, + fields: Vec, }, } impl Type { + pub fn function(function_type: FunctionType) -> Self { + Type::Function(Box::new(function_type)) + } + + pub fn list(element_type: Type) -> Self { + Type::List(Box::new(element_type)) + } + /// Returns a concrete type, either the type itself or the concrete type of a generic type. pub fn concrete_type(&self) -> &Type { if let Type::Generic { @@ -107,18 +115,18 @@ impl Type { return Ok(()); } - ( - Type::Function(FunctionType { + (Type::Function(left_function_type), Type::Function(right_function_type)) => { + let FunctionType { type_parameters: left_type_parameters, value_parameters: left_value_parameters, return_type: left_return, - }), - Type::Function(FunctionType { + } = left_function_type.as_ref(); + let FunctionType { type_parameters: right_type_parameters, value_parameters: right_value_parameters, return_type: right_return, - }), - ) => { + } = right_function_type.as_ref(); + if left_return != right_return || left_type_parameters != right_type_parameters || left_value_parameters != right_value_parameters @@ -185,25 +193,21 @@ impl Display for Type { } Type::None => write!(f, "none"), Type::Range { r#type } => write!(f, "{type} range"), - Type::SelfChunk => write!(f, "self"), + Type::SelfFunction => write!(f, "self"), Type::String => write!(f, "str"), Type::Struct(struct_type) => write!(f, "{struct_type}"), Type::Tuple { fields } => { - if let Some(fields) = fields { - write!(f, "(")?; + write!(f, "(")?; - for (index, r#type) in fields.iter().enumerate() { - write!(f, "{type}")?; - - if index != fields.len() - 1 { - write!(f, ", ")?; - } + for (index, r#type) in fields.iter().enumerate() { + if index > 0 { + write!(f, ", ")?; } - write!(f, ")") - } else { - write!(f, "tuple") + write!(f, "{type}")?; } + + write!(f, ")") } } } @@ -252,8 +256,8 @@ impl Ord for Type { left_type.cmp(right_type) } (Type::Range { .. }, _) => Ordering::Greater, - (Type::SelfChunk, Type::SelfChunk) => Ordering::Equal, - (Type::SelfChunk, _) => Ordering::Greater, + (Type::SelfFunction, Type::SelfFunction) => Ordering::Equal, + (Type::SelfFunction, _) => Ordering::Greater, (Type::String, Type::String) => Ordering::Equal, (Type::String { .. }, _) => Ordering::Greater, (Type::Struct(left_struct), Type::Struct(right_struct)) => { @@ -269,19 +273,33 @@ impl Ord for Type { #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] pub struct FunctionType { - pub type_parameters: Option>, - pub value_parameters: Option>, - pub return_type: Box, + pub type_parameters: Vec, + pub value_parameters: Vec<(u8, Type)>, + pub return_type: Type, +} + +impl FunctionType { + pub fn new>, U: Into>>( + type_parameters: T, + value_parameters: U, + return_type: Type, + ) -> Self { + FunctionType { + type_parameters: type_parameters.into(), + value_parameters: value_parameters.into(), + return_type, + } + } } impl Display for FunctionType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "fn ")?; - if let Some(type_parameters) = &self.type_parameters { + if !self.type_parameters.is_empty() { write!(f, "<")?; - for (index, type_parameter) in type_parameters.iter().enumerate() { + for (index, type_parameter) in self.type_parameters.iter().enumerate() { if index > 0 { write!(f, ", ")?; } @@ -294,19 +312,19 @@ impl Display for FunctionType { write!(f, "(")?; - if let Some(value_parameters) = &self.value_parameters { - for (index, (identifier, r#type)) in value_parameters.iter().enumerate() { + if !self.value_parameters.is_empty() { + for (index, (_, r#type)) in self.value_parameters.iter().enumerate() { if index > 0 { write!(f, ", ")?; } - write!(f, "{identifier}: {type}")?; + write!(f, "{type}")?; } } write!(f, ")")?; - if *self.return_type != Type::None { + if self.return_type != Type::None { write!(f, " -> {}", self.return_type)?; } diff --git a/dust-lang/src/value/abstract_list.rs b/dust-lang/src/value/abstract_list.rs new file mode 100644 index 0000000..f678d77 --- /dev/null +++ b/dust-lang/src/value/abstract_list.rs @@ -0,0 +1,45 @@ +use std::fmt::{self, Display, Formatter}; + +use crate::{vm::ThreadData, Pointer, Type}; + +use super::DustString; + +#[derive(Clone, Debug, PartialEq, PartialOrd)] +pub struct AbstractList { + pub item_type: Type, + pub item_pointers: Vec, +} + +impl AbstractList { + pub fn display(&self, data: &ThreadData) -> DustString { + let mut display = DustString::new(); + + display.push('['); + + for (i, item) in self.item_pointers.iter().enumerate() { + if i > 0 { + display.push_str(", "); + } + + let item_display = data.follow_pointer_unchecked(*item).display(data); + + display.push_str(&item_display); + } + + display.push(']'); + + display + } +} + +impl Display for AbstractList { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "[")?; + + for pointer in &self.item_pointers { + write!(f, "{}", pointer)?; + } + + write!(f, "]") + } +} diff --git a/dust-lang/src/value/abstract_value.rs b/dust-lang/src/value/abstract_value.rs deleted file mode 100644 index 6d59096..0000000 --- a/dust-lang/src/value/abstract_value.rs +++ /dev/null @@ -1,93 +0,0 @@ -use std::fmt::{self, Display, Formatter}; - -use crate::{vm::Pointer, ConcreteValue, Value, ValueRef, Vm, VmError}; - -#[derive(Debug, PartialEq, PartialOrd)] -pub enum AbstractValue { - FunctionSelf, - List { items: Vec }, -} - -impl AbstractValue { - pub fn to_value(self) -> Value { - Value::Abstract(self) - } - - pub fn to_value_ref(&self) -> ValueRef { - ValueRef::Abstract(self) - } - - pub fn to_concrete_owned(&self, vm: &Vm) -> Result { - match self { - AbstractValue::FunctionSelf => Ok(ConcreteValue::Function(vm.chunk().clone())), - AbstractValue::List { items, .. } => { - let mut resolved_items = Vec::with_capacity(items.len()); - - for pointer in items { - let resolved_item = vm.follow_pointer(*pointer)?.to_concrete_owned(vm)?; - - resolved_items.push(resolved_item); - } - - Ok(ConcreteValue::List(resolved_items)) - } - } - } - - pub fn display(&self, vm: &Vm) -> Result { - match self { - AbstractValue::FunctionSelf => Ok("self".to_string()), - AbstractValue::List { items, .. } => { - let mut display = "[".to_string(); - - for (i, item) in items.iter().enumerate() { - if i > 0 { - display.push_str(", "); - } - - let item_display = vm.follow_pointer(*item)?.display(vm)?; - - display.push_str(&item_display); - } - - display.push(']'); - - Ok(display) - } - } - } -} - -impl Clone for AbstractValue { - fn clone(&self) -> Self { - log::trace!("Cloning abstract value {:?}", self); - - match self { - AbstractValue::FunctionSelf => AbstractValue::FunctionSelf, - AbstractValue::List { items } => AbstractValue::List { - items: items.clone(), - }, - } - } -} - -impl Display for AbstractValue { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - AbstractValue::FunctionSelf => write!(f, "self"), - AbstractValue::List { items, .. } => { - write!(f, "[")?; - - for (i, item) in items.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - - write!(f, "{}", item)?; - } - - write!(f, "]") - } - } - } -} diff --git a/dust-lang/src/value/concrete_value.rs b/dust-lang/src/value/concrete_value.rs index 994de90..f923d2a 100644 --- a/dust-lang/src/value/concrete_value.rs +++ b/dust-lang/src/value/concrete_value.rs @@ -1,22 +1,25 @@ use std::fmt::{self, Display, Formatter}; use serde::{Deserialize, Serialize}; +use smartstring::{LazyCompact, SmartString}; +use tracing::trace; -use crate::{Chunk, Type, Value, ValueError, ValueRef}; +use crate::{Type, Value, ValueError}; use super::RangeValue; +pub type DustString = SmartString; + #[derive(Debug, PartialEq, PartialOrd, Serialize, Deserialize)] pub enum ConcreteValue { Boolean(bool), Byte(u8), Character(char), Float(f64), - Function(Chunk), Integer(i64), List(Vec), Range(RangeValue), - String(String), + String(DustString), } impl ConcreteValue { @@ -24,19 +27,15 @@ impl ConcreteValue { Value::Concrete(self) } - pub fn to_value_ref(&self) -> ValueRef { - ValueRef::Concrete(self) - } - pub fn list>>(into_list: T) -> Self { ConcreteValue::List(into_list.into()) } - pub fn string(to_string: T) -> Self { - ConcreteValue::String(to_string.to_string()) + pub fn string>>(to_string: T) -> Self { + ConcreteValue::String(to_string.into()) } - pub fn as_string(&self) -> Option<&String> { + pub fn as_string(&self) -> Option<&DustString> { if let ConcreteValue::String(string) = self { Some(string) } else { @@ -44,13 +43,16 @@ impl ConcreteValue { } } + pub fn display(&self) -> DustString { + DustString::from(self.to_string()) + } + pub fn r#type(&self) -> Type { match self { ConcreteValue::Boolean(_) => Type::Boolean, ConcreteValue::Byte(_) => Type::Byte, ConcreteValue::Character(_) => Type::Character, ConcreteValue::Float(_) => Type::Float, - ConcreteValue::Function(chunk) => Type::Function(chunk.r#type().clone()), ConcreteValue::Integer(_) => Type::Integer, ConcreteValue::List(list) => { let item_type = list.first().map_or(Type::Any, |item| item.r#type()); @@ -62,41 +64,76 @@ impl ConcreteValue { } } - pub fn add(&self, other: &Self) -> Result { + pub fn add(&self, other: &Self) -> ConcreteValue { use ConcreteValue::*; - let sum = match (self, other) { - (Byte(left), Byte(right)) => ConcreteValue::Byte(left.saturating_add(*right)), - (Float(left), Float(right)) => ConcreteValue::Float(*left + *right), - (Integer(left), Integer(right)) => ConcreteValue::Integer(left.saturating_add(*right)), - (String(left), String(right)) => ConcreteValue::string(format!("{}{}", left, right)), - _ => { - return Err(ValueError::CannotAdd( - self.clone().to_value(), - other.clone().to_value(), - )) - } - }; + match (self, other) { + (Byte(left), Byte(right)) => { + let sum = left.saturating_add(*right); - Ok(sum) + Byte(sum) + } + (Character(left), Character(right)) => { + let mut concatenated = DustString::new(); + + concatenated.push(*left); + concatenated.push(*right); + + String(concatenated) + } + (Character(left), String(right)) => { + let mut concatenated = DustString::new(); + + concatenated.push(*left); + concatenated.push_str(right); + + String(concatenated) + } + (Float(left), Float(right)) => { + let sum = left + right; + + Float(sum) + } + (Integer(left), Integer(right)) => { + let sum = left.saturating_add(*right); + + Integer(sum) + } + (String(left), Character(right)) => { + let concatenated = format!("{}{}", left, right); + + String(DustString::from(concatenated)) + } + (String(left), String(right)) => { + let concatenated = format!("{}{}", left, right); + + String(DustString::from(concatenated)) + } + _ => panic!( + "{}", + ValueError::CannotAdd( + Value::Concrete(self.clone()), + Value::Concrete(other.clone()) + ) + ), + } } - pub fn subtract(&self, other: &Self) -> Result { + pub fn subtract(&self, other: &Self) -> ConcreteValue { use ConcreteValue::*; - let difference = match (self, other) { + match (self, other) { (Byte(left), Byte(right)) => ConcreteValue::Byte(left.saturating_sub(*right)), (Float(left), Float(right)) => ConcreteValue::Float(left - right), (Integer(left), Integer(right)) => ConcreteValue::Integer(left.saturating_sub(*right)), - _ => { - return Err(ValueError::CannotSubtract( - self.clone().to_value(), - other.clone().to_value(), - )) - } - }; - - Ok(difference) + _ => panic!( + "{}", + ValueError::CannotSubtract( + Value::Concrete(self.clone()), + Value::Concrete(other.clone()) + ) + ), + } } pub fn multiply(&self, other: &Self) -> Result { @@ -155,18 +192,16 @@ impl ConcreteValue { Ok(product) } - pub fn negate(&self) -> Result { + pub fn negate(&self) -> ConcreteValue { use ConcreteValue::*; - let negated = match self { + match self { Boolean(value) => ConcreteValue::Boolean(!value), Byte(value) => ConcreteValue::Byte(value.wrapping_neg()), Float(value) => ConcreteValue::Float(-value), Integer(value) => ConcreteValue::Integer(value.wrapping_neg()), - _ => return Err(ValueError::CannotNegate(self.clone().to_value())), - }; - - Ok(negated) + _ => panic!("{}", ValueError::CannotNegate(self.clone().to_value())), + } } pub fn not(&self) -> Result { @@ -180,28 +215,28 @@ impl ConcreteValue { Ok(not) } - pub fn equal(&self, other: &ConcreteValue) -> Result { + pub fn equals(&self, other: &ConcreteValue) -> bool { use ConcreteValue::*; - let equal = match (self, other) { - (Boolean(left), Boolean(right)) => ConcreteValue::Boolean(left == right), - (Byte(left), Byte(right)) => ConcreteValue::Boolean(left == right), - (Character(left), Character(right)) => ConcreteValue::Boolean(left == right), - (Float(left), Float(right)) => ConcreteValue::Boolean(left == right), - (Function(left), Function(right)) => ConcreteValue::Boolean(left == right), - (Integer(left), Integer(right)) => ConcreteValue::Boolean(left == right), - (List(left), List(right)) => ConcreteValue::Boolean(left == right), - (Range(left), Range(right)) => ConcreteValue::Boolean(left == right), - (String(left), String(right)) => ConcreteValue::Boolean(left == right), + match (self, other) { + (Boolean(left), Boolean(right)) => left == right, + (Byte(left), Byte(right)) => left == right, + (Character(left), Character(right)) => left == right, + (Float(left), Float(right)) => left == right, + (Integer(left), Integer(right)) => left == right, + (List(left), List(right)) => left == right, + (Range(left), Range(right)) => left == right, + (String(left), String(right)) => left == right, _ => { - return Err(ValueError::CannotCompare( - self.clone().to_value(), - other.clone().to_value(), - )) + panic!( + "{}", + ValueError::CannotCompare( + Value::Concrete(self.clone()), + Value::Concrete(other.clone()) + ) + ) } - }; - - Ok(equal) + } } pub fn less_than(&self, other: &ConcreteValue) -> Result { @@ -212,7 +247,6 @@ impl ConcreteValue { (Byte(left), Byte(right)) => ConcreteValue::Boolean(left < right), (Character(left), Character(right)) => ConcreteValue::Boolean(left < right), (Float(left), Float(right)) => ConcreteValue::Boolean(left < right), - (Function(left), Function(right)) => ConcreteValue::Boolean(left < right), (Integer(left), Integer(right)) => ConcreteValue::Boolean(left < right), (List(left), List(right)) => ConcreteValue::Boolean(left < right), (Range(left), Range(right)) => ConcreteValue::Boolean(left < right), @@ -228,7 +262,7 @@ impl ConcreteValue { Ok(less_than) } - pub fn less_than_or_equal(&self, other: &ConcreteValue) -> Result { + pub fn less_than_or_equals(&self, other: &ConcreteValue) -> Result { use ConcreteValue::*; let less_than_or_equal = match (self, other) { @@ -236,7 +270,6 @@ impl ConcreteValue { (Byte(left), Byte(right)) => ConcreteValue::Boolean(left <= right), (Character(left), Character(right)) => ConcreteValue::Boolean(left <= right), (Float(left), Float(right)) => ConcreteValue::Boolean(left <= right), - (Function(left), Function(right)) => ConcreteValue::Boolean(left <= right), (Integer(left), Integer(right)) => ConcreteValue::Boolean(left <= right), (List(left), List(right)) => ConcreteValue::Boolean(left <= right), (Range(left), Range(right)) => ConcreteValue::Boolean(left <= right), @@ -255,14 +288,13 @@ impl ConcreteValue { impl Clone for ConcreteValue { fn clone(&self) -> Self { - log::trace!("Cloning concrete value {}", self); + trace!("Cloning concrete value {}", self); match self { ConcreteValue::Boolean(boolean) => ConcreteValue::Boolean(*boolean), ConcreteValue::Byte(byte) => ConcreteValue::Byte(*byte), ConcreteValue::Character(character) => ConcreteValue::Character(*character), ConcreteValue::Float(float) => ConcreteValue::Float(*float), - ConcreteValue::Function(function) => ConcreteValue::Function(function.clone()), ConcreteValue::Integer(integer) => ConcreteValue::Integer(*integer), ConcreteValue::List(list) => ConcreteValue::List(list.clone()), ConcreteValue::Range(range) => ConcreteValue::Range(*range), @@ -286,7 +318,6 @@ impl Display for ConcreteValue { Ok(()) } - ConcreteValue::Function(chunk) => write!(f, "{}", chunk.r#type()), ConcreteValue::Integer(integer) => write!(f, "{integer}"), ConcreteValue::List(list) => { write!(f, "[")?; diff --git a/dust-lang/src/value/function.rs b/dust-lang/src/value/function.rs new file mode 100644 index 0000000..fc74575 --- /dev/null +++ b/dust-lang/src/value/function.rs @@ -0,0 +1,26 @@ +use std::fmt::{self, Display, Formatter}; + +use crate::FunctionType; + +use super::DustString; + +#[derive(Clone, Debug, PartialEq, PartialOrd)] +pub struct Function { + pub name: Option, + pub r#type: FunctionType, + pub prototype_index: u8, +} + +impl Display for Function { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let mut type_string = self.r#type.to_string(); + + if let Some(name) = &self.name { + debug_assert!(type_string.starts_with("fn ")); + + type_string.insert_str(3, name); + } + + write!(f, "{type_string}") + } +} diff --git a/dust-lang/src/value/mod.rs b/dust-lang/src/value/mod.rs index 5d8b04a..6da63fb 100644 --- a/dust-lang/src/value/mod.rs +++ b/dust-lang/src/value/mod.rs @@ -1,107 +1,114 @@ //! Runtime values used by the VM. -mod abstract_value; +mod abstract_list; mod concrete_value; +mod function; mod range_value; -pub use abstract_value::AbstractValue; -pub use concrete_value::ConcreteValue; +pub use abstract_list::AbstractList; +pub use concrete_value::{ConcreteValue, DustString}; +pub use function::Function; pub use range_value::RangeValue; +use serde::{Deserialize, Serialize}; use std::fmt::{self, Debug, Display, Formatter}; -use crate::{Vm, VmError}; +use crate::{vm::ThreadData, Type}; -#[derive(Clone, Debug, PartialEq, PartialOrd)] +#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)] pub enum Value { - Abstract(AbstractValue), Concrete(ConcreteValue), + + #[serde(skip)] + AbstractList(AbstractList), + + #[serde(skip)] + Function(Function), } impl Value { - pub fn to_ref(&self) -> ValueRef { - match self { - Value::Abstract(abstract_value) => ValueRef::Abstract(abstract_value), - Value::Concrete(concrete_value) => ValueRef::Concrete(concrete_value), + pub fn boolean(boolean: bool) -> Self { + Value::Concrete(ConcreteValue::Boolean(boolean)) + } + + pub fn byte(byte: u8) -> Self { + Value::Concrete(ConcreteValue::Byte(byte)) + } + + pub fn character(character: char) -> Self { + Value::Concrete(ConcreteValue::Character(character)) + } + + pub fn float(float: f64) -> Self { + Value::Concrete(ConcreteValue::Float(float)) + } + + pub fn integer(integer: i64) -> Self { + Value::Concrete(ConcreteValue::Integer(integer)) + } + + pub fn string(string: impl Into) -> Self { + Value::Concrete(ConcreteValue::String(string.into())) + } + + pub fn as_boolean(&self) -> Option<&bool> { + if let Value::Concrete(ConcreteValue::Boolean(value)) = self { + Some(value) + } else { + None } } - pub fn to_concrete_owned(&self, vm: &Vm) -> Result { - match self { - Value::Abstract(abstract_value) => abstract_value.to_concrete_owned(vm), - Value::Concrete(concrete_value) => Ok(concrete_value.clone()), + pub fn as_function(&self) -> Option<&Function> { + if let Value::Function(function) = self { + Some(function) + } else { + None } } - pub fn display(&self, vm: &Vm) -> Result { - match self { - Value::Abstract(abstract_value) => abstract_value.display(vm), - Value::Concrete(concrete_value) => Ok(concrete_value.to_string()), - } - } -} - -impl Display for Value { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Value::Abstract(abstract_value) => write!(f, "{}", abstract_value), - Value::Concrete(concrete_value) => write!(f, "{}", concrete_value), - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] -pub enum ValueRef<'a> { - Abstract(&'a AbstractValue), - Concrete(&'a ConcreteValue), -} - -impl ValueRef<'_> { - pub fn to_owned(&self) -> Value { - match self { - ValueRef::Abstract(abstract_value) => Value::Abstract((*abstract_value).clone()), - ValueRef::Concrete(concrete_value) => Value::Concrete((*concrete_value).clone()), + pub fn as_string(&self) -> Option<&DustString> { + if let Value::Concrete(ConcreteValue::String(value)) = self { + Some(value) + } else { + None } } - pub fn to_concrete_owned(&self, vm: &Vm) -> Result { + pub fn r#type(&self) -> Type { match self { - ValueRef::Abstract(abstract_value) => abstract_value.to_concrete_owned(vm), - ValueRef::Concrete(concrete_value) => Ok((*concrete_value).clone()), - } - } - - pub fn display(&self, vm: &Vm) -> Result { - match self { - ValueRef::Abstract(abstract_value) => abstract_value.display(vm), - ValueRef::Concrete(concrete_value) => Ok(concrete_value.to_string()), - } - } - - pub fn add(&self, other: ValueRef) -> Result { - match (self, other) { - (ValueRef::Concrete(left), ValueRef::Concrete(right)) => { - left.add(right).map(|result| result.to_value()) + Value::Concrete(concrete_value) => concrete_value.r#type(), + Value::AbstractList(AbstractList { item_type, .. }) => { + Type::List(Box::new(item_type.clone())) } - _ => Err(ValueError::CannotAdd(self.to_owned(), other.to_owned())), + Value::Function(Function { r#type, .. }) => Type::Function(Box::new(r#type.clone())), } } - pub fn subtract(&self, other: ValueRef) -> Result { - match (self, other) { - (ValueRef::Concrete(left), ValueRef::Concrete(right)) => { - left.subtract(right).map(|result| result.to_value()) - } - _ => Err(ValueError::CannotSubtract( - self.to_owned(), - other.to_owned(), - )), - } + pub fn add(&self, other: &Value) -> Value { + let sum = match (self, other) { + (Value::Concrete(left), Value::Concrete(right)) => left.add(right), + _ => panic!("{}", ValueError::CannotAdd(self.clone(), other.clone())), + }; + + Value::Concrete(sum) } - pub fn multiply(&self, other: ValueRef) -> Result { + pub fn subtract(&self, other: &Value) -> Value { + let difference = match (self, other) { + (Value::Concrete(left), Value::Concrete(right)) => left.subtract(right), + _ => panic!( + "{}", + ValueError::CannotSubtract(self.clone(), other.clone()) + ), + }; + + Value::Concrete(difference) + } + + pub fn multiply(&self, other: &Value) -> Result { match (self, other) { - (ValueRef::Concrete(left), ValueRef::Concrete(right)) => { - left.multiply(right).map(|result| result.to_value()) + (Value::Concrete(left), Value::Concrete(right)) => { + left.multiply(right).map(Value::Concrete) } _ => Err(ValueError::CannotMultiply( self.to_owned(), @@ -110,75 +117,83 @@ impl ValueRef<'_> { } } - pub fn divide(&self, other: ValueRef) -> Result { + pub fn divide(&self, other: &Value) -> Result { match (self, other) { - (ValueRef::Concrete(left), ValueRef::Concrete(right)) => { - left.divide(right).map(|result| result.to_value()) + (Value::Concrete(left), Value::Concrete(right)) => { + left.divide(right).map(Value::Concrete) } _ => Err(ValueError::CannotDivide(self.to_owned(), other.to_owned())), } } - pub fn modulo(&self, other: ValueRef) -> Result { + pub fn modulo(&self, other: &Value) -> Result { match (self, other) { - (ValueRef::Concrete(left), ValueRef::Concrete(right)) => { - left.modulo(right).map(|result| result.to_value()) + (Value::Concrete(left), Value::Concrete(right)) => { + left.modulo(right).map(Value::Concrete) } _ => Err(ValueError::CannotModulo(self.to_owned(), other.to_owned())), } } - pub fn negate(&self) -> Result { - match self { - ValueRef::Concrete(concrete_value) => { - concrete_value.negate().map(|result| result.to_value()) - } - _ => Err(ValueError::CannotNegate(self.to_owned())), - } + pub fn negate(&self) -> Value { + let concrete = match self { + Value::Concrete(concrete_value) => concrete_value.negate(), + _ => panic!("{}", ValueError::CannotNegate(self.clone())), + }; + + Value::Concrete(concrete) } pub fn not(&self) -> Result { match self { - ValueRef::Concrete(concrete_value) => { - concrete_value.not().map(|result| result.to_value()) - } + Value::Concrete(concrete_value) => concrete_value.not().map(Value::Concrete), _ => Err(ValueError::CannotNot(self.to_owned())), } } - pub fn equal(&self, other: ValueRef) -> Result { + pub fn equals(&self, other: &Value) -> bool { match (self, other) { - (ValueRef::Concrete(left), ValueRef::Concrete(right)) => { - left.equal(right).map(|result| result.to_value()) + (Value::Concrete(left), Value::Concrete(right)) => left.equals(right), + _ => panic!( + "{}", + ValueError::CannotCompare(self.to_owned(), other.to_owned()) + ), + } + } + + pub fn less(&self, other: &Value) -> Result { + match (self, other) { + (Value::Concrete(left), Value::Concrete(right)) => { + left.less_than(right).map(Value::Concrete) } _ => Err(ValueError::CannotCompare(self.to_owned(), other.to_owned())), } } - pub fn less_than(&self, other: ValueRef) -> Result { + pub fn less_than_or_equals(&self, other: &Value) -> Result { match (self, other) { - (ValueRef::Concrete(left), ValueRef::Concrete(right)) => { - left.less_than(right).map(|result| result.to_value()) + (Value::Concrete(left), Value::Concrete(right)) => { + left.less_than_or_equals(right).map(Value::Concrete) } _ => Err(ValueError::CannotCompare(self.to_owned(), other.to_owned())), } } - pub fn less_than_or_equal(&self, other: ValueRef) -> Result { - match (self, other) { - (ValueRef::Concrete(left), ValueRef::Concrete(right)) => left - .less_than_or_equal(right) - .map(|result| result.to_value()), - _ => Err(ValueError::CannotCompare(self.to_owned(), other.to_owned())), + pub fn display(&self, data: &ThreadData) -> DustString { + match self { + Value::AbstractList(list) => list.display(data), + Value::Concrete(concrete_value) => concrete_value.display(), + Value::Function(function) => DustString::from(function.to_string()), } } } -impl Display for ValueRef<'_> { +impl Display for Value { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - ValueRef::Abstract(abstract_value) => write!(f, "{}", abstract_value), - ValueRef::Concrete(concrete_value) => write!(f, "{}", concrete_value), + Value::Concrete(concrete_value) => write!(f, "{concrete_value}"), + Value::AbstractList(list) => write!(f, "{list}"), + Value::Function(function) => write!(f, "{function}"), } } } diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs deleted file mode 100644 index 934f1b6..0000000 --- a/dust-lang/src/vm.rs +++ /dev/null @@ -1,903 +0,0 @@ -//! Virtual machine and errors -use std::{ - cmp::Ordering, - fmt::{self, Display, Formatter}, - io, -}; - -use crate::{ - compile, instruction::*, AbstractValue, AnnotatedError, Argument, Chunk, ConcreteValue, - Destination, DustError, Instruction, NativeFunctionError, Operation, Span, Value, ValueError, - ValueRef, -}; - -pub fn run(source: &str) -> Result, DustError> { - let chunk = compile(source)?; - let mut vm = Vm::new(&chunk, None); - - vm.run() - .map_err(|error| DustError::Runtime { error, source }) -} - -/// Dust virtual machine. -/// -/// See the [module-level documentation](index.html) for more information. -#[derive(Debug)] -pub struct Vm<'a> { - chunk: &'a Chunk, - stack: Vec, - parent: Option<&'a Vm<'a>>, - local_definitions: Vec>, - - ip: usize, - last_assigned_register: Option, - current_position: Span, -} - -impl<'a> Vm<'a> { - const STACK_LIMIT: usize = u16::MAX as usize; - - pub fn new(chunk: &'a Chunk, parent: Option<&'a Vm<'a>>) -> Self { - Self { - chunk, - stack: Vec::new(), - parent, - local_definitions: vec![None; chunk.locals().len()], - ip: 0, - last_assigned_register: None, - current_position: Span(0, 0), - } - } - - pub fn chunk(&self) -> &Chunk { - self.chunk - } - - pub fn current_position(&self) -> Span { - self.current_position - } - - pub fn run(&mut self) -> Result, VmError> { - while let Ok(instruction) = self.read() { - log::info!( - "{} | {} | {} | {}", - self.ip - 1, - self.current_position, - instruction.operation(), - instruction.disassembly_info() - ); - - match instruction.operation() { - Operation::Move => { - let Move { from, to } = Move::from(&instruction); - let from_register_has_value = self - .stack - .get(from as usize) - .is_some_and(|register| !matches!(register, Register::Empty)); - let register = Register::Pointer(Pointer::Stack(from)); - - if from_register_has_value { - self.set_register(to, register)?; - } - } - Operation::Close => { - let Close { from, to } = Close::from(&instruction); - - if self.stack.len() < to as usize { - return Err(VmError::StackUnderflow { - position: self.current_position, - }); - } - - for register_index in from..to { - self.stack[register_index as usize] = Register::Empty; - } - } - Operation::LoadBoolean => { - let LoadBoolean { - destination, - value, - jump_next, - } = LoadBoolean::from(&instruction); - let register_index = self.get_destination(destination)?; - let boolean = ConcreteValue::Boolean(value).to_value(); - let register = Register::Value(boolean); - - self.set_register(register_index, register)?; - - if jump_next { - self.jump(1, true); - } - } - Operation::LoadConstant => { - let LoadConstant { - destination, - constant_index, - jump_next, - } = LoadConstant::from(&instruction); - let register_index = self.get_destination(destination)?; - let register = Register::Pointer(Pointer::Constant(constant_index)); - - self.set_register(register_index, register)?; - - if jump_next { - self.jump(1, true); - } - } - Operation::LoadList => { - let LoadList { - destination, - start_register, - } = LoadList::from(&instruction); - let register_index = self.get_destination(destination)?; - let mut pointers = Vec::new(); - - for register in start_register..register_index { - if let Some(Register::Empty) = self.stack.get(register as usize) { - continue; - } - - let pointer = Pointer::Stack(register); - - pointers.push(pointer); - } - - let register = - Register::Value(AbstractValue::List { items: pointers }.to_value()); - - self.set_register(register_index, register)?; - } - Operation::LoadSelf => { - let LoadSelf { destination } = LoadSelf::from(&instruction); - let register_index = self.get_destination(destination)?; - let register = Register::Value(AbstractValue::FunctionSelf.to_value()); - - self.set_register(register_index, register)?; - } - Operation::DefineLocal => { - let DefineLocal { - register, - local_index, - is_mutable, - } = DefineLocal::from(&instruction); - - self.local_definitions[local_index as usize] = Some(register); - } - Operation::GetLocal => { - let GetLocal { - destination, - local_index, - } = GetLocal::from(&instruction); - let register_index = self.get_destination(destination)?; - let local_register = self.local_definitions[local_index as usize].ok_or( - VmError::UndefinedLocal { - local_index, - position: self.current_position, - }, - )?; - let register = Register::Pointer(Pointer::Stack(local_register)); - - self.set_register(register_index, register)?; - } - Operation::SetLocal => { - let SetLocal { - register, - local_index, - } = SetLocal::from(&instruction); - - self.local_definitions[local_index as usize] = Some(register); - } - Operation::Add => { - let Add { - destination, - left, - right, - } = Add::from(&instruction); - let register_index = self.get_destination(destination)?; - let left = self.get_argument(left)?; - let right = self.get_argument(right)?; - let sum = left.add(right).map_err(|error| VmError::Value { - error, - position: self.current_position, - })?; - - self.set_register(register_index, Register::Value(sum))?; - } - Operation::Subtract => { - let Subtract { - destination, - left, - right, - } = Subtract::from(&instruction); - let register_index = self.get_destination(destination)?; - let left = self.get_argument(left)?; - let right = self.get_argument(right)?; - let difference = left.subtract(right).map_err(|error| VmError::Value { - error, - position: self.current_position, - })?; - - self.set_register(register_index, Register::Value(difference))?; - } - Operation::Multiply => { - let Multiply { - destination, - left, - right, - } = Multiply::from(&instruction); - let register_index = self.get_destination(destination)?; - let left = self.get_argument(left)?; - let right = self.get_argument(right)?; - let product = left.multiply(right).map_err(|error| VmError::Value { - error, - position: self.current_position, - })?; - - self.set_register(register_index, Register::Value(product))?; - } - Operation::Divide => { - let Divide { - destination, - left, - right, - } = Divide::from(&instruction); - let register_index = self.get_destination(destination)?; - let left = self.get_argument(left)?; - let right = self.get_argument(right)?; - let quotient = left.divide(right).map_err(|error| VmError::Value { - error, - position: self.current_position, - })?; - - self.set_register(register_index, Register::Value(quotient))?; - } - Operation::Modulo => { - let Modulo { - destination, - left, - right, - } = Modulo::from(&instruction); - let register_index = self.get_destination(destination)?; - let left = self.get_argument(left)?; - let right = self.get_argument(right)?; - let remainder = left.modulo(right).map_err(|error| VmError::Value { - error, - position: self.current_position, - })?; - - self.set_register(register_index, Register::Value(remainder))?; - } - Operation::Test => { - let Test { - argument, - test_value, - } = Test::from(&instruction); - let value = self.get_argument(argument)?; - let boolean = if let ValueRef::Concrete(ConcreteValue::Boolean(boolean)) = value - { - *boolean - } else { - return Err(VmError::ExpectedBoolean { - found: value.to_owned(), - position: self.current_position, - }); - }; - - if boolean == test_value { - self.jump(1, true); - } - } - Operation::TestSet => { - let TestSet { - destination, - argument, - test_value, - } = TestSet::from(&instruction); - let register_index = self.get_destination(destination)?; - let value = self.get_argument(argument)?; - let boolean = if let ValueRef::Concrete(ConcreteValue::Boolean(boolean)) = value - { - *boolean - } else { - return Err(VmError::ExpectedBoolean { - found: value.to_owned(), - position: self.current_position, - }); - }; - - if boolean == test_value { - self.jump(1, true); - } else { - let pointer = match argument { - Argument::Constant(constant_index) => Pointer::Constant(constant_index), - Argument::Local(local_index) => { - let register_index = self.local_definitions[local_index as usize] - .ok_or(VmError::UndefinedLocal { - local_index, - position: self.current_position, - })?; - - Pointer::Stack(register_index) - } - Argument::Register(register_index) => Pointer::Stack(register_index), - }; - let register = Register::Pointer(pointer); - - self.set_register(register_index, register)?; - } - } - Operation::Equal => { - let Equal { value, left, right } = Equal::from(&instruction); - let left = self.get_argument(left)?; - let right = self.get_argument(right)?; - let equal_result = left.equal(right).map_err(|error| VmError::Value { - error, - position: self.current_position, - })?; - let is_equal = - if let Value::Concrete(ConcreteValue::Boolean(boolean)) = equal_result { - boolean - } else { - return Err(VmError::ExpectedBoolean { - found: equal_result, - position: self.current_position, - }); - }; - - if is_equal == value { - self.jump(1, true); - } - } - Operation::Less => { - let Less { value, left, right } = Less::from(&instruction); - let left = self.get_argument(left)?; - let right = self.get_argument(right)?; - let less_result = left.less_than(right).map_err(|error| VmError::Value { - error, - position: self.current_position, - })?; - let is_less_than = - if let Value::Concrete(ConcreteValue::Boolean(boolean)) = less_result { - boolean - } else { - return Err(VmError::ExpectedBoolean { - found: less_result, - position: self.current_position, - }); - }; - - if is_less_than == value { - self.jump(1, true); - } - } - Operation::LessEqual => { - let LessEqual { value, left, right } = LessEqual::from(&instruction); - let left = self.get_argument(left)?; - let right = self.get_argument(right)?; - let less_or_equal_result = - left.less_than_or_equal(right) - .map_err(|error| VmError::Value { - error, - position: self.current_position, - })?; - let is_less_than_or_equal = - if let Value::Concrete(ConcreteValue::Boolean(boolean)) = - less_or_equal_result - { - boolean - } else { - return Err(VmError::ExpectedBoolean { - found: less_or_equal_result, - position: self.current_position, - }); - }; - - if is_less_than_or_equal == value { - self.jump(1, true); - } - } - Operation::Negate => { - let Negate { - destination, - argument, - } = Negate::from(&instruction); - let value = self.get_argument(argument)?; - let negated = value.negate().map_err(|error| VmError::Value { - error, - position: self.current_position, - })?; - let register_index = self.get_destination(destination)?; - let register = Register::Value(negated); - - self.set_register(register_index, register)?; - } - Operation::Not => { - let Not { - destination, - argument, - } = Not::from(&instruction); - let value = self.get_argument(argument)?; - let not = value.not().map_err(|error| VmError::Value { - error, - position: self.current_position, - })?; - let register_index = self.get_destination(destination)?; - let register = Register::Value(not); - - self.set_register(register_index, register)?; - } - Operation::Jump => { - let Jump { - offset, - is_positive, - } = Jump::from(&instruction); - - self.jump(offset as usize, is_positive); - } - Operation::Call => { - let Call { - destination, - function, - argument_count, - } = Call::from(&instruction); - let register_index = self.get_destination(destination)?; - let function = self.get_argument(function)?; - let chunk = if let ValueRef::Concrete(ConcreteValue::Function(chunk)) = function - { - chunk - } else if let ValueRef::Abstract(AbstractValue::FunctionSelf) = function { - self.chunk - } else { - return Err(VmError::ExpectedFunction { - found: function.to_concrete_owned(self)?, - position: self.current_position, - }); - }; - let mut function_vm = Vm::new(chunk, Some(self)); - let first_argument_index = register_index - argument_count; - - for (argument_index, argument_register_index) in - (first_argument_index..register_index).enumerate() - { - function_vm.set_register( - argument_index as u16, - Register::Pointer(Pointer::ParentStack(argument_register_index)), - )?; - - function_vm.local_definitions[argument_index] = Some(argument_index as u16); - } - - let return_value = function_vm.run()?; - - if let Some(concrete_value) = return_value { - let register = Register::Value(concrete_value.to_value()); - - self.set_register(register_index, register)?; - } - } - Operation::CallNative => { - let CallNative { - destination, - function, - argument_count, - } = CallNative::from(&instruction); - let return_value = function.call(self, instruction)?; - - if let Some(value) = return_value { - let register_index = self.get_destination(destination)?; - let register = Register::Value(value); - - self.set_register(register_index, register)?; - } - } - Operation::Return => { - let Return { - should_return_value, - } = Return::from(&instruction); - - if !should_return_value { - return Ok(None); - } - - return if let Some(register_index) = self.last_assigned_register { - let return_value = self - .open_register(register_index)? - .to_concrete_owned(self)?; - - Ok(Some(return_value)) - } else { - Err(VmError::StackUnderflow { - position: self.current_position, - }) - }; - } - } - } - - Ok(None) - } - - pub(crate) fn follow_pointer(&self, pointer: Pointer) -> Result { - match pointer { - Pointer::Stack(register_index) => self.open_register(register_index), - Pointer::Constant(constant_index) => { - let constant = self.get_constant(constant_index)?; - - Ok(ValueRef::Concrete(constant)) - } - Pointer::ParentStack(register_index) => { - let parent = self - .parent - .as_ref() - .ok_or_else(|| VmError::ExpectedParent { - position: self.current_position, - })?; - - parent.open_register(register_index) - } - Pointer::ParentConstant(constant_index) => { - let parent = self - .parent - .as_ref() - .ok_or_else(|| VmError::ExpectedParent { - position: self.current_position, - })?; - let constant = parent.get_constant(constant_index)?; - - Ok(ValueRef::Concrete(constant)) - } - } - } - - fn open_register(&self, register_index: u16) -> Result { - let register_index = register_index as usize; - let register = - self.stack - .get(register_index) - .ok_or_else(|| VmError::RegisterIndexOutOfBounds { - index: register_index, - position: self.current_position, - })?; - - log::trace!("Open R{register_index} to {register}"); - - match register { - Register::Value(value) => Ok(value.to_ref()), - Register::Pointer(pointer) => self.follow_pointer(*pointer), - Register::Empty => Err(VmError::EmptyRegister { - index: register_index, - position: self.current_position, - }), - } - } - - pub(crate) fn open_register_allow_empty( - &self, - register_index: u16, - ) -> Result, VmError> { - let register_index = register_index as usize; - let register = - self.stack - .get(register_index) - .ok_or_else(|| VmError::RegisterIndexOutOfBounds { - index: register_index, - position: self.current_position, - })?; - - log::trace!("Open R{register_index} to {register}"); - - match register { - Register::Value(value) => Ok(Some(value.to_ref())), - Register::Pointer(pointer) => self.follow_pointer(*pointer).map(Some), - Register::Empty => Ok(None), - } - } - - /// DRY helper for handling JUMP instructions - fn jump(&mut self, offset: usize, is_positive: bool) { - log::trace!( - "Jumping {}", - if is_positive { - format!("+{}", offset) - } else { - format!("-{}", offset) - } - ); - - let new_ip = if is_positive { - self.ip + offset - } else { - self.ip - offset - 1 - }; - self.ip = new_ip; - } - - /// DRY helper to get a register index from a Destination - fn get_destination(&self, destination: Destination) -> Result { - let index = match destination { - Destination::Register(register_index) => register_index, - Destination::Local(local_index) => self - .local_definitions - .get(local_index as usize) - .copied() - .flatten() - .ok_or_else(|| VmError::UndefinedLocal { - local_index, - position: self.current_position, - })?, - }; - - Ok(index) - } - - /// DRY helper to get a value from an Argument - fn get_argument(&self, argument: Argument) -> Result { - let value_ref = match argument { - Argument::Constant(constant_index) => { - ValueRef::Concrete(self.get_constant(constant_index)?) - } - Argument::Register(register_index) => self.open_register(register_index)?, - Argument::Local(local_index) => self.get_local(local_index)?, - }; - - Ok(value_ref) - } - - fn set_register(&mut self, to_register: u16, register: Register) -> Result<(), VmError> { - self.last_assigned_register = Some(to_register); - - let length = self.stack.len(); - let to_register = to_register as usize; - - if length == Self::STACK_LIMIT { - return Err(VmError::StackOverflow { - position: self.current_position, - }); - } - - match to_register.cmp(&length) { - Ordering::Less => { - log::trace!("Change R{to_register} to {register}"); - - self.stack[to_register] = register; - - Ok(()) - } - Ordering::Equal => { - log::trace!("Set R{to_register} to {register}"); - - self.stack.push(register); - - Ok(()) - } - Ordering::Greater => { - let difference = to_register - length; - - for index in 0..difference { - log::trace!("Set R{index} to {register}"); - - self.stack.push(Register::Empty); - } - - log::trace!("Set R{to_register} to {register}"); - - self.stack.push(register); - - Ok(()) - } - } - } - - fn get_constant(&self, constant_index: u16) -> Result<&ConcreteValue, VmError> { - self.chunk - .constants() - .get(constant_index as usize) - .ok_or_else(|| VmError::ConstantIndexOutOfBounds { - index: constant_index as usize, - position: self.current_position, - }) - } - - fn get_local(&self, local_index: u16) -> Result { - let register_index = self - .local_definitions - .get(local_index as usize) - .ok_or_else(|| VmError::UndefinedLocal { - local_index, - position: self.current_position, - })? - .ok_or_else(|| VmError::UndefinedLocal { - local_index, - position: self.current_position, - })?; - - self.open_register(register_index) - } - - fn read(&mut self) -> Result { - let (instruction, _type, position) = - self.chunk.instructions().get(self.ip).ok_or_else(|| { - VmError::InstructionIndexOutOfBounds { - index: self.ip, - position: self.current_position, - } - })?; - - self.ip += 1; - self.current_position = *position; - - Ok(*instruction) - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum Register { - Empty, - Value(Value), - Pointer(Pointer), -} - -impl Display for Register { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Empty => write!(f, "empty"), - Self::Value(value) => write!(f, "{}", value), - Self::Pointer(pointer) => write!(f, "{}", pointer), - } - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub enum Pointer { - Stack(u16), - Constant(u16), - ParentStack(u16), - ParentConstant(u16), -} - -impl Display for Pointer { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Stack(index) => write!(f, "R{}", index), - Self::Constant(index) => write!(f, "C{}", index), - Self::ParentStack(index) => write!(f, "PR{}", index), - Self::ParentConstant(index) => write!(f, "PC{}", index), - } - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum VmError { - // Stack errors - StackOverflow { - position: Span, - }, - StackUnderflow { - position: Span, - }, - - // Register errors - EmptyRegister { - index: usize, - position: Span, - }, - ExpectedConcreteValue { - found: AbstractValue, - position: Span, - }, - ExpectedValue { - found: Register, - position: Span, - }, - RegisterIndexOutOfBounds { - index: usize, - position: Span, - }, - - // Local errors - UndefinedLocal { - local_index: u16, - position: Span, - }, - - // Execution errors - ExpectedBoolean { - found: Value, - position: Span, - }, - ExpectedFunction { - found: ConcreteValue, - position: Span, - }, - ExpectedParent { - position: Span, - }, - ValueDisplay { - error: io::ErrorKind, - position: Span, - }, - - // Chunk errors - ConstantIndexOutOfBounds { - index: usize, - position: Span, - }, - InstructionIndexOutOfBounds { - index: usize, - position: Span, - }, - LocalIndexOutOfBounds { - index: usize, - position: Span, - }, - - // Wrappers for foreign errors - NativeFunction(NativeFunctionError), - Value { - error: ValueError, - position: Span, - }, -} - -impl AnnotatedError for VmError { - fn title() -> &'static str { - "Runtime Error" - } - - fn description(&self) -> &'static str { - match self { - Self::ConstantIndexOutOfBounds { .. } => "Constant index out of bounds", - Self::EmptyRegister { .. } => "Empty register", - Self::ExpectedBoolean { .. } => "Expected boolean", - Self::ExpectedConcreteValue { .. } => "Expected concrete value", - Self::ExpectedFunction { .. } => "Expected function", - Self::ExpectedParent { .. } => "Expected parent", - Self::ExpectedValue { .. } => "Expected value", - Self::InstructionIndexOutOfBounds { .. } => "Instruction index out of bounds", - Self::LocalIndexOutOfBounds { .. } => "Local index out of bounds", - Self::NativeFunction(error) => error.description(), - Self::RegisterIndexOutOfBounds { .. } => "Register index out of bounds", - Self::StackOverflow { .. } => "Stack overflow", - Self::StackUnderflow { .. } => "Stack underflow", - Self::UndefinedLocal { .. } => "Undefined local", - Self::Value { .. } => "Value error", - Self::ValueDisplay { .. } => "Value display error", - } - } - - fn details(&self) -> Option { - match self { - Self::EmptyRegister { index, .. } => Some(format!("Register R{index} is empty")), - Self::ExpectedFunction { found, .. } => Some(format!("{found} is not a function")), - - Self::RegisterIndexOutOfBounds { index, .. } => { - Some(format!("Register {index} does not exist")) - } - Self::NativeFunction(error) => error.details(), - Self::Value { error, .. } => Some(error.to_string()), - Self::ValueDisplay { error, .. } => Some(error.to_string() + " while displaying value"), - _ => None, - } - } - - fn position(&self) -> Span { - match self { - Self::ConstantIndexOutOfBounds { position, .. } => *position, - Self::EmptyRegister { position, .. } => *position, - Self::ExpectedBoolean { position, .. } => *position, - Self::ExpectedConcreteValue { position, .. } => *position, - Self::ExpectedFunction { position, .. } => *position, - Self::ExpectedParent { position } => *position, - Self::ExpectedValue { position, .. } => *position, - Self::InstructionIndexOutOfBounds { position, .. } => *position, - Self::LocalIndexOutOfBounds { position, .. } => *position, - Self::NativeFunction(error) => error.position(), - Self::RegisterIndexOutOfBounds { position, .. } => *position, - Self::StackOverflow { position } => *position, - Self::StackUnderflow { position } => *position, - Self::UndefinedLocal { position, .. } => *position, - Self::Value { position, .. } => *position, - Self::ValueDisplay { position, .. } => *position, - } - } -} diff --git a/dust-lang/src/vm/function_call.rs b/dust-lang/src/vm/function_call.rs new file mode 100644 index 0000000..86a0991 --- /dev/null +++ b/dust-lang/src/vm/function_call.rs @@ -0,0 +1,39 @@ +use std::fmt::{self, Debug, Display, Formatter}; + +use crate::{Chunk, DustString}; + +use super::Register; + +#[derive(Debug)] +pub struct FunctionCall<'a> { + pub chunk: &'a Chunk, + pub ip: usize, + pub return_register: u8, + pub registers: Vec, +} + +impl<'a> FunctionCall<'a> { + pub fn new(chunk: &'a Chunk, return_register: u8) -> Self { + Self { + chunk, + ip: 0, + return_register, + registers: vec![Register::Empty; chunk.register_count], + } + } +} + +impl Display for FunctionCall<'_> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!( + f, + "FunctionCall: {} | IP: {} | Registers: {}", + self.chunk + .name + .as_ref() + .unwrap_or(&DustString::from("anonymous")), + self.ip, + self.registers.len() + ) + } +} diff --git a/dust-lang/src/vm/mod.rs b/dust-lang/src/vm/mod.rs new file mode 100644 index 0000000..4f16b3e --- /dev/null +++ b/dust-lang/src/vm/mod.rs @@ -0,0 +1,103 @@ +//! Virtual machine and errors +mod function_call; +mod run_action; +mod stack; +mod thread; + +use std::{ + fmt::{self, Debug, Display, Formatter}, + sync::mpsc, + thread::spawn, +}; + +pub use function_call::FunctionCall; +pub(crate) use run_action::get_next_action; +pub use run_action::RunAction; +pub use stack::Stack; +pub use thread::{Thread, ThreadData}; + +use tracing::{span, Level}; + +use crate::{compile, Chunk, DustError, Value}; + +pub fn run(source: &str) -> Result, DustError> { + let chunk = compile(source)?; + let vm = Vm::new(chunk); + + Ok(vm.run()) +} + +pub struct Vm { + threads: Vec, +} + +impl Vm { + pub fn new(chunk: Chunk) -> Self { + let threads = vec![Thread::new(chunk)]; + + debug_assert_eq!(1, threads.capacity()); + + Self { threads } + } + + pub fn run(mut self) -> Option { + let span = span!(Level::INFO, "Run"); + let _enter = span.enter(); + + if self.threads.len() == 1 { + return self.threads[0].run(); + } + + let (tx, rx) = mpsc::channel(); + + for mut thread in self.threads { + let tx = tx.clone(); + + spawn(move || { + let return_value = thread.run(); + + if let Some(value) = return_value { + tx.send(value).unwrap(); + } + }); + } + + rx.into_iter().last() + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Register { + Empty, + Value(Value), + Pointer(Pointer), +} + +impl Display for Register { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::Empty => write!(f, "empty"), + Self::Value(value) => write!(f, "{}", value), + Self::Pointer(pointer) => write!(f, "{}", pointer), + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub enum Pointer { + Register(u8), + Constant(u8), + Stack(usize, u8), +} + +impl Display for Pointer { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::Register(index) => write!(f, "PR{}", index), + Self::Constant(index) => write!(f, "PC{}", index), + Self::Stack(call_index, register_index) => { + write!(f, "PS{}R{}", call_index, register_index) + } + } + } +} diff --git a/dust-lang/src/vm/run_action.rs b/dust-lang/src/vm/run_action.rs new file mode 100644 index 0000000..3af2630 --- /dev/null +++ b/dust-lang/src/vm/run_action.rs @@ -0,0 +1,645 @@ +use tracing::trace; + +use crate::{ + instruction::{ + Add, Call, CallNative, Close, Divide, Equal, GetLocal, Jump, Less, LessEqual, LoadBoolean, + LoadConstant, LoadFunction, LoadList, LoadSelf, Modulo, Multiply, Negate, Not, Point, + Return, SetLocal, Subtract, Test, TestSet, + }, + vm::FunctionCall, + AbstractList, Argument, ConcreteValue, Instruction, Type, Value, +}; + +use super::{thread::ThreadData, Pointer, Register}; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct RunAction { + pub logic: RunnerLogic, + pub instruction: Instruction, +} + +impl From for RunAction { + fn from(instruction: Instruction) -> Self { + let operation = instruction.operation(); + let logic = RUNNER_LOGIC_TABLE[operation.0 as usize]; + + RunAction { logic, instruction } + } +} + +pub type RunnerLogic = fn(Instruction, &mut ThreadData) -> bool; + +pub const RUNNER_LOGIC_TABLE: [RunnerLogic; 25] = [ + point, + close, + load_boolean, + load_constant, + load_function, + load_list, + load_self, + get_local, + set_local, + add, + subtract, + multiply, + divide, + modulo, + test, + test_set, + equal, + less, + less_equal, + negate, + not, + call, + call_native, + jump, + r#return, +]; + +pub(crate) fn get_next_action(data: &mut ThreadData) -> RunAction { + let current_call = data.call_stack.last_mut_unchecked(); + let instruction = current_call.chunk.instructions[current_call.ip]; + let operation = instruction.operation(); + let logic = RUNNER_LOGIC_TABLE[operation.0 as usize]; + + current_call.ip += 1; + + RunAction { logic, instruction } +} + +pub fn point(instruction: Instruction, data: &mut ThreadData) -> bool { + let Point { from, to } = instruction.into(); + let from_register = data.get_register_unchecked(from); + let from_register_is_empty = matches!(from_register, Register::Empty); + + if !from_register_is_empty { + let register = Register::Pointer(Pointer::Register(to)); + + data.set_register(from, register); + } + + data.next_action = get_next_action(data); + + false +} + +pub fn close(instruction: Instruction, data: &mut ThreadData) -> bool { + let Close { from, to } = instruction.into(); + + for register_index in from..to { + data.set_register(register_index, Register::Empty); + } + + data.next_action = get_next_action(data); + + false +} + +pub fn load_boolean(instruction: Instruction, data: &mut ThreadData) -> bool { + let LoadBoolean { + destination, + value, + jump_next, + } = instruction.into(); + let boolean = Value::Concrete(ConcreteValue::Boolean(value)); + let register = Register::Value(boolean); + + data.set_register(destination, register); + + if jump_next { + let current_call = data.call_stack.last_mut_unchecked(); + + current_call.ip += 1; + } + + data.next_action = get_next_action(data); + + false +} + +pub fn load_constant(instruction: Instruction, data: &mut ThreadData) -> bool { + let LoadConstant { + destination, + constant_index, + jump_next, + } = instruction.into(); + let register = Register::Pointer(Pointer::Constant(constant_index)); + + trace!("Load constant {constant_index} into R{destination}"); + + data.set_register(destination, register); + + if jump_next { + let current_call = data.call_stack.last_mut_unchecked(); + + current_call.ip += 1; + } + + data.next_action = get_next_action(data); + + false +} + +pub fn load_list(instruction: Instruction, data: &mut ThreadData) -> bool { + let LoadList { + destination, + start_register, + } = instruction.into(); + let mut item_pointers = Vec::with_capacity((destination - start_register) as usize); + let mut item_type = Type::Any; + + for register_index in start_register..destination { + match data.get_register_unchecked(register_index) { + Register::Empty => continue, + Register::Value(value) => { + if item_type == Type::Any { + item_type = value.r#type(); + } + } + Register::Pointer(pointer) => { + if item_type == Type::Any { + item_type = data.follow_pointer_unchecked(*pointer).r#type(); + } + } + } + + let pointer = Pointer::Register(register_index); + + item_pointers.push(pointer); + } + + let list_value = Value::AbstractList(AbstractList { + item_type, + item_pointers, + }); + let register = Register::Value(list_value); + + data.set_register(destination, register); + + data.next_action = get_next_action(data); + + false +} + +pub fn load_function(instruction: Instruction, data: &mut ThreadData) -> bool { + let LoadFunction { + destination, + prototype_index, + } = instruction.into(); + let prototype_index = prototype_index as usize; + let current_call = data.call_stack.last_mut_unchecked(); + let prototype = ¤t_call.chunk.prototypes[prototype_index]; + let function = prototype.as_function(); + let register = Register::Value(Value::Function(function)); + + data.set_register(destination, register); + + data.next_action = get_next_action(data); + + false +} + +pub fn load_self(instruction: Instruction, data: &mut ThreadData) -> bool { + let LoadSelf { destination } = instruction.into(); + let current_call = data.call_stack.last_mut_unchecked(); + let prototype = ¤t_call.chunk; + let function = prototype.as_function(); + let register = Register::Value(Value::Function(function)); + + data.set_register(destination, register); + + data.next_action = get_next_action(data); + + false +} + +pub fn get_local(instruction: Instruction, data: &mut ThreadData) -> bool { + let GetLocal { + destination, + local_index, + } = instruction.into(); + let local_register_index = data.get_local_register(local_index); + let register = Register::Pointer(Pointer::Register(local_register_index)); + + data.set_register(destination, register); + + data.next_action = get_next_action(data); + + false +} + +pub fn set_local(instruction: Instruction, data: &mut ThreadData) -> bool { + let SetLocal { + register_index, + local_index, + } = instruction.into(); + let local_register_index = data.get_local_register(local_index); + let register = Register::Pointer(Pointer::Register(register_index)); + + data.set_register(local_register_index, register); + + data.next_action = get_next_action(data); + + false +} + +pub fn add(instruction: Instruction, data: &mut ThreadData) -> bool { + let Add { + destination, + left, + right, + } = instruction.into(); + let left = data.get_argument_unchecked(left); + let right = data.get_argument_unchecked(right); + let sum = left.add(right); + let register = Register::Value(sum); + + data.set_register(destination, register); + + data.next_action = get_next_action(data); + + false +} + +pub fn subtract(instruction: Instruction, data: &mut ThreadData) -> bool { + let Subtract { + destination, + left, + right, + } = instruction.into(); + let left = data.get_argument_unchecked(left); + let right = data.get_argument_unchecked(right); + let difference = left.subtract(right); + let register = Register::Value(difference); + + data.set_register(destination, register); + + data.next_action = get_next_action(data); + + false +} + +pub fn multiply(instruction: Instruction, data: &mut ThreadData) -> bool { + let Multiply { + destination, + left, + right, + } = instruction.into(); + let left = data.get_argument_unchecked(left); + let right = data.get_argument_unchecked(right); + let product = match (left, right) { + (Value::Concrete(left), Value::Concrete(right)) => match (left, right) { + (ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => { + ConcreteValue::Integer(left * right).to_value() + } + _ => panic!("Value Error: Cannot multiply values"), + }, + _ => panic!("Value Error: Cannot multiply values"), + }; + let register = Register::Value(product); + + data.set_register(destination, register); + + data.next_action = get_next_action(data); + + false +} + +pub fn divide(instruction: Instruction, data: &mut ThreadData) -> bool { + let Divide { + destination, + left, + right, + } = instruction.into(); + let left = data.get_argument_unchecked(left); + let right = data.get_argument_unchecked(right); + let quotient = match (left, right) { + (Value::Concrete(left), Value::Concrete(right)) => match (left, right) { + (ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => { + ConcreteValue::Integer(left / right).to_value() + } + _ => panic!("Value Error: Cannot divide values"), + }, + _ => panic!("Value Error: Cannot divide values"), + }; + let register = Register::Value(quotient); + + data.set_register(destination, register); + + data.next_action = get_next_action(data); + + false +} + +pub fn modulo(instruction: Instruction, data: &mut ThreadData) -> bool { + let Modulo { + destination, + left, + right, + } = instruction.into(); + let left = data.get_argument_unchecked(left); + let right = data.get_argument_unchecked(right); + let remainder = match (left, right) { + (Value::Concrete(left), Value::Concrete(right)) => match (left, right) { + (ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => { + ConcreteValue::Integer(left % right).to_value() + } + _ => panic!("Value Error: Cannot modulo values"), + }, + _ => panic!("Value Error: Cannot modulo values"), + }; + let register = Register::Value(remainder); + + data.set_register(destination, register); + + data.next_action = get_next_action(data); + + false +} + +pub fn test(instruction: Instruction, data: &mut ThreadData) -> bool { + let Test { + operand_register, + test_value, + } = instruction.into(); + let value = data.open_register_unchecked(operand_register); + let boolean = if let Value::Concrete(ConcreteValue::Boolean(boolean)) = value { + *boolean + } else { + panic!("VM Error: Expected boolean value for TEST operation",); + }; + + if boolean == test_value { + let current_call = data.call_stack.last_mut_unchecked(); + + current_call.ip += 1; + } + + data.next_action = get_next_action(data); + + false +} + +pub fn test_set(instruction: Instruction, data: &mut ThreadData) -> bool { + let TestSet { + destination, + argument, + test_value, + } = instruction.into(); + let value = data.get_argument_unchecked(argument); + let boolean = if let Value::Concrete(ConcreteValue::Boolean(boolean)) = value { + *boolean + } else { + panic!("VM Error: Expected boolean value for TEST_SET operation",); + }; + + if boolean == test_value { + } else { + let pointer = match argument { + Argument::Constant(constant_index) => Pointer::Constant(constant_index), + Argument::Register(register_index) => Pointer::Register(register_index), + }; + let register = Register::Pointer(pointer); + + data.set_register(destination, register); + } + + data.next_action = get_next_action(data); + + false +} + +pub fn equal(instruction: Instruction, data: &mut ThreadData) -> bool { + let Equal { value, left, right } = instruction.into(); + let left = data.get_argument_unchecked(left); + let right = data.get_argument_unchecked(right); + let is_equal = left.equals(right); + + if is_equal == value { + let current_call = data.call_stack.last_mut_unchecked(); + + current_call.ip += 1; + } + + data.next_action = get_next_action(data); + + false +} + +pub fn less(instruction: Instruction, data: &mut ThreadData) -> bool { + let Less { value, left, right } = instruction.into(); + let left = data.get_argument_unchecked(left); + let right = data.get_argument_unchecked(right); + let is_less = left < right; + + if is_less == value { + let current_call = data.call_stack.last_mut_unchecked(); + + current_call.ip += 1; + } + + data.next_action = get_next_action(data); + + false +} + +pub fn less_equal(instruction: Instruction, data: &mut ThreadData) -> bool { + let LessEqual { value, left, right } = instruction.into(); + let left = data.get_argument_unchecked(left); + let right = data.get_argument_unchecked(right); + let is_less_or_equal = left <= right; + + if is_less_or_equal == value { + let current_call = data.call_stack.last_mut_unchecked(); + + current_call.ip += 1; + } + + data.next_action = get_next_action(data); + + false +} + +pub fn negate(instruction: Instruction, data: &mut ThreadData) -> bool { + let Negate { + destination, + argument, + } = instruction.into(); + let argument = data.get_argument_unchecked(argument); + let negated = argument.negate(); + let register = Register::Value(negated); + + data.set_register(destination, register); + + data.next_action = get_next_action(data); + + false +} + +pub fn not(instruction: Instruction, data: &mut ThreadData) -> bool { + let Not { + destination, + argument, + } = instruction.into(); + let argument = data.get_argument_unchecked(argument); + let not = match argument { + Value::Concrete(ConcreteValue::Boolean(boolean)) => ConcreteValue::Boolean(!boolean), + _ => panic!("VM Error: Expected boolean value for NOT operation"), + }; + let register = Register::Value(Value::Concrete(not)); + + data.set_register(destination, register); + + data.next_action = get_next_action(data); + + false +} + +pub fn jump(instruction: Instruction, data: &mut ThreadData) -> bool { + let Jump { + offset, + is_positive, + } = instruction.into(); + let offset = offset as usize; + let current_call = data.call_stack.last_mut_unchecked(); + + if is_positive { + current_call.ip += offset; + } else { + current_call.ip -= offset + 1; + } + + data.next_action = get_next_action(data); + + false +} + +pub fn call(instruction: Instruction, data: &mut ThreadData) -> bool { + let Call { + destination: return_register, + function_register, + argument_count, + is_recursive, + } = instruction.into(); + let current_call = data.call_stack.last_unchecked(); + let first_argument_register = return_register - argument_count; + let prototype = if is_recursive { + current_call.chunk + } else { + let function = data + .open_register_unchecked(function_register) + .as_function() + .unwrap(); + + ¤t_call.chunk.prototypes[function.prototype_index as usize] + }; + let mut next_call = FunctionCall::new(prototype, return_register); + let mut argument_index = 0; + + for register_index in first_argument_register..return_register { + let value_option = data.open_register_allow_empty_unchecked(register_index); + let argument = if let Some(value) = value_option { + value.clone() + } else { + continue; + }; + + next_call.registers[argument_index] = Register::Value(argument); + argument_index += 1; + } + + data.call_stack.push(next_call); + + data.next_action = get_next_action(data); + + false +} + +pub fn call_native(instruction: Instruction, data: &mut ThreadData) -> bool { + let CallNative { + destination, + function, + argument_count, + } = instruction.into(); + let first_argument_index = destination - argument_count; + let argument_range = first_argument_index..destination; + + function.call(data, Some(destination), argument_range) +} + +pub fn r#return(instruction: Instruction, data: &mut ThreadData) -> bool { + trace!("Returning with call stack:\n{}", data.call_stack); + + let Return { + should_return_value, + return_register, + } = instruction.into(); + let (destination, return_value) = if data.call_stack.len() == 1 { + if should_return_value { + data.return_value_index = Some(return_register); + }; + + return true; + } else { + let return_value = data.empty_register_or_clone_constant_unchecked(return_register); + let destination = data.call_stack.pop_unchecked().return_register; + + (destination, return_value) + }; + + if should_return_value { + data.set_register(destination, Register::Value(return_value)); + } + + data.next_action = get_next_action(data); + + false +} + +#[cfg(test)] +mod tests { + + use crate::Operation; + + use super::*; + + const ALL_OPERATIONS: [(Operation, RunnerLogic); 24] = [ + (Operation::POINT, point), + (Operation::CLOSE, close), + (Operation::LOAD_BOOLEAN, load_boolean), + (Operation::LOAD_CONSTANT, load_constant), + (Operation::LOAD_LIST, load_list), + (Operation::LOAD_SELF, load_self), + (Operation::GET_LOCAL, get_local), + (Operation::SET_LOCAL, set_local), + (Operation::ADD, add), + (Operation::SUBTRACT, subtract), + (Operation::MULTIPLY, multiply), + (Operation::DIVIDE, divide), + (Operation::MODULO, modulo), + (Operation::TEST, test), + (Operation::TEST_SET, test_set), + (Operation::EQUAL, equal), + (Operation::LESS, less), + (Operation::LESS_EQUAL, less_equal), + (Operation::NEGATE, negate), + (Operation::NOT, not), + (Operation::CALL, call), + (Operation::CALL_NATIVE, call_native), + (Operation::JUMP, jump), + (Operation::RETURN, r#return), + ]; + + #[test] + fn operations_map_to_the_correct_runner() { + for (operation, expected_runner) in ALL_OPERATIONS { + let actual_runner = RUNNER_LOGIC_TABLE[operation.0 as usize]; + + assert_eq!( + expected_runner, actual_runner, + "{operation} runner is incorrect" + ); + } + } +} diff --git a/dust-lang/src/vm/stack.rs b/dust-lang/src/vm/stack.rs new file mode 100644 index 0000000..ac8d2e9 --- /dev/null +++ b/dust-lang/src/vm/stack.rs @@ -0,0 +1,137 @@ +use std::{ + fmt::{self, Debug, Display, Formatter}, + ops::{Index, IndexMut, Range}, +}; + +use super::FunctionCall; + +#[derive(Clone, PartialEq)] +pub struct Stack { + items: Vec, +} + +impl Stack { + pub fn new() -> Self { + Stack { + items: Vec::with_capacity(1), + } + } + + pub fn with_capacity(capacity: usize) -> Self { + Stack { + items: Vec::with_capacity(capacity), + } + } + + pub fn is_empty(&self) -> bool { + self.items.is_empty() + } + + pub fn len(&self) -> usize { + self.items.len() + } + + pub fn get_unchecked(&self, index: usize) -> &T { + if cfg!(debug_assertions) { + assert!(index < self.len(), "Stack underflow"); + + &self.items[index] + } else { + unsafe { self.items.get_unchecked(index) } + } + } + + pub fn get_unchecked_mut(&mut self, index: usize) -> &mut T { + if cfg!(debug_assertions) { + assert!(index < self.len(), "Stack underflow"); + + &mut self.items[index] + } else { + unsafe { self.items.get_unchecked_mut(index) } + } + } + + pub fn push(&mut self, item: T) { + self.items.push(item); + } + + pub fn pop(&mut self) -> Option { + self.items.pop() + } + + pub fn last(&self) -> Option<&T> { + self.items.last() + } + + pub fn last_mut(&mut self) -> Option<&mut T> { + self.items.last_mut() + } + + pub fn pop_unchecked(&mut self) -> T { + if cfg!(debug_assertions) { + assert!(!self.is_empty(), "Stack underflow"); + + self.items.pop().unwrap() + } else { + unsafe { self.items.pop().unwrap_unchecked() } + } + } + + pub fn last_unchecked(&self) -> &T { + if cfg!(debug_assertions) { + assert!(!self.is_empty(), "Stack underflow"); + + self.items.last().unwrap() + } else { + unsafe { self.items.last().unwrap_unchecked() } + } + } + + pub fn last_mut_unchecked(&mut self) -> &mut T { + if cfg!(debug_assertions) { + assert!(!self.is_empty(), "Stack underflow"); + + self.items.last_mut().unwrap() + } else { + unsafe { self.items.last_mut().unwrap_unchecked() } + } + } +} + +impl Default for Stack { + fn default() -> Self { + Self::new() + } +} + +impl Index> for Stack { + type Output = [T]; + + fn index(&self, index: Range) -> &Self::Output { + &self.items[index] + } +} + +impl IndexMut> for Stack { + fn index_mut(&mut self, index: Range) -> &mut Self::Output { + &mut self.items[index] + } +} + +impl Debug for Stack { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{:?}", self.items) + } +} + +impl Display for Stack> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + writeln!(f, "----- DUST CALL STACK -----")?; + + for (index, function_call) in self.items.iter().enumerate().rev() { + writeln!(f, "{index:02} | {function_call}")?; + } + + write!(f, "---------------------------") + } +} diff --git a/dust-lang/src/vm/thread.rs b/dust-lang/src/vm/thread.rs new file mode 100644 index 0000000..8152a5b --- /dev/null +++ b/dust-lang/src/vm/thread.rs @@ -0,0 +1,272 @@ +use std::mem::replace; + +use tracing::{info, trace}; + +use crate::{vm::FunctionCall, Argument, Chunk, DustString, Span, Value}; + +use super::{Pointer, Register, RunAction, Stack}; + +pub struct Thread { + chunk: Chunk, +} + +impl Thread { + pub fn new(chunk: Chunk) -> Self { + Thread { chunk } + } + + pub fn run(&mut self) -> Option { + info!( + "Starting thread with {}", + self.chunk + .name + .clone() + .unwrap_or_else(|| DustString::from("anonymous")) + ); + + let mut call_stack = Stack::with_capacity(self.chunk.prototypes.len() + 1); + let main_call = FunctionCall::new(&self.chunk, 0); + + call_stack.push(main_call); + + let first_action = RunAction::from(*self.chunk.instructions.first().unwrap()); + let mut thread_data = ThreadData { + call_stack, + next_action: first_action, + return_value_index: None, + }; + + loop { + trace!("Instruction: {}", thread_data.next_action.instruction); + + let should_end = (thread_data.next_action.logic)( + thread_data.next_action.instruction, + &mut thread_data, + ); + + if should_end { + let return_value = if let Some(register_index) = thread_data.return_value_index { + let value = + thread_data.empty_register_or_clone_constant_unchecked(register_index); + + Some(value) + } else { + None + }; + + return return_value; + } + } + } +} + +#[derive(Debug)] +pub struct ThreadData<'a> { + pub call_stack: Stack>, + pub next_action: RunAction, + pub return_value_index: Option, +} + +impl ThreadData<'_> { + pub fn current_position(&self) -> Span { + let current_call = self.call_stack.last_unchecked(); + + current_call.chunk.positions[current_call.ip] + } + + pub(crate) fn follow_pointer_unchecked(&self, pointer: Pointer) -> &Value { + trace!("Follow pointer {pointer}"); + + match pointer { + Pointer::Register(register_index) => self.open_register_unchecked(register_index), + Pointer::Constant(constant_index) => self.get_constant_unchecked(constant_index), + Pointer::Stack(stack_index, register_index) => unsafe { + let register = self + .call_stack + .get_unchecked(stack_index) + .registers + .get_unchecked(register_index as usize); + + match register { + Register::Value(value) => value, + Register::Pointer(pointer) => self.follow_pointer_unchecked(*pointer), + Register::Empty => panic!("VM Error: Register {register_index} is empty"), + } + }, + } + } + + pub fn get_register_unchecked(&self, register_index: u8) -> &Register { + trace!("Get register R{register_index}"); + + let register_index = register_index as usize; + + if cfg!(debug_assertions) { + &self.call_stack.last_unchecked().registers[register_index] + } else { + unsafe { + self.call_stack + .last_unchecked() + .registers + .get_unchecked(register_index) + } + } + } + + pub fn set_register(&mut self, to_register: u8, register: Register) { + let to_register = to_register as usize; + + self.call_stack.last_mut_unchecked().registers[to_register] = register; + } + + pub fn open_register_unchecked(&self, register_index: u8) -> &Value { + let register_index = register_index as usize; + + let register = if cfg!(debug_assertions) { + &self.call_stack.last_unchecked().registers[register_index] + } else { + unsafe { + self.call_stack + .last_unchecked() + .registers + .get_unchecked(register_index) + } + }; + + match register { + Register::Value(value) => { + trace!("Register R{register_index} opened to value {value}"); + + value + } + Register::Pointer(pointer) => { + trace!("Open register R{register_index} opened to pointer {pointer}"); + + self.follow_pointer_unchecked(*pointer) + } + Register::Empty => panic!("VM Error: Register {register_index} is empty"), + } + } + + pub fn open_register_allow_empty_unchecked(&self, register_index: u8) -> Option<&Value> { + trace!("Open register R{register_index}"); + + let register = self.get_register_unchecked(register_index); + + match register { + Register::Value(value) => { + trace!("Register R{register_index} openned to value {value}"); + + Some(value) + } + Register::Pointer(pointer) => { + trace!("Open register R{register_index} openned to pointer {pointer}"); + + Some(self.follow_pointer_unchecked(*pointer)) + } + Register::Empty => None, + } + } + + pub fn empty_register_or_clone_constant_unchecked(&mut self, register_index: u8) -> Value { + let register_index = register_index as usize; + let old_register = replace( + &mut self.call_stack.last_mut_unchecked().registers[register_index], + Register::Empty, + ); + + match old_register { + Register::Value(value) => value, + Register::Pointer(pointer) => match pointer { + Pointer::Register(register_index) => { + self.empty_register_or_clone_constant_unchecked(register_index) + } + Pointer::Constant(constant_index) => { + self.get_constant_unchecked(constant_index).clone() + } + Pointer::Stack(stack_index, register_index) => { + let call = self.call_stack.get_unchecked_mut(stack_index); + + let old_register = replace( + &mut call.registers[register_index as usize], + Register::Empty, + ); + + match old_register { + Register::Value(value) => value, + Register::Pointer(pointer) => { + self.follow_pointer_unchecked(pointer).clone() + } + Register::Empty => panic!("VM Error: Register {register_index} is empty"), + } + } + }, + Register::Empty => panic!("VM Error: Register {register_index} is empty"), + } + } + + pub fn clone_register_value_or_constant_unchecked(&self, register_index: u8) -> Value { + let register = self.get_register_unchecked(register_index); + + match register { + Register::Value(value) => value.clone(), + Register::Pointer(pointer) => match pointer { + Pointer::Register(register_index) => { + self.open_register_unchecked(*register_index).clone() + } + Pointer::Constant(constant_index) => { + self.get_constant_unchecked(*constant_index).clone() + } + Pointer::Stack(stack_index, register_index) => { + let call = self.call_stack.get_unchecked(*stack_index); + let register = &call.registers[*register_index as usize]; + + match register { + Register::Value(value) => value.clone(), + Register::Pointer(pointer) => { + self.follow_pointer_unchecked(*pointer).clone() + } + Register::Empty => panic!("VM Error: Register {register_index} is empty"), + } + } + }, + Register::Empty => panic!("VM Error: Register {register_index} is empty"), + } + } + + /// DRY helper to get a value from an Argument + pub fn get_argument_unchecked(&self, argument: Argument) -> &Value { + match argument { + Argument::Constant(constant_index) => self.get_constant_unchecked(constant_index), + Argument::Register(register_index) => self.open_register_unchecked(register_index), + } + } + + pub fn get_constant_unchecked(&self, constant_index: u8) -> &Value { + let constant_index = constant_index as usize; + + if cfg!(debug_assertions) { + &self.call_stack.last().unwrap().chunk.constants[constant_index] + } else { + unsafe { + self.call_stack + .last_unchecked() + .chunk + .constants + .get_unchecked(constant_index) + } + } + } + + pub fn get_local_register(&self, local_index: u8) -> u8 { + let local_index = local_index as usize; + let chunk = self.call_stack.last_unchecked().chunk; + + assert!( + local_index < chunk.locals.len(), + "VM Error: Local index out of bounds" + ); + + chunk.locals[local_index].register_index + } +} diff --git a/dust-lang/tests/assignment_errors.rs b/dust-lang/tests/assignment_errors.rs new file mode 100644 index 0000000..a117612 --- /dev/null +++ b/dust-lang/tests/assignment_errors.rs @@ -0,0 +1,81 @@ +use dust_lang::*; + +#[test] +fn add_assign_expects_mutable_variable() { + let source = "1 += 2"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::ExpectedMutableVariable { + found: Token::Integer("1").to_owned(), + position: Span(0, 1) + }, + source + }) + ); +} + +#[test] +fn divide_assign_expects_mutable_variable() { + let source = "1 -= 2"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::ExpectedMutableVariable { + found: Token::Integer("1").to_owned(), + position: Span(0, 1) + }, + source + }) + ); +} + +#[test] +fn multiply_assign_expects_mutable_variable() { + let source = "1 *= 2"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::ExpectedMutableVariable { + found: Token::Integer("1").to_owned(), + position: Span(0, 1) + }, + source + }) + ); +} + +#[test] +fn subtract_assign_expects_mutable_variable() { + let source = "1 -= 2"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::ExpectedMutableVariable { + found: Token::Integer("1").to_owned(), + position: Span(0, 1) + }, + source + }) + ); +} + +#[test] +fn modulo_assign_expects_mutable_variable() { + let source = "1 %= 2"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::ExpectedMutableVariable { + found: Token::Integer("1").to_owned(), + position: Span(0, 1) + }, + source + }) + ); +} diff --git a/dust-lang/tests/basic.rs b/dust-lang/tests/basic.rs index f6be57e..442592c 100644 --- a/dust-lang/tests/basic.rs +++ b/dust-lang/tests/basic.rs @@ -11,15 +11,11 @@ fn constant() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::Integer) + return_type: Type::Integer }, vec![ - ( - Instruction::load_constant(Destination::Register(0), 0, false), - Type::Integer, - Span(0, 2) - ), - (Instruction::r#return(true), Type::None, Span(2, 2)) + (Instruction::load_constant(0, 0, false), Span(0, 2)), + (Instruction::r#return(true), Span(2, 2)) ], vec![ConcreteValue::Integer(42)], vec![] @@ -40,9 +36,9 @@ fn empty() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::None) + return_type: Type::None }, - vec![(Instruction::r#return(false), Type::None, Span(0, 0))], + vec![(Instruction::r#return(false), Span(0, 0))], vec![], vec![] )) @@ -61,28 +57,18 @@ fn parentheses_precedence() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::Integer) + return_type: Type::Integer }, vec![ ( - Instruction::add( - Destination::Register(0), - Argument::Constant(0), - Argument::Constant(1) - ), - Type::Integer, + Instruction::add(0, Argument::Constant(0), Argument::Constant(1)), Span(3, 4) ), ( - Instruction::multiply( - Destination::Register(1), - Argument::Register(0), - Argument::Constant(2) - ), - Type::Integer, + Instruction::multiply(1, Argument::Register(0), Argument::Constant(2)), Span(8, 9) ), - (Instruction::r#return(true), Type::None, Span(11, 11)), + (Instruction::r#return(true), Span(11, 11)), ], vec![ ConcreteValue::Integer(1), @@ -95,3 +81,49 @@ fn parentheses_precedence() { assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(9)))); } + +#[test] +fn math_operator_precedence() { + let source = "1 + 2 - 3 * 4 / 5"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Integer, + }, + vec![ + ( + Instruction::add(0, Argument::Constant(0), Argument::Constant(1)), + Span(2, 3) + ), + ( + Instruction::multiply(1, Argument::Constant(2), Argument::Constant(3)), + Span(10, 11) + ), + ( + Instruction::divide(2, Argument::Register(1), Argument::Constant(4)), + Span(14, 15) + ), + ( + Instruction::subtract(3, Argument::Register(0), Argument::Register(2)), + Span(6, 7) + ), + (Instruction::r#return(true), Span(17, 17)), + ], + vec![ + ConcreteValue::Integer(1), + ConcreteValue::Integer(2), + ConcreteValue::Integer(3), + ConcreteValue::Integer(4), + ConcreteValue::Integer(5), + ], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(1)))); +} diff --git a/dust-lang/tests/comparison.rs b/dust-lang/tests/comparison.rs index 1b19be9..ae19d6c 100644 --- a/dust-lang/tests/comparison.rs +++ b/dust-lang/tests/comparison.rs @@ -11,26 +11,14 @@ fn equal() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::Boolean) + return_type: Type::Boolean }, vec![ ( - Instruction::equal(true, Argument::Constant(0), Argument::Constant(1)), - Type::None, + Instruction::equal(0, true, Argument::Constant(0), Argument::Constant(1)), Span(2, 4) ), - (Instruction::jump(1, true), Type::None, Span(2, 4)), - ( - Instruction::load_boolean(Destination::Register(0), true, true), - Type::Boolean, - Span(2, 4) - ), - ( - Instruction::load_boolean(Destination::Register(0), false, false), - Type::Boolean, - Span(2, 4) - ), - (Instruction::r#return(true), Type::None, Span(6, 6)), + (Instruction::r#return(true), Span(6, 6)), ], vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)], vec![] @@ -51,26 +39,14 @@ fn greater() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::Boolean) + return_type: Type::Boolean }, vec![ ( - Instruction::less_equal(false, Argument::Constant(0), Argument::Constant(1)), - Type::None, + Instruction::less_equal(0, false, Argument::Constant(0), Argument::Constant(1)), Span(2, 3) ), - (Instruction::jump(1, true), Type::None, Span(2, 3)), - ( - Instruction::load_boolean(Destination::Register(0), true, true), - Type::Boolean, - Span(2, 3) - ), - ( - Instruction::load_boolean(Destination::Register(0), false, false), - Type::Boolean, - Span(2, 3) - ), - (Instruction::r#return(true), Type::None, Span(5, 5)), + (Instruction::r#return(true), Span(5, 5)), ], vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)], vec![] @@ -91,26 +67,14 @@ fn greater_than_or_equal() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::Boolean) + return_type: Type::Boolean }, vec![ ( - Instruction::less(false, Argument::Constant(0), Argument::Constant(1)), - Type::None, + Instruction::less(0, false, Argument::Constant(0), Argument::Constant(1)), Span(2, 4) ), - (Instruction::jump(1, true), Type::None, Span(2, 4)), - ( - Instruction::load_boolean(Destination::Register(0), true, true), - Type::Boolean, - Span(2, 4) - ), - ( - Instruction::load_boolean(Destination::Register(0), false, false), - Type::Boolean, - Span(2, 4) - ), - (Instruction::r#return(true), Type::None, Span(6, 6)), + (Instruction::r#return(true), Span(6, 6)), ], vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)], vec![] @@ -131,26 +95,14 @@ fn less_than() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::Boolean) + return_type: Type::Boolean }, vec![ ( - Instruction::less(true, Argument::Constant(0), Argument::Constant(1)), - Type::None, + Instruction::less(0, true, Argument::Constant(0), Argument::Constant(1)), Span(2, 3) ), - (Instruction::jump(1, true), Type::None, Span(2, 3)), - ( - Instruction::load_boolean(Destination::Register(0), true, true), - Type::Boolean, - Span(2, 3) - ), - ( - Instruction::load_boolean(Destination::Register(0), false, false), - Type::Boolean, - Span(2, 3) - ), - (Instruction::r#return(true), Type::None, Span(5, 5)), + (Instruction::r#return(true), Span(5, 5)), ], vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)], vec![] @@ -171,26 +123,14 @@ fn less_than_or_equal() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::Boolean) + return_type: Type::Boolean }, vec![ ( - Instruction::less_equal(true, Argument::Constant(0), Argument::Constant(1)), - Type::None, + Instruction::less_equal(0, true, Argument::Constant(0), Argument::Constant(1)), Span(2, 4) ), - (Instruction::jump(1, true), Type::None, Span(2, 4)), - ( - Instruction::load_boolean(Destination::Register(0), true, true), - Type::Boolean, - Span(2, 4) - ), - ( - Instruction::load_boolean(Destination::Register(0), false, false), - Type::Boolean, - Span(2, 4) - ), - (Instruction::r#return(true), Type::None, Span(6, 6)), + (Instruction::r#return(true), Span(6, 6)), ], vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)], vec![] @@ -211,26 +151,14 @@ fn not_equal() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::Boolean) + return_type: Type::Boolean }, vec![ ( - Instruction::equal(false, Argument::Constant(0), Argument::Constant(1)), - Type::None, + Instruction::equal(0, false, Argument::Constant(0), Argument::Constant(1)), Span(2, 4) ), - (Instruction::jump(1, true), Type::None, Span(2, 4)), - ( - Instruction::load_boolean(Destination::Register(0), true, true), - Type::Boolean, - Span(2, 4) - ), - ( - Instruction::load_boolean(Destination::Register(0), false, false), - Type::Boolean, - Span(2, 4) - ), - (Instruction::r#return(true), Type::None, Span(6, 6)), + (Instruction::r#return(true), Span(6, 6)), ], vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)], vec![] diff --git a/dust-lang/tests/control_flow/comparison_expressions.rs b/dust-lang/tests/control_flow/comparison_expressions.rs deleted file mode 100644 index 31eb35f..0000000 --- a/dust-lang/tests/control_flow/comparison_expressions.rs +++ /dev/null @@ -1,409 +0,0 @@ -use dust_lang::*; - -#[test] -fn equality_assignment_long() { - let source = "let a = if 4 == 4 { true } else { false }; a"; - - assert_eq!( - compile(source), - Ok(Chunk::with_data( - None, - FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::Boolean) - }, - vec![ - ( - *Instruction::equal(true, 0, 0) - .set_b_is_constant() - .set_c_is_constant(), - Span(13, 15) - ), - (Instruction::jump(1, true), Span(18, 19)), - (Instruction::load_boolean(0, true, true), Span(20, 24)), - (Instruction::load_boolean(0, false, false), Span(34, 39)), - (Instruction::define_local(0, 0, false), Span(4, 5)), - (Instruction::get_local(1, 0), Span(43, 44)), - (Instruction::r#return(true), Span(44, 44)), - ], - vec![ConcreteValue::Integer(4), ConcreteValue::string("a")], - vec![Local::new(1, Type::Boolean, false, Scope::default(),)] - )), - ); - - assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(true)))); -} - -#[test] -fn equality_assignment_short() { - let source = "let a = 4 == 4 a"; - - assert_eq!( - compile(source), - Ok(Chunk::with_data( - None, - FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::Boolean) - }, - vec![ - ( - *Instruction::equal(true, 0, 0) - .set_b_is_constant() - .set_c_is_constant(), - Span(10, 12) - ), - (Instruction::jump(1, true), Span(10, 12)), - (Instruction::load_boolean(0, true, true), Span(10, 12)), - (Instruction::load_boolean(0, false, false), Span(10, 12)), - (Instruction::define_local(0, 0, false), Span(4, 5)), - (Instruction::get_local(1, 0), Span(15, 16)), - (Instruction::r#return(true), Span(16, 16)), - ], - vec![ConcreteValue::Integer(4), ConcreteValue::string("a")], - vec![Local::new(1, Type::Boolean, false, Scope::default())] - )), - ); - - assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(true)))); -} - -#[test] -fn if_else_assigment_false() { - let source = r#" - let a = if 4 == 3 { - panic(); - 0 - } else { - 1; 2; 3; 4; - 42 - }; - a"#; - - assert_eq!( - compile(source), - Ok(Chunk::with_data( - None, - FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::Integer) - }, - vec![ - ( - *Instruction::equal(true, 0, 1) - .set_b_is_constant() - .set_c_is_constant(), - Span(22, 24) - ), - (Instruction::jump(3, true), Span(27, 28)), - ( - Instruction::call_native(0, NativeFunction::Panic, 0), - Span(41, 48) - ), - (Instruction::load_constant(0, 2, false), Span(62, 63)), - (Instruction::jump(5, true), Span(129, 130)), - (Instruction::load_constant(1, 3, false), Span(93, 94)), - (Instruction::load_constant(2, 4, false), Span(96, 97)), - (Instruction::load_constant(3, 1, false), Span(99, 100)), - (Instruction::load_constant(4, 0, false), Span(102, 103)), - (Instruction::load_constant(5, 5, false), Span(117, 119)), - (Instruction::r#move(5, 0), Span(129, 130)), - (Instruction::define_local(5, 0, false), Span(13, 14)), - (Instruction::get_local(6, 0), Span(139, 140)), - (Instruction::r#return(true), Span(140, 140)), - ], - vec![ - ConcreteValue::Integer(4), - ConcreteValue::Integer(3), - ConcreteValue::Integer(0), - ConcreteValue::Integer(1), - ConcreteValue::Integer(2), - ConcreteValue::Integer(42), - ConcreteValue::string("a") - ], - vec![Local::new(6, Type::Integer, false, Scope::default())] - )), - ); - - assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(42)))); -} - -#[test] -fn if_else_assigment_true() { - let source = r#" - let a = if 4 == 4 { - 1; 2; 3; 4; - 42 - } else { - panic(); - 0 - }; - a"#; - - assert_eq!( - compile(source), - Ok(Chunk::with_data( - None, - FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::Integer) - }, - vec![ - ( - *Instruction::equal(true, 0, 0) - .set_b_is_constant() - .set_c_is_constant(), - Span(22, 24) - ), - (Instruction::jump(6, true), Span(27, 28)), - (Instruction::load_constant(0, 1, false), Span(41, 42)), - (Instruction::load_constant(1, 2, false), Span(44, 45)), - (Instruction::load_constant(2, 3, false), Span(47, 48)), - (Instruction::load_constant(3, 0, false), Span(50, 51)), - (Instruction::load_constant(4, 4, false), Span(65, 67)), - (Instruction::jump(2, true), Span(129, 130)), - ( - Instruction::call_native(5, NativeFunction::Panic, 0), - Span(97, 104) - ), - (Instruction::load_constant(5, 5, false), Span(118, 119)), - (Instruction::r#move(5, 4), Span(129, 130)), - (Instruction::define_local(5, 0, false), Span(13, 14)), - (Instruction::get_local(6, 0), Span(139, 140)), - (Instruction::r#return(true), Span(140, 140)), - ], - vec![ - ConcreteValue::Integer(4), - ConcreteValue::Integer(1), - ConcreteValue::Integer(2), - ConcreteValue::Integer(3), - ConcreteValue::Integer(42), - ConcreteValue::Integer(0), - ConcreteValue::string("a") - ], - vec![Local::new(6, Type::Integer, false, Scope::default())] - )), - ); - - assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(42)))); -} - -#[test] -fn if_else_complex() { - let source = " - if 1 == 1 { - 1; 2; 3; 4; - } else { - 1; 2; 3; 4; - }"; - - assert_eq!( - compile(source), - Ok(Chunk::with_data( - None, - FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::None) - }, - vec![ - ( - *Instruction::equal(true, 0, 0) - .set_b_is_constant() - .set_c_is_constant(), - Span(14, 16) - ), - (Instruction::jump(5, true), Span(19, 20)), - (Instruction::load_constant(0, 0, false), Span(33, 34)), - (Instruction::load_constant(1, 1, false), Span(36, 37)), - (Instruction::load_constant(2, 2, false), Span(39, 40)), - (Instruction::load_constant(3, 3, false), Span(42, 43)), - (Instruction::jump(4, true), Span(95, 95)), - (Instruction::load_constant(4, 0, false), Span(74, 75)), - (Instruction::load_constant(5, 1, false), Span(77, 78)), - (Instruction::load_constant(6, 2, false), Span(80, 81)), - (Instruction::load_constant(7, 3, false), Span(83, 84)), - (Instruction::r#move(7, 3), Span(95, 95)), - (Instruction::r#return(false), Span(95, 95)), - ], - vec![ - ConcreteValue::Integer(1), - ConcreteValue::Integer(2), - ConcreteValue::Integer(3), - ConcreteValue::Integer(4), - ], - vec![] - )) - ); - - assert_eq!(run(source), Ok(None)); -} - -#[test] -fn if_else_false() { - let source = "if 1 == 2 { panic(); 0 } else { 42 }"; - - assert_eq!( - compile(source), - Ok(Chunk::with_data( - None, - FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::Integer) - }, - vec![ - ( - *Instruction::equal(true, 0, 1) - .set_b_is_constant() - .set_c_is_constant(), - Span(5, 7) - ), - (Instruction::jump(2, true), Span(10, 11)), - ( - Instruction::call_native(0, NativeFunction::Panic, 0), - Span(12, 19) - ), - (Instruction::load_constant(0, 2, true), Span(21, 22)), - (Instruction::load_constant(1, 3, false), Span(32, 34)), - (Instruction::r#move(1, 0), Span(36, 36)), - (Instruction::r#return(true), Span(36, 36)), - ], - vec![ - ConcreteValue::Integer(1), - ConcreteValue::Integer(2), - ConcreteValue::Integer(0), - ConcreteValue::Integer(42) - ], - vec![] - )), - ); - - assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(42)))); -} - -#[test] -fn if_else_true() { - let source = "if 1 == 1 { 42 } else { panic(); 0 }"; - - assert_eq!( - compile(source), - Ok(Chunk::with_data( - None, - FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::Integer) - }, - vec![ - ( - *Instruction::equal(true, 0, 0) - .set_b_is_constant() - .set_c_is_constant(), - Span(5, 7) - ), - (Instruction::jump(2, true), Span(10, 11)), - (Instruction::load_constant(0, 1, false), Span(12, 14)), - (Instruction::jump(2, true), Span(36, 36)), - ( - Instruction::call_native(1, NativeFunction::Panic, 0), - Span(24, 31) - ), - (Instruction::load_constant(1, 2, false), Span(33, 34)), - (Instruction::r#move(1, 0), Span(36, 36)), - (Instruction::r#return(true), Span(36, 36)) - ], - vec![ - ConcreteValue::Integer(1), - ConcreteValue::Integer(42), - ConcreteValue::Integer(0) - ], - vec![] - )), - ); - - assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(42)))); -} - -#[test] -fn if_false() { - let source = "if 1 == 2 { panic() }"; - - assert_eq!( - compile(source), - Ok(Chunk::with_data( - None, - FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::None) - }, - vec![ - ( - *Instruction::equal(true, 0, 1) - .set_b_is_constant() - .set_c_is_constant(), - Span(5, 7) - ), - (Instruction::jump(1, true), Span(10, 11)), - ( - Instruction::call_native(0, NativeFunction::Panic, 0), - Span(12, 19) - ), - (Instruction::r#return(false), Span(21, 21)) - ], - vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)], - vec![] - )), - ); - - assert_eq!(run(source), Ok(None)); -} - -#[test] -fn if_true() { - let source = "if 1 == 1 { panic() }"; - - assert_eq!( - compile(source), - Ok(Chunk::with_data( - None, - FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::None) - }, - vec![ - ( - *Instruction::equal(true, 0, 0) - .set_b_is_constant() - .set_c_is_constant(), - Span(5, 7) - ), - (Instruction::jump(1, true), Span(10, 11)), - ( - Instruction::call_native(0, NativeFunction::Panic, 0), - Span(12, 19) - ), - (Instruction::r#return(false), Span(21, 21)) - ], - vec![ConcreteValue::Integer(1)], - vec![] - )), - ); - - assert_eq!( - run(source), - Err(DustError::Runtime { - error: VmError::NativeFunction(NativeFunctionError::Panic { - message: None, - position: Span(12, 19) - }), - source - }) - ); -} diff --git a/dust-lang/tests/control_flow/logic_expressions.rs b/dust-lang/tests/control_flow/logic_expressions.rs deleted file mode 100644 index a00b14c..0000000 --- a/dust-lang/tests/control_flow/logic_expressions.rs +++ /dev/null @@ -1,34 +0,0 @@ -#[test] -fn if_true() { - let source = "if true && true { 42 } else { 0 }"; - - assert_eq!( - compile(source), - Ok(Chunk::with_data( - None, - FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::None) - }, - vec![ - ( - *Instruction::equal(true, 0, 0) - .set_b_is_constant() - .set_c_is_constant(), - Span(5, 7) - ), - (Instruction::jump(1, true), Span(10, 11)), - ( - Instruction::call_native(0, NativeFunction::Panic, 0), - Span(12, 19) - ), - (Instruction::r#return(false), Span(21, 21)) - ], - vec![ConcreteValue::Integer(1)], - vec![] - )), - ); - - assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(42))),); -} diff --git a/dust-lang/tests/functions.rs b/dust-lang/tests/functions.rs index 6eab230..1ffdcfa 100644 --- a/dust-lang/tests/functions.rs +++ b/dust-lang/tests/functions.rs @@ -1,4 +1,5 @@ use dust_lang::*; +use smallvec::smallvec; #[test] fn function() { @@ -6,33 +7,28 @@ fn function() { assert_eq!( run(source), - Ok(Some(ConcreteValue::Function(Chunk::with_data( + Ok(Some(ConcreteValue::function(Chunk::with_data( None, FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::Function(FunctionType { + return_type: Type::function(FunctionType { type_parameters: None, - value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]), - return_type: Box::new(Type::Integer), - })) + value_parameters: Some(smallvec![(0, Type::Integer), (1, Type::Integer)]), + return_type: Type::Integer, + }) }, vec![ ( - Instruction::add( - Destination::Register(2), - Argument::Local(0), - Argument::Local(1) - ), - Type::Integer, + Instruction::add(2, Argument::Register(0), Argument::Register(1)), Span(30, 31) ), - (Instruction::r#return(true), Type::None, Span(35, 35)), + (Instruction::r#return(true), Span(34, 35)), ], vec![ConcreteValue::string("a"), ConcreteValue::string("b"),], vec![ - Local::new(0, Type::Integer, false, Scope::default()), - Local::new(1, Type::Integer, false, Scope::default()) + Local::new(0, 0, false, Scope::default()), + Local::new(1, 1, false, Scope::default()) ] )))) ); @@ -49,59 +45,34 @@ fn function_call() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::Integer) + return_type: Type::Integer }, vec![ - ( - Instruction::load_constant(Destination::Register(0), 0, false), - Type::Function(FunctionType { - type_parameters: None, - value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]), - return_type: Box::new(Type::Integer), - }), - Span(0, 36) - ), - ( - Instruction::load_constant(Destination::Register(1), 1, false), - Type::Integer, - Span(36, 37) - ), - ( - Instruction::load_constant(Destination::Register(2), 2, false), - Type::Integer, - Span(39, 40) - ), - ( - Instruction::call(Destination::Register(3), Argument::Constant(0), 2), - Type::Integer, - Span(35, 41) - ), - (Instruction::r#return(true), Type::None, Span(41, 41)), + (Instruction::load_constant(0, 0, false), Span(0, 35)), + (Instruction::load_constant(1, 1, false), Span(36, 37)), + (Instruction::load_constant(2, 2, false), Span(39, 40)), + (Instruction::call(3, Argument::Constant(0), 2), Span(35, 41)), + (Instruction::r#return(true), Span(41, 41)), ], vec![ - ConcreteValue::Function(Chunk::with_data( + ConcreteValue::function(Chunk::with_data( None, FunctionType { type_parameters: None, - value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]), - return_type: Box::new(Type::Integer) + value_parameters: Some(smallvec![(0, Type::Integer), (1, Type::Integer)]), + return_type: Type::Integer }, vec![ ( - Instruction::add( - Destination::Register(2), - Argument::Local(0), - Argument::Local(1) - ), - Type::Integer, + Instruction::add(2, Argument::Register(0), Argument::Register(1)), Span(30, 31) ), - (Instruction::r#return(true), Type::None, Span(35, 36)), + (Instruction::r#return(true), Span(34, 35)), ], vec![ConcreteValue::string("a"), ConcreteValue::string("b"),], vec![ - Local::new(0, Type::Integer, false, Scope::default()), - Local::new(1, Type::Integer, false, Scope::default()) + Local::new(0, 0, false, Scope::default()), + Local::new(1, 1, false, Scope::default()) ] )), ConcreteValue::Integer(1), @@ -125,63 +96,36 @@ fn function_declaration() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::None) + return_type: Type::None }, vec![ - ( - Instruction::load_constant(Destination::Register(0), 0, false), - Type::Function(FunctionType { - type_parameters: None, - value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]), - return_type: Box::new(Type::Integer), - }), - Span(0, 40) - ), - ( - Instruction::define_local(0, 0, false), - Type::None, - Span(3, 6) - ), - (Instruction::r#return(false), Type::None, Span(40, 40)) + (Instruction::load_constant(0, 0, false), Span(0, 40)), + (Instruction::r#return(false), Span(40, 40)) ], vec![ - ConcreteValue::Function(Chunk::with_data( - Some("add".to_string()), + ConcreteValue::function(Chunk::with_data( + Some("add".into()), FunctionType { type_parameters: None, - value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]), - return_type: Box::new(Type::Integer) + value_parameters: Some(smallvec![(0, Type::Integer), (1, Type::Integer)]), + return_type: Type::Integer }, vec![ ( - Instruction::add( - Destination::Register(2), - Argument::Local(0), - Argument::Local(1) - ), - Type::Integer, + Instruction::add(2, Argument::Register(0), Argument::Register(1)), Span(35, 36) ), - (Instruction::r#return(true), Type::None, Span(40, 40)), + (Instruction::r#return(true), Span(39, 40)), ], vec![ConcreteValue::string("a"), ConcreteValue::string("b")], vec![ - Local::new(0, Type::Integer, false, Scope::default()), - Local::new(1, Type::Integer, false, Scope::default()) + Local::new(0, 0, false, Scope::default()), + Local::new(1, 1, false, Scope::default()) ] )), ConcreteValue::string("add"), ], - vec![Local::new( - 1, - Type::Function(FunctionType { - type_parameters: None, - value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]), - return_type: Box::new(Type::Integer), - }), - false, - Scope::default(), - ),], + vec![Local::new(1, 0, false, Scope::default(),),], )), ); diff --git a/dust-lang/tests/lists.rs b/dust-lang/tests/lists.rs index c8fcd5e..b8d8aac 100644 --- a/dust-lang/tests/lists.rs +++ b/dust-lang/tests/lists.rs @@ -11,15 +11,11 @@ fn empty_list() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::List(Box::new(Type::Any))), + return_type: Type::List(Box::new(Type::Any)), }, vec![ - ( - Instruction::load_list(Destination::Register(0), 0), - Type::List(Box::new(Type::Any)), - Span(0, 2) - ), - (Instruction::r#return(true), Type::None, Span(2, 2)), + (Instruction::load_list(0, 0), Span(0, 2)), + (Instruction::r#return(true), Span(2, 2)), ], vec![], vec![] @@ -40,30 +36,14 @@ fn list() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::List(Box::new(Type::Integer))), + return_type: Type::List(Box::new(Type::Integer)), }, vec![ - ( - Instruction::load_constant(Destination::Register(0), 0, false), - Type::Integer, - Span(1, 2) - ), - ( - Instruction::load_constant(Destination::Register(1), 1, false), - Type::Integer, - Span(4, 5) - ), - ( - Instruction::load_constant(Destination::Register(2), 2, false), - Type::Integer, - Span(7, 8) - ), - ( - Instruction::load_list(Destination::Register(3), 0), - Type::List(Box::new(Type::Integer)), - Span(0, 9) - ), - (Instruction::r#return(true), Type::None, Span(9, 9)), + (Instruction::load_constant(0, 0, false), Span(1, 2)), + (Instruction::load_constant(1, 1, false), Span(4, 5)), + (Instruction::load_constant(2, 2, false), Span(7, 8)), + (Instruction::load_list(3, 0), Span(0, 9)), + (Instruction::r#return(true), Span(9, 9)), ], vec![ ConcreteValue::Integer(1), @@ -95,48 +75,25 @@ fn list_with_complex_expression() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::List(Box::new(Type::Integer))), + return_type: Type::List(Box::new(Type::Integer)), }, vec![ + (Instruction::load_constant(0, 0, false), Span(1, 2)), ( - Instruction::load_constant(Destination::Register(0), 0, false), - Type::Integer, - Span(1, 2) - ), - ( - Instruction::add( - Destination::Register(1), - Argument::Constant(1), - Argument::Constant(2) - ), - Type::Integer, + Instruction::add(1, Argument::Constant(1), Argument::Constant(2)), Span(6, 7) ), ( - Instruction::multiply( - Destination::Register(2), - Argument::Constant(3), - Argument::Constant(4) - ), - Type::Integer, + Instruction::multiply(2, Argument::Constant(3), Argument::Constant(4)), Span(14, 15) ), ( - Instruction::subtract( - Destination::Register(3), - Argument::Register(1), - Argument::Register(2) - ), - Type::Integer, + Instruction::subtract(3, Argument::Register(1), Argument::Register(2)), Span(10, 11) ), - (Instruction::close(1, 3), Type::None, Span(17, 18)), - ( - Instruction::load_list(Destination::Register(4), 0), - Type::List(Box::new(Type::Integer)), - Span(0, 18) - ), - (Instruction::r#return(true), Type::None, Span(18, 18)), + (Instruction::close(1, 3), Span(17, 18)), + (Instruction::load_list(4, 0), Span(0, 18)), + (Instruction::r#return(true), Span(18, 18)), ], vec![ ConcreteValue::Integer(1), @@ -169,34 +126,17 @@ fn list_with_simple_expression() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::List(Box::new(Type::Integer))), + return_type: Type::List(Box::new(Type::Integer)), }, vec![ + (Instruction::load_constant(0, 0, false), Span(1, 2)), ( - Instruction::load_constant(Destination::Register(0), 0, false), - Type::Integer, - Span(1, 2) - ), - ( - Instruction::add( - Destination::Register(1), - Argument::Constant(1), - Argument::Constant(2) - ), - Type::Integer, + Instruction::add(1, Argument::Constant(1), Argument::Constant(2)), Span(6, 7) ), - ( - Instruction::load_constant(Destination::Register(2), 3, false), - Type::Integer, - Span(11, 12) - ), - ( - Instruction::load_list(Destination::Register(3), 0), - Type::List(Box::new(Type::Integer)), - Span(0, 13) - ), - (Instruction::r#return(true), Type::None, Span(13, 13)), + (Instruction::load_constant(2, 3, false), Span(11, 12)), + (Instruction::load_list(3, 0), Span(0, 13)), + (Instruction::r#return(true), Span(13, 13)), ], vec![ ConcreteValue::Integer(1), diff --git a/dust-lang/tests/logic.rs b/dust-lang/tests/logic.rs deleted file mode 100644 index 5b1784c..0000000 --- a/dust-lang/tests/logic.rs +++ /dev/null @@ -1,160 +0,0 @@ -use dust_lang::*; - -#[test] -fn and() { - let source = "true && false"; - - assert_eq!( - compile(source), - Ok(Chunk::with_data( - None, - FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::Boolean), - }, - vec![ - ( - Instruction::load_boolean(Destination::Register(0), true, false), - Type::Boolean, - Span(0, 4) - ), - ( - Instruction::test(Argument::Register(0), true), - Type::None, - Span(5, 7) - ), - (Instruction::jump(1, true), Type::None, Span(5, 7)), - ( - Instruction::load_boolean(Destination::Register(1), false, false), - Type::Boolean, - Span(8, 13) - ), - (Instruction::r#return(true), Type::None, Span(13, 13)), - ], - vec![], - vec![] - )) - ); - - assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(false)))); -} - -#[test] -fn or() { - let source = "true || false"; - - assert_eq!( - compile(source), - Ok(Chunk::with_data( - None, - FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::Boolean), - }, - vec![ - ( - Instruction::load_boolean(Destination::Register(0), true, false), - Type::Boolean, - Span(0, 4) - ), - ( - Instruction::test(Argument::Register(0), false), - Type::None, - Span(5, 7) - ), - (Instruction::jump(1, true), Type::None, Span(5, 7)), - ( - Instruction::load_boolean(Destination::Register(1), false, false), - Type::Boolean, - Span(8, 13) - ), - (Instruction::r#return(true), Type::None, Span(13, 13)), - ], - vec![], - vec![] - )) - ); - - assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(true)))); -} - -#[test] -fn and_and_or() { - let source = "let a = true; let b = true; let c = false; a && b || c"; - - assert_eq!( - compile(source), - Ok(Chunk::with_data( - None, - FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::Boolean), - }, - vec![ - ( - Instruction::load_boolean(Destination::Register(0), true, false), - Type::Boolean, - Span(8, 12) - ), - ( - Instruction::define_local(0, 0, false), - Type::None, - Span(4, 5) - ), - ( - Instruction::load_boolean(Destination::Register(1), true, false), - Type::Boolean, - Span(22, 26) - ), - ( - Instruction::define_local(1, 1, false), - Type::None, - Span(18, 19) - ), - ( - Instruction::load_boolean(Destination::Register(2), false, false), - Type::Boolean, - Span(36, 41) - ), - ( - Instruction::define_local(2, 2, false), - Type::None, - Span(32, 33) - ), - ( - Instruction::test(Argument::Local(0), true), - Type::None, - Span(45, 47) - ), - (Instruction::jump(1, true), Type::None, Span(45, 47)), - ( - Instruction::test(Argument::Local(1), false), - Type::None, - Span(50, 52) - ), - (Instruction::jump(1, true), Type::None, Span(50, 52)), - ( - Instruction::get_local(Destination::Register(3), 2), - Type::Boolean, - Span(53, 54) - ), - (Instruction::r#return(true), Type::None, Span(54, 54)), - ], - vec![ - ConcreteValue::string("a"), - ConcreteValue::string("b"), - ConcreteValue::string("c") - ], - vec![ - Local::new(0, Type::Boolean, false, Scope::default()), - Local::new(1, Type::Boolean, false, Scope::default()), - Local::new(2, Type::Boolean, false, Scope::default()) - ] - )) - ); - - assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(true)))); -} diff --git a/dust-lang/tests/logic/and.rs b/dust-lang/tests/logic/and.rs new file mode 100644 index 0000000..68f9d6a --- /dev/null +++ b/dust-lang/tests/logic/and.rs @@ -0,0 +1,146 @@ +use dust_lang::*; +use smallvec::smallvec; + +#[test] +fn true_and_true() { + let source = "true && true"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Boolean, + }, + smallvec![ + Instruction::load_boolean(0, true, false), + Instruction::test(0, true), + Instruction::jump(1, true), + Instruction::load_boolean(1, true, false), + Instruction::r#return(true), + ], + smallvec![ + Span(0, 4), + Span(5, 7), + Span(5, 7), + Span(8, 12), + Span(12, 12), + ], + smallvec![], + smallvec![], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(Value::boolean(true)))); +} + +#[test] +fn false_and_false() { + let source = "false && false"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Boolean, + }, + smallvec![ + Instruction::load_boolean(0, false, false), + Instruction::test(0, true), + Instruction::jump(1, true), + Instruction::load_boolean(1, false, false), + Instruction::r#return(true), + ], + smallvec![ + Span(0, 5), + Span(6, 8), + Span(6, 8), + Span(9, 14), + Span(14, 14), + ], + smallvec![], + smallvec![], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(Value::boolean(false)))); +} + +#[test] +fn false_and_true() { + let source = "false && true"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Boolean, + }, + smallvec![ + Instruction::load_boolean(0, false, false), + Instruction::test(0, true), + Instruction::jump(1, true), + Instruction::load_boolean(1, true, false), + Instruction::r#return(true), + ], + smallvec![ + Span(0, 5), + Span(6, 8), + Span(6, 8), + Span(9, 13), + Span(13, 13) + ], + smallvec![], + smallvec![], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(Value::boolean(false)))); +} + +#[test] +fn true_and_false() { + let source = "true && false"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Boolean, + }, + smallvec![ + Instruction::load_boolean(0, true, false), + Instruction::test(0, true), + Instruction::jump(1, true), + Instruction::load_boolean(1, false, false), + Instruction::r#return(true), + ], + smallvec![ + Span(0, 4), + Span(5, 7), + Span(5, 7), + Span(8, 13), + Span(13, 13) + ], + smallvec![], + smallvec![], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(Value::boolean(false)))); +} diff --git a/dust-lang/tests/logic/and_and.rs b/dust-lang/tests/logic/and_and.rs new file mode 100644 index 0000000..53c38ba --- /dev/null +++ b/dust-lang/tests/logic/and_and.rs @@ -0,0 +1,42 @@ +use dust_lang::*; +use smallvec::smallvec; + +#[test] +fn true_and_true_and_true() { + let source = "true && true && true"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Boolean, + }, + smallvec![ + Instruction::load_boolean(0, true, false), + Instruction::test(0, true), + Instruction::jump(1, true), + Instruction::load_boolean(1, true, false), + Instruction::test(1, true), + Instruction::jump(1, true), + Instruction::load_boolean(2, true, false), + Instruction::r#return(true), + ], + smallvec![ + Span(0, 4), + Span(5, 7), + Span(5, 7), + Span(8, 12), + Span(13, 15), + Span(13, 15), + Span(16, 20), + Span(20, 20) + ], + smallvec![], + smallvec![], + vec![], + )) + ); +} diff --git a/dust-lang/tests/logic/or.rs b/dust-lang/tests/logic/or.rs new file mode 100644 index 0000000..0886f23 --- /dev/null +++ b/dust-lang/tests/logic/or.rs @@ -0,0 +1,38 @@ +use dust_lang::*; +use smallvec::smallvec; + +#[test] +fn true_or_false() { + let source = "true || false"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Boolean, + }, + smallvec![ + Instruction::load_boolean(0, true, false), + Instruction::test(0, false), + Instruction::jump(1, true), + Instruction::load_boolean(1, false, false), + Instruction::r#return(true), + ], + smallvec![ + Span(0, 4), + Span(5, 7), + Span(5, 7), + Span(8, 13), + Span(13, 13), + ], + smallvec![], + smallvec![], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(Value::boolean(true)))); +} diff --git a/dust-lang/tests/loops.rs b/dust-lang/tests/loops.rs index 2ca09aa..252478c 100644 --- a/dust-lang/tests/loops.rs +++ b/dust-lang/tests/loops.rs @@ -11,41 +11,22 @@ fn r#while() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::Integer), + return_type: Type::Integer, }, vec![ + (Instruction::load_constant(0, 0, false), Span(12, 13)), ( - Instruction::load_constant(Destination::Register(0), 0, false), - Type::Integer, - Span(12, 13) - ), - ( - Instruction::define_local(0, 0, true), - Type::None, - Span(8, 9) - ), - ( - Instruction::less(true, Argument::Local(0), Argument::Constant(2)), - Type::None, + Instruction::less(0, true, Argument::Register(0), Argument::Constant(2)), Span(23, 24) ), - (Instruction::jump(2, true), Type::None, Span(41, 42)), + (Instruction::jump(2, true), Span(41, 42)), ( - Instruction::add( - Destination::Local(0), - Argument::Local(0), - Argument::Constant(3) - ), - Type::Integer, + Instruction::add(0, Argument::Register(0), Argument::Constant(3)), Span(35, 36) ), - (Instruction::jump(3, false), Type::None, Span(41, 42)), - ( - Instruction::get_local(Destination::Register(1), 0), - Type::Integer, - Span(41, 42) - ), - (Instruction::r#return(true), Type::None, Span(42, 42)), + (Instruction::jump(3, false), Span(41, 42)), + (Instruction::get_local(1, 0), Span(41, 42)), + (Instruction::r#return(true), Span(42, 42)), ], vec![ ConcreteValue::Integer(0), @@ -53,7 +34,7 @@ fn r#while() { ConcreteValue::Integer(5), ConcreteValue::Integer(1), ], - vec![Local::new(1, Type::Integer, true, Scope::default())] + vec![Local::new(1, 0, true, Scope::default())] )), ); diff --git a/dust-lang/tests/math.rs b/dust-lang/tests/math.rs deleted file mode 100644 index f17fe8c..0000000 --- a/dust-lang/tests/math.rs +++ /dev/null @@ -1,483 +0,0 @@ -use dust_lang::*; - -#[test] -fn add() { - let source = "1 + 2"; - - assert_eq!( - compile(source), - Ok(Chunk::with_data( - None, - FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::Integer), - }, - vec![ - ( - Instruction::add( - Destination::Register(0), - Argument::Constant(0), - Argument::Constant(1) - ), - Type::Integer, - Span(2, 3) - ), - (Instruction::r#return(true), Type::None, Span(5, 5)) - ], - vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)], - vec![] - )) - ); - - assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(3)))); -} - -#[test] -fn add_assign() { - let source = "let mut a = 1; a += 2; a"; - - assert_eq!( - compile(source), - Ok(Chunk::with_data( - None, - FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::Integer), - }, - vec![ - ( - Instruction::load_constant(Destination::Register(0), 0, false), - Type::Integer, - Span(12, 13) - ), - ( - Instruction::define_local(0, 0, true), - Type::None, - Span(8, 9) - ), - ( - Instruction::add( - Destination::Local(0), - Argument::Local(0), - Argument::Constant(2) - ), - Type::None, - Span(17, 19) - ), - ( - Instruction::get_local(Destination::Register(1), 0), - Type::Integer, - Span(23, 24) - ), - (Instruction::r#return(true), Type::None, Span(24, 24)) - ], - vec![ - ConcreteValue::Integer(1), - ConcreteValue::string("a"), - ConcreteValue::Integer(2) - ], - vec![Local::new(1, Type::Integer, true, Scope::default())] - )) - ); - - assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(3)))); -} - -#[test] -fn add_assign_expects_mutable_variable() { - let source = "1 += 2"; - - assert_eq!( - compile(source), - Err(DustError::Compile { - error: CompileError::ExpectedMutableVariable { - found: Token::Integer("1").to_owned(), - position: Span(0, 1) - }, - source - }) - ); -} - -// #[test] -// fn add_expects_integer_float_or_string() { -// let source = "true + false"; - -// assert_eq!( -// parse(source), -// Err(DustError::Parse { -// error: ParseError::ExpectedIntegerFloatOrString { -// found: Token::True, -// position: Span(0, 3) -// }, -// source -// }) -// ); -// } - -#[test] -fn divide() { - let source = "2 / 2"; - - assert_eq!( - compile(source), - Ok(Chunk::with_data( - None, - FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::Integer), - }, - vec![ - ( - Instruction::divide( - Destination::Register(0), - Argument::Constant(0), - Argument::Constant(0) - ), - Type::Integer, - Span(2, 3) - ), - (Instruction::r#return(true), Type::None, Span(5, 5)) - ], - vec![ConcreteValue::Integer(2)], - vec![] - )) - ); - - assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(1)))); -} - -#[test] -fn divide_assign() { - let source = "let mut a = 2; a /= 2; a"; - - assert_eq!( - compile(source), - Ok(Chunk::with_data( - None, - FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::Integer), - }, - vec![ - ( - Instruction::load_constant(Destination::Register(0), 0, false), - Type::Integer, - Span(12, 13) - ), - ( - Instruction::define_local(0, 0, true), - Type::None, - Span(8, 9) - ), - ( - Instruction::divide( - Destination::Local(0), - Argument::Local(0), - Argument::Constant(0) - ), - Type::None, - Span(17, 19) - ), - ( - Instruction::get_local(Destination::Register(1), 0), - Type::Integer, - Span(23, 24) - ), - (Instruction::r#return(true), Type::None, Span(24, 24)) - ], - vec![ConcreteValue::Integer(2), ConcreteValue::string("a")], - vec![Local::new(1, Type::Integer, true, Scope::default())] - )) - ); - - assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(1)))); -} - -#[test] -fn divide_assign_expects_mutable_variable() { - let source = "1 -= 2"; - - assert_eq!( - compile(source), - Err(DustError::Compile { - error: CompileError::ExpectedMutableVariable { - found: Token::Integer("1").to_owned(), - position: Span(0, 1) - }, - source - }) - ); -} - -#[test] -fn math_operator_precedence() { - let source = "1 + 2 - 3 * 4 / 5"; - - assert_eq!( - compile(source), - Ok(Chunk::with_data( - None, - FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::Integer), - }, - vec![ - ( - Instruction::add( - Destination::Register(0), - Argument::Constant(0), - Argument::Constant(1) - ), - Type::Integer, - Span(2, 3) - ), - ( - Instruction::multiply( - Destination::Register(1), - Argument::Constant(2), - Argument::Constant(3) - ), - Type::Integer, - Span(10, 11) - ), - ( - Instruction::divide( - Destination::Register(2), - Argument::Register(1), - Argument::Constant(4) - ), - Type::Integer, - Span(14, 15) - ), - ( - Instruction::subtract( - Destination::Register(3), - Argument::Register(0), - Argument::Register(2) - ), - Type::Integer, - Span(6, 7) - ), - (Instruction::r#return(true), Type::None, Span(17, 17)), - ], - vec![ - ConcreteValue::Integer(1), - ConcreteValue::Integer(2), - ConcreteValue::Integer(3), - ConcreteValue::Integer(4), - ConcreteValue::Integer(5), - ], - vec![] - )) - ); - - assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(1)))); -} - -#[test] -fn multiply() { - let source = "1 * 2"; - - assert_eq!( - compile(source), - Ok(Chunk::with_data( - None, - FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::Integer), - }, - vec![ - ( - Instruction::multiply( - Destination::Register(0), - Argument::Constant(0), - Argument::Constant(1) - ), - Type::Integer, - Span(2, 3) - ), - (Instruction::r#return(true), Type::None, Span(5, 5)), - ], - vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)], - vec![] - )) - ); - - assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(2)))); -} - -#[test] -fn multiply_assign() { - let source = "let mut a = 2; a *= 3 a"; - - assert_eq!( - compile(source), - Ok(Chunk::with_data( - None, - FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::Integer), - }, - vec![ - ( - Instruction::load_constant(Destination::Register(0), 0, false), - Type::Integer, - Span(12, 13) - ), - ( - Instruction::define_local(0, 0, true), - Type::None, - Span(8, 9) - ), - ( - Instruction::multiply( - Destination::Local(0), - Argument::Local(0), - Argument::Constant(2) - ), - Type::None, - Span(17, 19) - ), - ( - Instruction::get_local(Destination::Register(1), 0), - Type::Integer, - Span(22, 23) - ), - (Instruction::r#return(true), Type::None, Span(23, 23)) - ], - vec![ - ConcreteValue::Integer(2), - ConcreteValue::string("a"), - ConcreteValue::Integer(3) - ], - vec![Local::new(1, Type::Integer, true, Scope::default())] - )) - ); - - assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(6)))); -} - -#[test] -fn multiply_assign_expects_mutable_variable() { - let source = "1 *= 2"; - - assert_eq!( - compile(source), - Err(DustError::Compile { - error: CompileError::ExpectedMutableVariable { - found: Token::Integer("1").to_owned(), - position: Span(0, 1) - }, - source - }) - ); -} - -#[test] -fn subtract() { - let source = "1 - 2"; - - assert_eq!( - compile(source), - Ok(Chunk::with_data( - None, - FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::Integer), - }, - vec![ - ( - Instruction::subtract( - Destination::Register(0), - Argument::Constant(0), - Argument::Constant(1) - ), - Type::Integer, - Span(2, 3) - ), - (Instruction::r#return(true), Type::None, Span(5, 5)), - ], - vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)], - vec![] - )) - ); - - assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(-1)))); -} - -#[test] -fn subtract_assign() { - let source = "let mut x = 42; x -= 2; x"; - - assert_eq!( - compile(source), - Ok(Chunk::with_data( - None, - FunctionType { - type_parameters: None, - value_parameters: None, - return_type: Box::new(Type::Integer), - }, - vec![ - ( - Instruction::load_constant(Destination::Register(0), 0, false), - Type::Integer, - Span(12, 14) - ), - ( - Instruction::define_local(0, 0, true), - Type::None, - Span(8, 9) - ), - ( - Instruction::subtract( - Destination::Local(0), - Argument::Local(0), - Argument::Constant(2) - ), - Type::None, - Span(18, 20) - ), - ( - Instruction::get_local(Destination::Register(1), 0), - Type::Integer, - Span(24, 25) - ), - (Instruction::r#return(true), Type::None, Span(25, 25)), - ], - vec![ - ConcreteValue::Integer(42), - ConcreteValue::string("x"), - ConcreteValue::Integer(2) - ], - vec![Local::new(1, Type::Integer, true, Scope::default())] - )), - ); - - assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(40)))); -} - -#[test] -fn subtract_assign_expects_mutable_variable() { - let source = "1 -= 2"; - - assert_eq!( - compile(source), - Err(DustError::Compile { - error: CompileError::ExpectedMutableVariable { - found: Token::Integer("1").to_owned(), - position: Span(0, 1) - }, - source - }) - ); -} diff --git a/dust-lang/tests/math/add.rs b/dust-lang/tests/math/add.rs new file mode 100644 index 0000000..59c77cf --- /dev/null +++ b/dust-lang/tests/math/add.rs @@ -0,0 +1,270 @@ +use dust_lang::*; +use smallvec::smallvec; + +#[test] +fn add_bytes() { + let source = "0xfe + 0x01"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Byte, + }, + smallvec![ + Instruction::add(0, Argument::Constant(0), Argument::Constant(1)), + Instruction::r#return(true), + ], + smallvec![Span(5, 6), Span(11, 11),], + smallvec![Value::byte(0xfe), Value::byte(0x01)], + smallvec![], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(Value::byte(0xff)))); +} + +#[test] +fn add_bytes_saturate() { + let source = "0xff + 0x01"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Byte, + }, + smallvec![ + Instruction::add(0, Argument::Constant(0), Argument::Constant(1)), + Instruction::r#return(true), + ], + smallvec![Span(5, 6), Span(11, 11)], + smallvec![Value::byte(0xff), Value::byte(0x01)], + smallvec![], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(Value::byte(0xff)))); +} + +#[test] +fn add_characters() { + let source = "'a' + 'b'"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::String, + }, + smallvec![ + Instruction::add(0, Argument::Constant(0), Argument::Constant(1)), + Instruction::r#return(true) + ], + smallvec![Span(4, 5), Span(11, 11)], + smallvec![Value::character('a'), Value::character('b')], + smallvec![], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(Value::string("ab")))); +} + +#[test] +fn add_character_and_string() { + let source = "'a' + \"b\""; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::String, + }, + smallvec![ + Instruction::add(0, Argument::Constant(0), Argument::Constant(1)), + Instruction::r#return(true), + ], + smallvec![Span(4, 5), Span(9, 9),], + smallvec![Value::character('a'), Value::string("b")], + smallvec![], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(Value::string("ab")))); +} + +#[test] +fn add_floats() { + let source = "1.0 + 2.0"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Float, + }, + smallvec![ + Instruction::add(0, Argument::Constant(0), Argument::Constant(1)), + Instruction::r#return(true), + ], + smallvec![Span(4, 5), Span(9, 9),], + smallvec![Value::float(1.0), Value::float(2.0)], + smallvec![], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(Value::float(3.0)))); +} + +#[test] +fn add_floats_saturatate() { + let source = "1.7976931348623157E+308 + 0.00000001"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Float, + }, + smallvec![ + Instruction::add(0, Argument::Constant(0), Argument::Constant(1)), + Instruction::r#return(true), + ], + smallvec![Span(24, 25), Span(36, 36),], + smallvec![Value::float(f64::MAX), Value::float(0.00000001)], + smallvec![], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(Value::float(f64::MAX)))); +} + +#[test] +fn add_integers() { + let source = "1 + 2"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Integer, + }, + smallvec![ + Instruction::add(0, Argument::Constant(0), Argument::Constant(1)), + Instruction::r#return(true) + ], + smallvec![Span(2, 3), Span(5, 5),], + smallvec![Value::integer(1), Value::integer(2)], + smallvec![], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(Value::integer(3)))); +} + +#[test] +fn add_integers_saturate() { + let source = "9223372036854775807 + 1"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Integer, + }, + smallvec![ + Instruction::add(0, Argument::Constant(0), Argument::Constant(1)), + Instruction::r#return(true) + ], + smallvec![Span(20, 21), Span(23, 23),], + smallvec![Value::integer(i64::MAX), Value::integer(1)], + smallvec![], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(Value::integer(i64::MAX)))); +} + +#[test] +fn add_strings() { + let source = "\"Hello, \" + \"world!\""; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::String, + }, + smallvec![ + Instruction::add(0, Argument::Constant(0), Argument::Constant(1)), + Instruction::r#return(true), + ], + smallvec![Span(10, 11), Span(20, 20)], + smallvec![Value::string("Hello, "), Value::string("world!")], + smallvec![], + vec![] + )) + ); +} + +#[test] +fn add_string_and_character() { + let source = "\"a\" + 'b'"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::String, + }, + smallvec![ + Instruction::add(0, Argument::Constant(0), Argument::Constant(1)), + Instruction::r#return(true), + ], + smallvec![Span(4, 5), Span(9, 9),], + smallvec![Value::string("a"), Value::character('b')], + smallvec![], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(Value::string("ab")))); +} diff --git a/dust-lang/tests/math/add_assign.rs b/dust-lang/tests/math/add_assign.rs new file mode 100644 index 0000000..a434bb7 --- /dev/null +++ b/dust-lang/tests/math/add_assign.rs @@ -0,0 +1,31 @@ +use dust_lang::*; +use smallvec::smallvec; + +#[test] +fn add_assign() { + let source = "let mut a = 1; a += 2; a"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Integer, + }, + smallvec![ + Instruction::load_constant(0, 0, false), + Instruction::add(0, Argument::Register(0), Argument::Constant(2)), + Instruction::get_local(1, 0), + Instruction::r#return(true) + ], + smallvec![Span(12, 13), Span(17, 19), Span(23, 24), Span(24, 24)], + smallvec![Value::integer(1), Value::string("a"), Value::integer(2)], + smallvec![Local::new(1, 0, true, Scope::default())], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(Value::integer(3)))); +} diff --git a/dust-lang/tests/math/add_errors.rs b/dust-lang/tests/math/add_errors.rs new file mode 100644 index 0000000..6268790 --- /dev/null +++ b/dust-lang/tests/math/add_errors.rs @@ -0,0 +1,405 @@ +use dust_lang::*; + +#[test] +fn add_boolean_left() { + let source = "true + 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddType { + argument_type: Type::Boolean, + position: Span(0, 4) + }, + source, + }) + ); +} + +#[test] +fn add_boolean_right() { + let source = "1 + true"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddType { + argument_type: Type::Boolean, + position: Span(4, 8) + }, + source, + }) + ); +} + +#[test] +fn add_function_left() { + let source = "fn(){} + 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddType { + argument_type: Type::function(FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::None + }), + position: Span(0, 6) + }, + source, + }) + ); +} + +#[test] +fn add_function_right() { + let source = "1 + fn(){}"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddType { + argument_type: Type::function(FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::None + }), + position: Span(4, 10) + }, + source, + }) + ); +} + +#[test] +fn add_list_left() { + let source = "[1, 2] + 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddType { + argument_type: Type::List(Box::new(Type::Integer)), + position: Span(0, 6) + }, + source, + }) + ); +} + +#[test] +fn add_list_right() { + let source = "1 + [1, 2]"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddType { + argument_type: Type::List(Box::new(Type::Integer)), + position: Span(4, 10) + }, + source, + }) + ); +} + +// #[test] +// fn add_range_left() { +// todo!("Add ranges") +// } + +// #[test] +// fn add_range_right() { +// todo!("Add ranges") +// } +// + +#[test] +fn add_byte_and_character() { + let source = "0xff + 'a'"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddArguments { + left_type: Type::Byte, + right_type: Type::Character, + position: Span(0, 10) + }, + source, + }) + ); +} + +#[test] +fn add_byte_and_integer() { + let source = "0xff + 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddArguments { + left_type: Type::Byte, + right_type: Type::Integer, + position: Span(0, 8) + }, + source, + }) + ); +} + +#[test] +fn add_byte_and_string() { + let source = "0xff + \"hello\""; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddArguments { + left_type: Type::Byte, + right_type: Type::String, + position: Span(0, 14) + }, + source, + }) + ); +} + +#[test] +fn add_character_and_byte() { + let source = "'a' + 0xff"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddArguments { + left_type: Type::Character, + right_type: Type::Byte, + position: Span(0, 10) + }, + source, + }) + ); +} + +#[test] +fn add_character_and_float() { + let source = "'a' + 1.0"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddArguments { + left_type: Type::Character, + right_type: Type::Float, + position: Span(0, 9) + }, + source, + }) + ); +} + +#[test] +fn add_character_and_integer() { + let source = "'a' + 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddArguments { + left_type: Type::Character, + right_type: Type::Integer, + position: Span(0, 7) + }, + source, + }) + ); +} + +#[test] +fn add_float_and_byte() { + let source = "1.0 + 0xff"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddArguments { + left_type: Type::Float, + right_type: Type::Byte, + position: Span(0, 10) + }, + source, + }) + ); +} + +#[test] +fn add_float_and_character() { + let source = "1.0 + 'a'"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddArguments { + left_type: Type::Float, + right_type: Type::Character, + position: Span(0, 9) + }, + source, + }) + ); +} + +#[test] +fn add_float_and_integer() { + let source = "1.0 + 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddArguments { + left_type: Type::Float, + right_type: Type::Integer, + position: Span(0, 7) + }, + source, + }) + ); +} + +#[test] +fn add_float_and_string() { + let source = "1.0 + \"hello\""; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddArguments { + left_type: Type::Float, + right_type: Type::String, + position: Span(0, 13) + }, + source, + }) + ); +} + +#[test] +fn add_integer_and_byte() { + let source = "1 + 0xff"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddArguments { + left_type: Type::Integer, + right_type: Type::Byte, + position: Span(0, 8) + }, + source, + }) + ); +} + +#[test] +fn add_integer_and_character() { + let source = "1 + 'a'"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddArguments { + left_type: Type::Integer, + right_type: Type::Character, + position: Span(0, 7) + }, + source, + }) + ); +} + +#[test] +fn add_integer_and_float() { + let source = "1 + 1.0"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddArguments { + left_type: Type::Integer, + right_type: Type::Float, + position: Span(0, 7) + }, + source, + }) + ); +} + +#[test] +fn add_integer_and_string() { + let source = "1 + \"hello\""; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddArguments { + left_type: Type::Integer, + right_type: Type::String, + position: Span(0, 11) + }, + source, + }) + ); +} + +#[test] +fn add_string_and_byte() { + let source = "\"hello\" + 0xff"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddArguments { + left_type: Type::String, + right_type: Type::Byte, + position: Span(0, 14) + }, + source, + }) + ); +} + +#[test] +fn add_string_and_float() { + let source = "\"hello\" + 1.0"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddArguments { + left_type: Type::String, + right_type: Type::Float, + position: Span(0, 13) + }, + source, + }) + ); +} + +#[test] +fn add_string_and_integer() { + let source = "\"hello\" + 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotAddArguments { + left_type: Type::String, + right_type: Type::Integer, + position: Span(0, 11) + }, + source, + }) + ); +} diff --git a/dust-lang/tests/math/divide.rs b/dust-lang/tests/math/divide.rs new file mode 100644 index 0000000..d81dfb8 --- /dev/null +++ b/dust-lang/tests/math/divide.rs @@ -0,0 +1,85 @@ +use dust_lang::*; + +#[test] +fn divide_bytes() { + let source = "0xff / 0x01"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Byte, + }, + vec![ + ( + Instruction::divide(0, Argument::Constant(0), Argument::Constant(1)), + Span(5, 6) + ), + (Instruction::r#return(true), Span(11, 11)) + ], + vec![ConcreteValue::Byte(0xff), ConcreteValue::Byte(0x01)], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(ConcreteValue::Byte(0xff)))); +} + +#[test] +fn divide_floats() { + let source = "2.0 / 2.0"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Float, + }, + vec![ + ( + Instruction::divide(0, Argument::Constant(0), Argument::Constant(0)), + Span(4, 5) + ), + (Instruction::r#return(true), Span(9, 9)) + ], + vec![ConcreteValue::Float(2.0)], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(ConcreteValue::Float(1.0)))); +} + +#[test] +fn divide_integers() { + let source = "2 / 2"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Integer, + }, + vec![ + ( + Instruction::divide(0, Argument::Constant(0), Argument::Constant(0)), + Span(2, 3) + ), + (Instruction::r#return(true), Span(5, 5)) + ], + vec![ConcreteValue::Integer(2)], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(1)))); +} diff --git a/dust-lang/tests/math/divide_assign.rs b/dust-lang/tests/math/divide_assign.rs new file mode 100644 index 0000000..943feee --- /dev/null +++ b/dust-lang/tests/math/divide_assign.rs @@ -0,0 +1,31 @@ +use dust_lang::*; + +#[test] +fn divide_assign() { + let source = "let mut a = 2; a /= 2; a"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Integer, + }, + vec![ + (Instruction::load_constant(0, 0, false), Span(12, 13)), + ( + Instruction::divide(0, Argument::Register(0), Argument::Constant(0)), + Span(17, 19) + ), + (Instruction::get_local(1, 0), Span(23, 24)), + (Instruction::r#return(true), Span(24, 24)) + ], + vec![ConcreteValue::Integer(2), ConcreteValue::string("a")], + vec![Local::new(1, 0, true, Scope::default())] + )) + ); + + assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(1)))); +} diff --git a/dust-lang/tests/math/divide_errors.rs b/dust-lang/tests/math/divide_errors.rs new file mode 100644 index 0000000..a3118cc --- /dev/null +++ b/dust-lang/tests/math/divide_errors.rs @@ -0,0 +1,229 @@ +use dust_lang::*; + +#[test] +fn divide_boolean_left() { + let source = "true / 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotDivideType { + argument_type: Type::Boolean, + position: Span(0, 4) + }, + source, + }) + ); +} + +#[test] +fn divide_boolean_right() { + let source = "1 / true"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotDivideType { + argument_type: Type::Boolean, + position: Span(4, 8) + }, + source, + }) + ); +} + +#[test] +fn divide_character_left() { + let source = "'a' / 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotDivideType { + argument_type: Type::Character, + position: Span(0, 3) + }, + source, + }) + ); +} + +#[test] +fn divide_character_right() { + let source = "1 / 'a'"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotDivideType { + argument_type: Type::Character, + position: Span(4, 7) + }, + source, + }) + ); +} + +#[test] +fn divide_float_and_character() { + let source = "1.0 / 'a'"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotDivideType { + argument_type: Type::Character, + position: Span(6, 9) + }, + source, + }) + ); +} + +#[test] +fn divide_float_and_integer() { + let source = "1.0 / 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotDivideArguments { + left_type: Type::Float, + right_type: Type::Integer, + position: Span(0, 7) + }, + source, + }) + ); +} + +#[test] +fn divide_function_left() { + let source = "fn(){} / 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotDivideType { + argument_type: Type::function(FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::None + }), + position: Span(0, 6) + }, + source, + }) + ); +} + +#[test] +fn divide_function_right() { + let source = "1 / fn(){}"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotDivideType { + argument_type: Type::function(FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::None + }), + position: Span(4, 10) + }, + source, + }) + ); +} + +#[test] +fn divide_integer_and_float() { + let source = "1 / 1.0"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotDivideArguments { + left_type: Type::Integer, + right_type: Type::Float, + position: Span(0, 7) + }, + source, + }) + ); +} + +#[test] +fn divide_list_left() { + let source = "[1, 2] / 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotDivideType { + argument_type: Type::List(Box::new(Type::Integer)), + position: Span(0, 6) + }, + source, + }) + ); +} + +#[test] +fn divide_list_right() { + let source = "1 / [1, 2]"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotDivideType { + argument_type: Type::List(Box::new(Type::Integer)), + position: Span(4, 10) + }, + source, + }) + ); +} + +// #[test] +// fn add_range_left() { +// todo!("Add ranges") +// } + +// #[test] +// fn add_range_right() { +// todo!("Add ranges") +// } + +#[test] +fn divide_string_left() { + let source = "\"hello\" / 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotDivideType { + argument_type: Type::String, + position: Span(0, 7) + }, + source, + }) + ); +} + +#[test] +fn divide_string_right() { + let source = "1 / \"hello\""; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotDivideType { + argument_type: Type::String, + position: Span(4, 11) + }, + source, + }) + ); +} diff --git a/dust-lang/tests/math/modulo.rs b/dust-lang/tests/math/modulo.rs new file mode 100644 index 0000000..b1ee83e --- /dev/null +++ b/dust-lang/tests/math/modulo.rs @@ -0,0 +1,57 @@ +use dust_lang::*; + +#[test] +fn modulo_floats() { + let source = "2.0 % 2.0"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Float, + }, + vec![ + ( + Instruction::modulo(0, Argument::Constant(0), Argument::Constant(0)), + Span(4, 5) + ), + (Instruction::r#return(true), Span(9, 9)) + ], + vec![ConcreteValue::Float(2.0)], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(ConcreteValue::Float(0.0)))); +} + +#[test] +fn modulo_integers() { + let source = "2 % 2"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Integer, + }, + vec![ + ( + Instruction::modulo(0, Argument::Constant(0), Argument::Constant(0)), + Span(2, 3) + ), + (Instruction::r#return(true), Span(5, 5)) + ], + vec![ConcreteValue::Integer(2)], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(0)))); +} diff --git a/dust-lang/tests/math/modulo_assign.rs b/dust-lang/tests/math/modulo_assign.rs new file mode 100644 index 0000000..e69de29 diff --git a/dust-lang/tests/math/modulo_errors.rs b/dust-lang/tests/math/modulo_errors.rs new file mode 100644 index 0000000..d283ec4 --- /dev/null +++ b/dust-lang/tests/math/modulo_errors.rs @@ -0,0 +1,229 @@ +use dust_lang::*; + +#[test] +fn modulo_boolean_left() { + let source = "true % 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotModuloType { + argument_type: Type::Boolean, + position: Span(0, 4) + }, + source, + }) + ); +} + +#[test] +fn modulo_boolean_right() { + let source = "1 % true"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotModuloType { + argument_type: Type::Boolean, + position: Span(4, 8) + }, + source, + }) + ); +} + +#[test] +fn modulo_character_left() { + let source = "'a' % 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotModuloType { + argument_type: Type::Character, + position: Span(0, 3) + }, + source, + }) + ); +} + +#[test] +fn modulo_character_right() { + let source = "1 % 'a'"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotModuloType { + argument_type: Type::Character, + position: Span(4, 7) + }, + source, + }) + ); +} + +#[test] +fn modulo_float_and_character() { + let source = "1.0 % 'a'"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotModuloType { + argument_type: Type::Character, + position: Span(6, 9) + }, + source, + }) + ); +} + +#[test] +fn modulo_float_and_integer() { + let source = "1.0 % 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotModuloArguments { + left_type: Type::Float, + right_type: Type::Integer, + position: Span(0, 7) + }, + source, + }) + ); +} + +#[test] +fn modulo_function_left() { + let source = "fn(){} % 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotModuloType { + argument_type: Type::function(FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::None + }), + position: Span(0, 6) + }, + source, + }) + ); +} + +#[test] +fn modulo_function_right() { + let source = "1 % fn(){}"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotModuloType { + argument_type: Type::function(FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::None + }), + position: Span(4, 10) + }, + source, + }) + ); +} + +#[test] +fn modulo_integer_and_float() { + let source = "1 % 1.0"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotModuloArguments { + left_type: Type::Integer, + right_type: Type::Float, + position: Span(0, 7) + }, + source, + }) + ); +} + +#[test] +fn modulo_list_left() { + let source = "[1, 2] % 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotModuloType { + argument_type: Type::List(Box::new(Type::Integer)), + position: Span(0, 6) + }, + source, + }) + ); +} + +#[test] +fn modulo_list_right() { + let source = "1 % [1, 2]"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotModuloType { + argument_type: Type::List(Box::new(Type::Integer)), + position: Span(4, 10) + }, + source, + }) + ); +} + +// #[test] +// fn add_range_left() { +// todo!("Add ranges") +// } + +// #[test] +// fn add_range_right() { +// todo!("Add ranges") +// } + +#[test] +fn modulo_string_left() { + let source = "\"hello\" % 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotModuloType { + argument_type: Type::String, + position: Span(0, 7) + }, + source, + }) + ); +} + +#[test] +fn modulo_string_right() { + let source = "1 % \"hello\""; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotModuloType { + argument_type: Type::String, + position: Span(4, 11) + }, + source, + }) + ); +} diff --git a/dust-lang/tests/math/multiply.rs b/dust-lang/tests/math/multiply.rs new file mode 100644 index 0000000..a3241c0 --- /dev/null +++ b/dust-lang/tests/math/multiply.rs @@ -0,0 +1,57 @@ +use dust_lang::*; + +#[test] +fn multiply_floats() { + let source = "2.0 * 2.0"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Float + }, + vec![ + ( + Instruction::multiply(0, Argument::Constant(0), Argument::Constant(0)), + Span(4, 5) + ), + (Instruction::r#return(true), Span(9, 9)), + ], + vec![ConcreteValue::Float(2.0)], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(ConcreteValue::Float(4.0)))); +} + +#[test] +fn multiply_integers() { + let source = "1 * 2"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Integer, + }, + vec![ + ( + Instruction::multiply(0, Argument::Constant(0), Argument::Constant(1)), + Span(2, 3) + ), + (Instruction::r#return(true), Span(5, 5)), + ], + vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(2)))); +} diff --git a/dust-lang/tests/math/multiply_assign.rs b/dust-lang/tests/math/multiply_assign.rs new file mode 100644 index 0000000..ec6f87d --- /dev/null +++ b/dust-lang/tests/math/multiply_assign.rs @@ -0,0 +1,35 @@ +use dust_lang::*; + +#[test] +fn multiply_assign() { + let source = "let mut a = 2; a *= 3 a"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Integer, + }, + vec![ + (Instruction::load_constant(0, 0, false), Span(12, 13)), + ( + Instruction::multiply(0, Argument::Register(0), Argument::Constant(2)), + Span(17, 19) + ), + (Instruction::get_local(1, 0), Span(22, 23)), + (Instruction::r#return(true), Span(23, 23)) + ], + vec![ + ConcreteValue::Integer(2), + ConcreteValue::string("a"), + ConcreteValue::Integer(3) + ], + vec![Local::new(1, 0, true, Scope::default())] + )) + ); + + assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(6)))); +} diff --git a/dust-lang/tests/math/multiply_errors.rs b/dust-lang/tests/math/multiply_errors.rs new file mode 100644 index 0000000..5c03033 --- /dev/null +++ b/dust-lang/tests/math/multiply_errors.rs @@ -0,0 +1,229 @@ +use dust_lang::*; + +#[test] +fn multiply_boolean_left() { + let source = "true * 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotMultiplyType { + argument_type: Type::Boolean, + position: Span(0, 4) + }, + source, + }) + ); +} + +#[test] +fn multiply_boolean_right() { + let source = "1 * true"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotMultiplyType { + argument_type: Type::Boolean, + position: Span(4, 8) + }, + source, + }) + ); +} + +#[test] +fn multiply_character_left() { + let source = "'a' * 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotMultiplyType { + argument_type: Type::Character, + position: Span(0, 3) + }, + source, + }) + ); +} + +#[test] +fn multiply_character_right() { + let source = "1 * 'a'"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotMultiplyType { + argument_type: Type::Character, + position: Span(4, 7) + }, + source, + }) + ); +} + +#[test] +fn multiply_float_and_character() { + let source = "1.0 * 'a'"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotMultiplyType { + argument_type: Type::Character, + position: Span(6, 9) + }, + source, + }) + ); +} + +#[test] +fn multiply_float_and_integer() { + let source = "1.0 * 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotMultiplyArguments { + left_type: Type::Float, + right_type: Type::Integer, + position: Span(0, 7) + }, + source, + }) + ); +} + +#[test] +fn multiply_function_left() { + let source = "fn(){} * 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotMultiplyType { + argument_type: Type::function(FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::None + }), + position: Span(0, 6) + }, + source, + }) + ); +} + +#[test] +fn multiply_function_right() { + let source = "1 * fn(){}"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotMultiplyType { + argument_type: Type::function(FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::None + }), + position: Span(4, 10) + }, + source, + }) + ); +} + +#[test] +fn multiply_integer_and_float() { + let source = "1 * 1.0"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotMultiplyArguments { + left_type: Type::Integer, + right_type: Type::Float, + position: Span(0, 7) + }, + source, + }) + ); +} + +#[test] +fn multiply_list_left() { + let source = "[1, 2] * 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotMultiplyType { + argument_type: Type::List(Box::new(Type::Integer)), + position: Span(0, 6) + }, + source, + }) + ); +} + +#[test] +fn multiply_list_right() { + let source = "1 * [1, 2]"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotMultiplyType { + argument_type: Type::List(Box::new(Type::Integer)), + position: Span(4, 10) + }, + source, + }) + ); +} + +// #[test] +// fn add_range_left() { +// todo!("Add ranges") +// } + +// #[test] +// fn add_range_right() { +// todo!("Add ranges") +// } + +#[test] +fn multiply_string_left() { + let source = "\"hello\" * 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotMultiplyType { + argument_type: Type::String, + position: Span(0, 7) + }, + source, + }) + ); +} + +#[test] +fn multiply_string_right() { + let source = "1 * \"hello\""; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotMultiplyType { + argument_type: Type::String, + position: Span(4, 11) + }, + source, + }) + ); +} diff --git a/dust-lang/tests/math/subtract.rs b/dust-lang/tests/math/subtract.rs new file mode 100644 index 0000000..2fc5b84 --- /dev/null +++ b/dust-lang/tests/math/subtract.rs @@ -0,0 +1,116 @@ +use dust_lang::*; + +#[test] +fn subtract_floats() { + let source = "2.0 - 2.0"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Float, + }, + vec![ + ( + Instruction::subtract(0, Argument::Constant(0), Argument::Constant(0)), + Span(4, 5) + ), + (Instruction::r#return(true), Span(9, 9)), + ], + vec![ConcreteValue::Float(2.0)], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(ConcreteValue::Float(0.0)))); +} + +#[test] +fn subtract_floats_saturate() { + let source = "-1.7976931348623157E+308 - 0.0000001"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Float, + }, + vec![ + ( + Instruction::subtract(0, Argument::Constant(0), Argument::Constant(1)), + Span(25, 26) + ), + (Instruction::r#return(true), Span(36, 36)), + ], + vec![ + ConcreteValue::Float(f64::MIN), + ConcreteValue::Float(0.0000001), + ], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(ConcreteValue::Float(f64::MIN)))); +} + +#[test] +fn subtract_integers() { + let source = "1 - 2"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Integer, + }, + vec![ + ( + Instruction::subtract(0, Argument::Constant(0), Argument::Constant(1)), + Span(2, 3) + ), + (Instruction::r#return(true), Span(5, 5)), + ], + vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(-1)))); +} + +#[test] +fn subtract_integers_saturate() { + let source = "-9223372036854775808 - 1"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Integer, + }, + vec![ + ( + Instruction::subtract(0, Argument::Constant(0), Argument::Constant(1)), + Span(21, 22) + ), + (Instruction::r#return(true), Span(24, 24)), + ], + vec![ConcreteValue::Integer(i64::MIN), ConcreteValue::Integer(1),], + vec![] + )) + ); + + assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(i64::MIN)))); +} diff --git a/dust-lang/tests/math/subtract_assign.rs b/dust-lang/tests/math/subtract_assign.rs new file mode 100644 index 0000000..733eded --- /dev/null +++ b/dust-lang/tests/math/subtract_assign.rs @@ -0,0 +1,35 @@ +use dust_lang::*; + +#[test] +fn subtract_assign() { + let source = "let mut x = 42; x -= 2; x"; + + assert_eq!( + compile(source), + Ok(Chunk::with_data( + None, + FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::Integer, + }, + vec![ + (Instruction::load_constant(0, 0, false), Span(12, 14)), + ( + Instruction::subtract(0, Argument::Register(0), Argument::Constant(2)), + Span(18, 20) + ), + (Instruction::get_local(1, 0), Span(24, 25)), + (Instruction::r#return(true), Span(25, 25)), + ], + vec![ + ConcreteValue::Integer(42), + ConcreteValue::string("x"), + ConcreteValue::Integer(2) + ], + vec![Local::new(1, 0, true, Scope::default())] + )), + ); + + assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(40)))); +} diff --git a/dust-lang/tests/math/subtract_errors.rs b/dust-lang/tests/math/subtract_errors.rs new file mode 100644 index 0000000..1d73bde --- /dev/null +++ b/dust-lang/tests/math/subtract_errors.rs @@ -0,0 +1,229 @@ +use dust_lang::*; + +#[test] +fn subtract_boolean_left() { + let source = "true - 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotSubtractType { + argument_type: Type::Boolean, + position: Span(0, 4) + }, + source, + }) + ); +} + +#[test] +fn subtract_boolean_right() { + let source = "1 - true"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotSubtractType { + argument_type: Type::Boolean, + position: Span(4, 8) + }, + source, + }) + ); +} + +#[test] +fn subtract_character_left() { + let source = "'a' - 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotSubtractType { + argument_type: Type::Character, + position: Span(0, 3) + }, + source, + }) + ); +} + +#[test] +fn subtract_character_right() { + let source = "1 - 'a'"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotSubtractType { + argument_type: Type::Character, + position: Span(4, 7) + }, + source, + }) + ); +} + +#[test] +fn subtract_float_and_character() { + let source = "1.0 - 'a'"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotSubtractType { + argument_type: Type::Character, + position: Span(6, 9) + }, + source, + }) + ); +} + +#[test] +fn subtract_float_and_integer() { + let source = "1.0 - 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotSubtractArguments { + left_type: Type::Float, + right_type: Type::Integer, + position: Span(0, 7) + }, + source, + }) + ); +} + +#[test] +fn subtract_function_left() { + let source = "fn(){} - 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotSubtractType { + argument_type: Type::function(FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::None + }), + position: Span(0, 6) + }, + source, + }) + ); +} + +#[test] +fn subtract_function_right() { + let source = "1 - fn(){}"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotSubtractType { + argument_type: Type::function(FunctionType { + type_parameters: None, + value_parameters: None, + return_type: Type::None + }), + position: Span(4, 10) + }, + source, + }) + ); +} + +#[test] +fn subtract_integer_and_float() { + let source = "1 - 1.0"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotSubtractArguments { + left_type: Type::Integer, + right_type: Type::Float, + position: Span(0, 7) + }, + source, + }) + ); +} + +#[test] +fn subtract_list_left() { + let source = "[1, 2] - 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotSubtractType { + argument_type: Type::List(Box::new(Type::Integer)), + position: Span(0, 6) + }, + source, + }) + ); +} + +#[test] +fn subtract_list_right() { + let source = "1 - [1, 2]"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotSubtractType { + argument_type: Type::List(Box::new(Type::Integer)), + position: Span(4, 10) + }, + source, + }) + ); +} + +// #[test] +// fn add_range_left() { +// todo!("Add ranges") +// } + +// #[test] +// fn add_range_right() { +// todo!("Add ranges") +// } + +#[test] +fn subtract_string_left() { + let source = "\"hello\" - 1"; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotSubtractType { + argument_type: Type::String, + position: Span(0, 7) + }, + source, + }) + ); +} + +#[test] +fn subtract_string_right() { + let source = "1 - \"hello\""; + + assert_eq!( + compile(source), + Err(DustError::Compile { + error: CompileError::CannotSubtractType { + argument_type: Type::String, + position: Span(4, 11) + }, + source, + }) + ); +} diff --git a/dust-lang/tests/native_functions.rs b/dust-lang/tests/native_functions.rs index 4b83b21..e6d13db 100644 --- a/dust-lang/tests/native_functions.rs +++ b/dust-lang/tests/native_functions.rs @@ -11,25 +11,16 @@ fn panic() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::None), + return_type: Type::None, }, vec![ + (Instruction::load_constant(0, 0, false), Span(6, 22)), + (Instruction::load_constant(1, 1, false), Span(24, 26)), ( - Instruction::load_constant(Destination::Register(0), 0, false), - Type::String, - Span(6, 22) - ), - ( - Instruction::load_constant(Destination::Register(1), 1, false), - Type::Integer, - Span(24, 26) - ), - ( - Instruction::call_native(Destination::Register(2), NativeFunction::Panic, 2), - Type::None, + Instruction::call_native(2, NativeFunction::Panic, 2), Span(0, 27) ), - (Instruction::r#return(false), Type::None, Span(27, 27)) + (Instruction::r#return(false), Span(27, 27)) ], vec![ ConcreteValue::string("Goodbye world!"), @@ -62,20 +53,15 @@ fn to_string() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::String), + return_type: Type::String, }, vec![ + (Instruction::load_constant(0, 0, false), Span(10, 12)), ( - Instruction::load_constant(Destination::Register(0), 0, false), - Type::Integer, - Span(10, 12) - ), - ( - Instruction::call_native(Destination::Register(1), NativeFunction::ToString, 1), - Type::String, + Instruction::call_native(1, NativeFunction::ToString, 1), Span(0, 13) ), - (Instruction::r#return(true), Type::None, Span(13, 13)) + (Instruction::r#return(true), Span(13, 13)) ], vec![ConcreteValue::Integer(42)], vec![] diff --git a/dust-lang/tests/scopes.rs b/dust-lang/tests/scopes.rs index c34d33b..d9e14e3 100644 --- a/dust-lang/tests/scopes.rs +++ b/dust-lang/tests/scopes.rs @@ -33,60 +33,15 @@ fn block_scope() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::None), + return_type: Type::None, }, vec![ - ( - Instruction::load_constant(Destination::Register(0), 0, false), - Type::Integer, - Span(17, 18) - ), - ( - Instruction::define_local(0, 0, false), - Type::None, - Span(13, 14) - ), - ( - Instruction::load_constant(Destination::Register(1), 2, false), - Type::Integer, - Span(50, 52) - ), - ( - Instruction::define_local(1, 1, false), - Type::None, - Span(46, 47) - ), - ( - Instruction::load_constant(Destination::Register(2), 4, false), - Type::Integer, - Span(92, 93) - ), - ( - Instruction::define_local(2, 2, false), - Type::None, - Span(88, 89) - ), - ( - Instruction::load_constant(Destination::Register(3), 6, false), - Type::Integer, - Span(129, 130) - ), - ( - Instruction::define_local(3, 3, false), - Type::None, - Span(125, 126) - ), - ( - Instruction::load_constant(Destination::Register(4), 4, false), - Type::Integer, - Span(158, 159) - ), - ( - Instruction::define_local(4, 4, false), - Type::None, - Span(154, 155) - ), - (Instruction::r#return(false), Type::None, Span(165, 165)) + (Instruction::load_constant(0, 0, false), Span(17, 18)), + (Instruction::load_constant(1, 2, false), Span(50, 52)), + (Instruction::load_constant(2, 4, false), Span(92, 93)), + (Instruction::load_constant(3, 6, false), Span(129, 130)), + (Instruction::load_constant(4, 4, false), Span(158, 159)), + (Instruction::r#return(false), Span(165, 165)) ], vec![ ConcreteValue::Integer(0), @@ -100,11 +55,11 @@ fn block_scope() { ConcreteValue::string("e"), ], vec![ - Local::new(1, Type::Integer, false, Scope::new(0, 0)), - Local::new(3, Type::Integer, false, Scope::new(1, 1)), - Local::new(5, Type::Integer, false, Scope::new(2, 2)), - Local::new(7, Type::Integer, false, Scope::new(1, 1)), - Local::new(8, Type::Integer, false, Scope::new(0, 0)), + Local::new(1, 0, false, Scope::new(0, 0)), + Local::new(3, 2, false, Scope::new(1, 1)), + Local::new(5, 4, false, Scope::new(2, 2)), + Local::new(7, 6, false, Scope::new(1, 1)), + Local::new(8, 7, false, Scope::new(0, 0)), ] )), ); @@ -141,100 +96,19 @@ fn multiple_block_scopes() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::None), + return_type: Type::None, }, vec![ - ( - Instruction::load_constant(Destination::Register(0), 0, false), - Type::Integer, - Span(17, 18) - ), - ( - Instruction::define_local(0, 0, false), - Type::None, - Span(13, 14) - ), - ( - Instruction::load_constant(Destination::Register(1), 2, false), - Type::Integer, - Span(50, 52) - ), - ( - Instruction::define_local(1, 1, false), - Type::None, - Span(46, 47) - ), - ( - Instruction::load_constant(Destination::Register(2), 4, false), - Type::Integer, - Span(92, 93) - ), - ( - Instruction::define_local(2, 2, false), - Type::None, - Span(88, 89) - ), - ( - Instruction::get_local(Destination::Register(3), 1), - Type::Integer, - Span(129, 130) - ), - ( - Instruction::define_local(3, 3, false), - Type::None, - Span(125, 126) - ), - ( - Instruction::get_local(Destination::Register(4), 0), - Type::Integer, - Span(158, 159) - ), - ( - Instruction::define_local(4, 4, false), - Type::None, - Span(154, 155) - ), - ( - Instruction::load_constant(Destination::Register(5), 2, false), - Type::Integer, - Span(191, 193) - ), - ( - Instruction::define_local(5, 5, false), - Type::None, - Span(187, 188) - ), - ( - Instruction::load_constant(Destination::Register(6), 4, false), - Type::Integer, - Span(233, 234) - ), - ( - Instruction::define_local(6, 6, false), - Type::None, - Span(229, 230) - ), - ( - Instruction::get_local(Destination::Register(7), 5), - Type::Integer, - Span(270, 271) - ), - ( - Instruction::define_local(7, 7, false), - Type::None, - Span(266, 267) - ), - ( - Instruction::get_local(Destination::Register(8), 0), - Type::Integer, - Span(299, 300) - ), - ( - Instruction::define_local(8, 8, false), - Type::None, - Span(295, 296) - ), - (Instruction::r#return(false), Type::None, Span(306, 306)) + (Instruction::load_constant(0, 0, false), Span(17, 18)), + (Instruction::load_constant(1, 2, false), Span(50, 52)), + (Instruction::load_constant(2, 4, false), Span(92, 93)), + (Instruction::get_local(3, 1), Span(129, 130)), + (Instruction::get_local(4, 0), Span(158, 159)), + (Instruction::load_constant(5, 2, false), Span(191, 193)), + (Instruction::load_constant(4, 4, false), Span(233, 234)), + (Instruction::get_local(7, 5), Span(270, 271)), + (Instruction::get_local(8, 0), Span(299, 300)), + (Instruction::r#return(false), Span(306, 306)) ], vec![ ConcreteValue::Integer(0), @@ -248,15 +122,15 @@ fn multiple_block_scopes() { ConcreteValue::string("e"), ], vec![ - Local::new(1, Type::Integer, false, Scope::new(0, 0)), - Local::new(3, Type::Integer, false, Scope::new(1, 1)), - Local::new(5, Type::Integer, false, Scope::new(2, 2)), - Local::new(6, Type::Integer, false, Scope::new(1, 1)), - Local::new(7, Type::Integer, false, Scope::new(0, 0)), - Local::new(3, Type::Integer, false, Scope::new(1, 3)), - Local::new(5, Type::Integer, false, Scope::new(2, 4)), - Local::new(6, Type::Integer, false, Scope::new(1, 3)), - Local::new(8, Type::Integer, false, Scope::new(0, 0)), + Local::new(1, 0, false, Scope::new(0, 0)), + Local::new(3, 2, false, Scope::new(1, 1)), + Local::new(5, 4, false, Scope::new(2, 2)), + Local::new(6, 5, false, Scope::new(1, 1)), + Local::new(7, 6, false, Scope::new(0, 0)), + Local::new(3, 1, false, Scope::new(1, 3)), + Local::new(5, 1, false, Scope::new(2, 4)), + Local::new(6, 1, false, Scope::new(1, 3)), + Local::new(8, 1, false, Scope::new(0, 0)), ] )), ); diff --git a/dust-lang/tests/unary_operations.rs b/dust-lang/tests/unary_operations.rs index 8e289c5..c04486e 100644 --- a/dust-lang/tests/unary_operations.rs +++ b/dust-lang/tests/unary_operations.rs @@ -11,15 +11,11 @@ fn negate() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::Integer), + return_type: Type::Integer, }, vec![ - ( - Instruction::negate(Destination::Register(0), Argument::Constant(0)), - Type::Integer, - Span(0, 1) - ), - (Instruction::r#return(true), Type::None, Span(5, 5)), + (Instruction::negate(0, Argument::Constant(0)), Span(0, 1)), + (Instruction::r#return(true), Span(5, 5)), ], vec![ConcreteValue::Integer(42)], vec![] @@ -40,20 +36,12 @@ fn not() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::Boolean), + return_type: Type::Boolean, }, vec![ - ( - Instruction::load_boolean(Destination::Register(0), true, false), - Type::Boolean, - Span(1, 5) - ), - ( - Instruction::not(Destination::Register(1), Argument::Register(0)), - Type::Boolean, - Span(0, 1) - ), - (Instruction::r#return(true), Type::None, Span(5, 5)), + (Instruction::load_boolean(0, true, false), Span(1, 5)), + (Instruction::not(1, Argument::Register(0)), Span(0, 1)), + (Instruction::r#return(true), Span(5, 5)), ], vec![], vec![] diff --git a/dust-lang/tests/variables.rs b/dust-lang/tests/variables.rs index bdd7254..b1f67d0 100644 --- a/dust-lang/tests/variables.rs +++ b/dust-lang/tests/variables.rs @@ -11,23 +11,14 @@ fn define_local() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::None), + return_type: Type::None, }, vec![ - ( - Instruction::load_constant(Destination::Register(0), 0, false), - Type::Integer, - Span(8, 10) - ), - ( - Instruction::define_local(0, 0, false), - Type::None, - Span(4, 5) - ), - (Instruction::r#return(false), Type::None, Span(11, 11)) + (Instruction::load_constant(0, 0, false), Span(8, 10)), + (Instruction::r#return(false), Span(11, 11)) ], vec![ConcreteValue::Integer(42), ConcreteValue::string("x")], - vec![Local::new(1, Type::Integer, false, Scope::default())] + vec![Local::new(1, 0, false, Scope::default())] )), ); @@ -62,38 +53,21 @@ fn set_local() { FunctionType { type_parameters: None, value_parameters: None, - return_type: Box::new(Type::Integer), + return_type: Type::Integer, }, vec![ - ( - Instruction::load_constant(Destination::Register(0), 0, false), - Type::Integer, - Span(12, 14) - ), - ( - Instruction::define_local(0, 0, true), - Type::None, - Span(8, 9) - ), - ( - Instruction::load_constant(Destination::Register(1), 2, false), - Type::Integer, - Span(20, 22) - ), - (Instruction::set_local(1, 0), Type::None, Span(16, 17)), - ( - Instruction::get_local(Destination::Register(2), 0), - Type::Integer, - Span(24, 25) - ), - (Instruction::r#return(true), Type::None, Span(25, 25)), + (Instruction::load_constant(0, 0, false), Span(12, 14)), + (Instruction::load_constant(1, 2, false), Span(20, 22)), + (Instruction::set_local(1, 0), Span(16, 17)), + (Instruction::get_local(2, 0), Span(24, 25)), + (Instruction::r#return(true), Span(25, 25)), ], vec![ ConcreteValue::Integer(41), ConcreteValue::string("x"), ConcreteValue::Integer(42) ], - vec![Local::new(1, Type::Integer, true, Scope::default())] + vec![Local::new(1, 0, true, Scope::default())] )), ); diff --git a/examples/assets/count.js b/examples/assets/count.js deleted file mode 100644 index ba97d27..0000000 --- a/examples/assets/count.js +++ /dev/null @@ -1,5 +0,0 @@ -var i = 0; - -while (i < 10000) { - i++; -} diff --git a/examples/async_count.ds b/examples/async_count.ds deleted file mode 100644 index 8b6038f..0000000 --- a/examples/async_count.ds +++ /dev/null @@ -1,21 +0,0 @@ -count_slowly = fn ( - multiplier: int, -) { - i = 0 - - while i < 10 { - sleep_time = i * multiplier; - - thread.sleep(sleep_time) - thread.write_line(i as str) - - i += 1 - } -} - -async { - count_slowly(50) - count_slowly(100) - count_slowly(200) - count_slowly(250) -} diff --git a/examples/count.ds b/examples/count.ds deleted file mode 100644 index 82ff9ac..0000000 --- a/examples/count.ds +++ /dev/null @@ -1,5 +0,0 @@ -let mut i = 0; - -while i < 10000 { - i += 1; -} diff --git a/examples/guessing_game.ds b/examples/guessing_game.ds index ae31ed1..d3a4378 100644 --- a/examples/guessing_game.ds +++ b/examples/guessing_game.ds @@ -1,12 +1,12 @@ write_line("Guess the number.") -let secret_number = random(0..100); +let secret_number = random(0..100) loop { write_line("Input your guess.") - let input = io.read_line(); - let guess = int.parse(input); + let input = io.read_line() + let guess = int.parse(input) if guess < secret_number { io.write_line("Too low!") diff --git a/examples/json_length.ds b/examples/json_length.ds deleted file mode 100644 index 81a32fc..0000000 --- a/examples/json_length.ds +++ /dev/null @@ -1,4 +0,0 @@ -input = fs.read_file('examples/assets/data.json') -data = json.parse(input) - -length(data) diff --git a/examples/type_inference.ds b/examples/type_inference.ds deleted file mode 100644 index db30012..0000000 --- a/examples/type_inference.ds +++ /dev/null @@ -1,20 +0,0 @@ -// This function returns its argument. -foo = fn (x: T) -> T { x } - -// Use turbofish to supply type information. -bar = foo::("hi") - -// Use type annotation -baz: str = foo("hi") - -// The `json.parse` function takes a string and returns the specified type - -// Use turbofish -x = json.parse::("1") - -// Use type annotation -x: int = json.parse("1") - -x: int = { - json.parse("1") -}