Unified the C# mod and the Rust compiler into a monorepo
This commit is contained in:
884
rust_compiler/Cargo.lock
generated
Normal file
884
rust_compiler/Cargo.lock
generated
Normal file
@@ -0,0 +1,884 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium",
|
||||
"tap",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "borsh"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f"
|
||||
dependencies = [
|
||||
"borsh-derive",
|
||||
"cfg_aliases",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "borsh-derive"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
|
||||
[[package]]
|
||||
name = "bytecheck"
|
||||
version = "0.6.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2"
|
||||
dependencies = [
|
||||
"bytecheck_derive",
|
||||
"ptr_meta",
|
||||
"simdutf8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytecheck_derive"
|
||||
version = "0.6.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "compiler"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indoc",
|
||||
"parser",
|
||||
"pretty_assertions",
|
||||
"quick-error",
|
||||
"tokenizer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.32.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.16.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.177"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.37.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "parser"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"quick-error",
|
||||
"tokenizer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
|
||||
dependencies = [
|
||||
"diff",
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ptr_meta"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1"
|
||||
dependencies = [
|
||||
"ptr_meta_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ptr_meta_derive"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rend"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c"
|
||||
dependencies = [
|
||||
"bytecheck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rkyv"
|
||||
version = "0.7.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"bytecheck",
|
||||
"bytes",
|
||||
"hashbrown 0.12.3",
|
||||
"ptr_meta",
|
||||
"rend",
|
||||
"rkyv_derive",
|
||||
"seahash",
|
||||
"tinyvec",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rkyv_derive"
|
||||
version = "0.7.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust_decimal"
|
||||
version = "1.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35affe401787a9bd846712274d97654355d21b2a2c092a3139aabe31e9022282"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"borsh",
|
||||
"bytes",
|
||||
"num-traits",
|
||||
"rand",
|
||||
"rkyv",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "seahash"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simdutf8"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||
|
||||
[[package]]
|
||||
name = "stationlang"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"compiler",
|
||||
"parser",
|
||||
"quick-error",
|
||||
"rust_decimal",
|
||||
"tokenizer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.111"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokenizer"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"quick-error",
|
||||
"rust_decimal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.23.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
|
||||
dependencies = [
|
||||
"tap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
]
|
||||
35
rust_compiler/Cargo.toml
Normal file
35
rust_compiler/Cargo.toml
Normal file
@@ -0,0 +1,35 @@
|
||||
[package]
|
||||
name = "stationlang"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[workspace]
|
||||
members = ["libs/*"]
|
||||
|
||||
[workspace.dependencies]
|
||||
quick-error = "2"
|
||||
rust_decimal = "1"
|
||||
|
||||
[profile.release]
|
||||
strip = true
|
||||
|
||||
[[bin]]
|
||||
name = "slang"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lib]
|
||||
name = "slang"
|
||||
path = "src/lib.rs"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "^4.5", features = ["derive"] }
|
||||
quick-error = { workspace = true }
|
||||
rust_decimal = { workspace = true }
|
||||
tokenizer = { path = "libs/tokenizer" }
|
||||
parser = { path = "libs/parser" }
|
||||
compiler = { path = "libs/compiler" }
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { version = "^1.0", features = ["backtrace"] }
|
||||
14
rust_compiler/libs/compiler/Cargo.toml
Normal file
14
rust_compiler/libs/compiler/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "compiler"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
quick-error = { workspace = true }
|
||||
parser = { path = "../parser" }
|
||||
tokenizer = { path = "../tokenizer" }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { version = "1.0" }
|
||||
indoc = { version = "2.0" }
|
||||
pretty_assertions = "1"
|
||||
6
rust_compiler/libs/compiler/src/lib.rs
Normal file
6
rust_compiler/libs/compiler/src/lib.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
mod v1;
|
||||
mod variable_manager;
|
||||
|
||||
pub use v1::{Compiler, CompilerConfig, Error};
|
||||
100
rust_compiler/libs/compiler/src/test/binary_expression.rs
Normal file
100
rust_compiler/libs/compiler/src/test/binary_expression.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
use crate::compile;
|
||||
use indoc::indoc;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn simple_binary_expression() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let i = 1 + 2;
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
add r1 1 2
|
||||
move r8 r1 #i
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_binary_expressions() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
fn calculateArgs(arg1, arg2, arg3) {
|
||||
return (arg1 + arg2) * arg3;
|
||||
};
|
||||
|
||||
let returned = calculateArgs(10, 20, 30) + 100;
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
calculateArgs:
|
||||
pop r8 #arg3
|
||||
pop r9 #arg2
|
||||
pop r10 #arg1
|
||||
push ra
|
||||
add r1 r10 r9
|
||||
mul r2 r1 r8
|
||||
move r15 r2
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
j ra
|
||||
main:
|
||||
push 10
|
||||
push 20
|
||||
push 30
|
||||
jal calculateArgs
|
||||
move r1 r15 #__binary_temp_3
|
||||
add r2 r1 100
|
||||
move r8 r2 #returned
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stress_test_negation_with_stack_spillover() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let negationHell = (-1 + -2) * (-3 + (-4 * (-5 + -6)));
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
add r1 -1 -2
|
||||
add r2 -5 -6
|
||||
mul r3 -4 r2
|
||||
add r4 -3 r3
|
||||
mul r5 r1 r4
|
||||
move r8 r5 #negationHell
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
158
rust_compiler/libs/compiler/src/test/branching.rs
Normal file
158
rust_compiler/libs/compiler/src/test/branching.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
use crate::compile;
|
||||
use indoc::indoc;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_if_statement() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let a = 10;
|
||||
if (a > 5) {
|
||||
a = 20;
|
||||
}
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
move r8 10 #a
|
||||
sgt r1 r8 5
|
||||
beq r1 0 L1
|
||||
move r8 20 #a
|
||||
L1:
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_if_else_statement() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let a = 0;
|
||||
if (10 > 5) {
|
||||
a = 1;
|
||||
} else {
|
||||
a = 2;
|
||||
}
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
move r8 0 #a
|
||||
sgt r1 10 5
|
||||
beq r1 0 L2
|
||||
move r8 1 #a
|
||||
j L1
|
||||
L2:
|
||||
move r8 2 #a
|
||||
L1:
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_if_else_if_statement() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let a = 0;
|
||||
if (a == 1) {
|
||||
a = 10;
|
||||
} else if (a == 2) {
|
||||
a = 20;
|
||||
} else {
|
||||
a = 30;
|
||||
}
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
move r8 0 #a
|
||||
seq r1 r8 1
|
||||
beq r1 0 L2
|
||||
move r8 10 #a
|
||||
j L1
|
||||
L2:
|
||||
seq r2 r8 2
|
||||
beq r2 0 L4
|
||||
move r8 20 #a
|
||||
j L3
|
||||
L4:
|
||||
move r8 30 #a
|
||||
L3:
|
||||
L1:
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_spilled_variable_update_in_branch() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let a = 1;
|
||||
let b = 2;
|
||||
let c = 3;
|
||||
let d = 4;
|
||||
let e = 5;
|
||||
let f = 6;
|
||||
let g = 7;
|
||||
let h = 8; // Spilled to stack (offset 0)
|
||||
|
||||
if (a == 1) {
|
||||
h = 99;
|
||||
}
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
move r8 1 #a
|
||||
move r9 2 #b
|
||||
move r10 3 #c
|
||||
move r11 4 #d
|
||||
move r12 5 #e
|
||||
move r13 6 #f
|
||||
move r14 7 #g
|
||||
push 8 #h
|
||||
seq r1 r8 1
|
||||
beq r1 0 L1
|
||||
sub r0 sp 1
|
||||
put db r0 99 #h
|
||||
L1:
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
use crate::compile;
|
||||
use indoc::indoc;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn no_arguments() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
fn doSomething() {};
|
||||
let i = doSomething();
|
||||
"
|
||||
};
|
||||
|
||||
let to_test = indoc! {
|
||||
"
|
||||
j main
|
||||
doSomething:
|
||||
push ra
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
j ra
|
||||
main:
|
||||
jal doSomething
|
||||
move r8 r15 #i
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(compiled, to_test);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_var_args() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
fn doSomething(arg1) {};
|
||||
let arg1 = 123;
|
||||
let i = doSomething(arg1);
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
doSomething:
|
||||
pop r8 #arg1
|
||||
push ra
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
j ra
|
||||
main:
|
||||
move r8 123 #arg1
|
||||
push r8
|
||||
push r8
|
||||
jal doSomething
|
||||
sub r0 sp 1
|
||||
get r8 db r0
|
||||
sub sp sp 1
|
||||
move r9 r15 #i
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn incorrect_args_count() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
result
|
||||
"
|
||||
fn doSomething(arg1, arg2){};
|
||||
let i = doSomething();
|
||||
"
|
||||
};
|
||||
|
||||
assert!(matches!(
|
||||
compiled,
|
||||
Err(super::super::Error::AgrumentMismatch(_))
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inline_literal_args() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
fn doSomething(arg1, arg2) {};
|
||||
let thisVariableShouldStayInPlace = 123;
|
||||
let returnedValue = doSomething(12, 34);
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
doSomething:
|
||||
pop r8 #arg2
|
||||
pop r9 #arg1
|
||||
push ra
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
j ra
|
||||
main:
|
||||
move r8 123 #thisVariableShouldStayInPlace
|
||||
push r8
|
||||
push 12
|
||||
push 34
|
||||
jal doSomething
|
||||
sub r0 sp 1
|
||||
get r8 db r0
|
||||
sub sp sp 1
|
||||
move r9 r15 #returnedValue
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_args() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let arg1 = 123;
|
||||
let returnValue = doSomething(arg1, 456);
|
||||
fn doSomething(arg1, arg2) {};
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
doSomething:
|
||||
pop r8 #arg2
|
||||
pop r9 #arg1
|
||||
push ra
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
j ra
|
||||
main:
|
||||
move r8 123 #arg1
|
||||
push r8
|
||||
push r8
|
||||
push 456
|
||||
jal doSomething
|
||||
sub r0 sp 1
|
||||
get r8 db r0
|
||||
sub sp sp 1
|
||||
move r9 r15 #returnValue
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_return_statement() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
fn doSomething(arg1) {
|
||||
return 456;
|
||||
};
|
||||
|
||||
let returned = doSomething(123);
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
doSomething:
|
||||
pop r8 #arg1
|
||||
push ra
|
||||
move r15 456 #returnValue
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
j ra
|
||||
main:
|
||||
push 123
|
||||
jal doSomething
|
||||
move r8 r15 #returned
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_negative_return_literal() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
fn doSomething() {
|
||||
return -1;
|
||||
};
|
||||
let i = doSomething();
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
doSomething:
|
||||
push ra
|
||||
move r15 -1 #returnValue
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
j ra
|
||||
main:
|
||||
jal doSomething
|
||||
move r8 r15 #i
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
147
rust_compiler/libs/compiler/src/test/declaration_literal.rs
Normal file
147
rust_compiler/libs/compiler/src/test/declaration_literal.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
use indoc::indoc;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn variable_declaration_numeric_literal() -> anyhow::Result<()> {
|
||||
let compiled = crate::compile! {
|
||||
debug r#"
|
||||
let i = 20c;
|
||||
"#
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
move r8 293.15 #i
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variable_declaration_numeric_literal_stack_spillover() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
r#"
|
||||
let a = 0;
|
||||
let b = 1;
|
||||
let c = 2;
|
||||
let d = 3;
|
||||
let e = 4;
|
||||
let f = 5;
|
||||
let g = 6;
|
||||
let h = 7;
|
||||
let i = 8;
|
||||
let j = 9;
|
||||
"#};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
move r8 0 #a
|
||||
move r9 1 #b
|
||||
move r10 2 #c
|
||||
move r11 3 #d
|
||||
move r12 4 #e
|
||||
move r13 5 #f
|
||||
move r14 6 #g
|
||||
push 7 #h
|
||||
push 8 #i
|
||||
push 9 #j
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variable_declaration_negative() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let i = -1;
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
move r8 -1 #i
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boolean_declaration() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let t = true;
|
||||
let f = false;
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
move r8 1 #t
|
||||
move r9 0 #f
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boolean_return() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
fn getTrue() {
|
||||
return true;
|
||||
};
|
||||
|
||||
let val = getTrue();
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
getTrue:
|
||||
push ra
|
||||
move r15 1 #returnValue
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
j ra
|
||||
main:
|
||||
jal getTrue
|
||||
move r8 r15 #val
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
58
rust_compiler/libs/compiler/src/test/function_declaration.rs
Normal file
58
rust_compiler/libs/compiler/src/test/function_declaration.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use indoc::indoc;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> {
|
||||
let compiled = compile!(debug r#"
|
||||
// we need more than 4 params to 'spill' into a stack var
|
||||
fn doSomething(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) {};
|
||||
"#);
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {"
|
||||
j main
|
||||
doSomething:
|
||||
pop r8 #arg9
|
||||
pop r9 #arg8
|
||||
pop r10 #arg7
|
||||
pop r11 #arg6
|
||||
pop r12 #arg5
|
||||
pop r13 #arg4
|
||||
pop r14 #arg3
|
||||
push ra
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 3
|
||||
j ra
|
||||
"}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_declaration_with_register_params() -> anyhow::Result<()> {
|
||||
let compiled = compile!(debug r#"
|
||||
// This is a test function declaration with no body
|
||||
fn doSomething(arg1, arg2) {
|
||||
};
|
||||
"#);
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {"
|
||||
j main
|
||||
doSomething:
|
||||
pop r8 #arg2
|
||||
pop r9 #arg1
|
||||
push ra
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
j ra
|
||||
"}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
177
rust_compiler/libs/compiler/src/test/logic_expression.rs
Normal file
177
rust_compiler/libs/compiler/src/test/logic_expression.rs
Normal file
@@ -0,0 +1,177 @@
|
||||
use crate::compile;
|
||||
use indoc::indoc;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_comparison_expressions() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let isGreater = 10 > 5;
|
||||
let isLess = 5 < 10;
|
||||
let isEqual = 5 == 5;
|
||||
let isNotEqual = 5 != 10;
|
||||
let isGreaterOrEqual = 10 >= 10;
|
||||
let isLessOrEqual = 5 <= 5;
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
sgt r1 10 5
|
||||
move r8 r1 #isGreater
|
||||
slt r2 5 10
|
||||
move r9 r2 #isLess
|
||||
seq r3 5 5
|
||||
move r10 r3 #isEqual
|
||||
sne r4 5 10
|
||||
move r11 r4 #isNotEqual
|
||||
sge r5 10 10
|
||||
move r12 r5 #isGreaterOrEqual
|
||||
sle r6 5 5
|
||||
move r13 r6 #isLessOrEqual
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_logical_and_or_not() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let logic1 = 1 && 1;
|
||||
let logic2 = 1 || 0;
|
||||
let logic3 = !1;
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
and r1 1 1
|
||||
move r8 r1 #logic1
|
||||
or r2 1 0
|
||||
move r9 r2 #logic2
|
||||
seq r3 1 0
|
||||
move r10 r3 #logic3
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_logic() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let logic = (10 > 5) && (5 < 10);
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
sgt r1 10 5
|
||||
slt r2 5 10
|
||||
and r3 r1 r2
|
||||
move r8 r3 #logic
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_math_with_logic() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let logic = (1 + 2) > 1;
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
add r1 1 2
|
||||
sgt r2 r1 1
|
||||
move r8 r2 #logic
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boolean_in_logic() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let res = true && false;
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
and r1 1 0
|
||||
move r8 r1 #res
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invert_a_boolean() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let i = true;
|
||||
let y = !i;
|
||||
|
||||
let result = y == false;
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
move r8 1 #i
|
||||
seq r1 r8 0
|
||||
move r9 r1 #y
|
||||
seq r2 r9 0
|
||||
move r10 r2 #result
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
149
rust_compiler/libs/compiler/src/test/loops.rs
Normal file
149
rust_compiler/libs/compiler/src/test/loops.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
use crate::compile;
|
||||
use indoc::indoc;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_infinite_loop() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let a = 0;
|
||||
loop {
|
||||
a = a + 1;
|
||||
}
|
||||
"
|
||||
};
|
||||
|
||||
// Labels: L1 (start), L2 (end)
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
move r8 0 #a
|
||||
L1:
|
||||
add r1 r8 1
|
||||
move r8 r1 #a
|
||||
j L1
|
||||
L2:
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_loop_break() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let a = 0;
|
||||
loop {
|
||||
a = a + 1;
|
||||
if (a > 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
"
|
||||
};
|
||||
|
||||
// Labels: L1 (start), L2 (end), L3 (if end - implicit else label)
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
move r8 0 #a
|
||||
L1:
|
||||
add r1 r8 1
|
||||
move r8 r1 #a
|
||||
sgt r2 r8 10
|
||||
beq r2 0 L3
|
||||
j L2
|
||||
L3:
|
||||
j L1
|
||||
L2:
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_while_loop() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let a = 0;
|
||||
while (a < 10) {
|
||||
a = a + 1;
|
||||
}
|
||||
"
|
||||
};
|
||||
|
||||
// Labels: L1 (start), L2 (end)
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
move r8 0 #a
|
||||
L1:
|
||||
slt r1 r8 10
|
||||
beq r1 0 L2
|
||||
add r2 r8 1
|
||||
move r8 r2 #a
|
||||
j L1
|
||||
L2:
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_loop_continue() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
r#"
|
||||
let a = 0;
|
||||
loop {
|
||||
a = a + 1;
|
||||
if (a < 5) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
"#
|
||||
};
|
||||
|
||||
// Labels: L1 (start), L2 (end), L3 (if end)
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
move r8 0 #a
|
||||
L1:
|
||||
add r1 r8 1
|
||||
move r8 r1 #a
|
||||
slt r2 r8 5
|
||||
beq r2 0 L3
|
||||
j L1
|
||||
L3:
|
||||
j L2
|
||||
j L1
|
||||
L2:
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
50
rust_compiler/libs/compiler/src/test/mod.rs
Normal file
50
rust_compiler/libs/compiler/src/test/mod.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
#![allow(clippy::crate_in_macro_def)]
|
||||
|
||||
macro_rules! output {
|
||||
($input:expr) => {
|
||||
String::from_utf8($input.into_inner()?)?
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg_attr(test, macro_export)]
|
||||
macro_rules! compile {
|
||||
($source:expr) => {{
|
||||
let mut writer = std::io::BufWriter::new(Vec::new());
|
||||
let compiler = ::Compiler::new(
|
||||
parser::Parser::new(tokenizer::Tokenizer::from(String::from($source))),
|
||||
&mut writer,
|
||||
None,
|
||||
);
|
||||
compiler.compile()?;
|
||||
output!(writer)
|
||||
}};
|
||||
|
||||
(result $source:expr) => {{
|
||||
let mut writer = std::io::BufWriter::new(Vec::new());
|
||||
let compiler = crate::Compiler::new(
|
||||
parser::Parser::new(tokenizer::Tokenizer::from(String::from($source))),
|
||||
&mut writer,
|
||||
Some(crate::CompilerConfig { debug: true }),
|
||||
);
|
||||
compiler.compile()
|
||||
}};
|
||||
|
||||
(debug $source:expr) => {{
|
||||
let mut writer = std::io::BufWriter::new(Vec::new());
|
||||
let compiler = crate::Compiler::new(
|
||||
parser::Parser::new(tokenizer::Tokenizer::from(String::from($source))),
|
||||
&mut writer,
|
||||
Some(crate::CompilerConfig { debug: true }),
|
||||
);
|
||||
compiler.compile()?;
|
||||
output!(writer)
|
||||
}};
|
||||
}
|
||||
mod binary_expression;
|
||||
mod branching;
|
||||
mod declaration_function_invocation;
|
||||
mod declaration_literal;
|
||||
mod function_declaration;
|
||||
mod logic_expression;
|
||||
mod loops;
|
||||
mod syscall;
|
||||
159
rust_compiler/libs/compiler/src/test/syscall.rs
Normal file
159
rust_compiler/libs/compiler/src/test/syscall.rs
Normal file
@@ -0,0 +1,159 @@
|
||||
use crate::compile;
|
||||
use indoc::indoc;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_yield() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
yield();
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
yield
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sleep() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
sleep(3);
|
||||
let sleepAmount = 15;
|
||||
sleep(sleepAmount);
|
||||
sleep(sleepAmount * 2);
|
||||
"
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
sleep 3
|
||||
move r8 15 #sleepAmount
|
||||
sleep r8
|
||||
mul r1 r8 2
|
||||
sleep r1
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_on_device() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
r#"
|
||||
device airConditioner = "d0";
|
||||
let internalTemp = 20c;
|
||||
|
||||
setOnDevice(airConditioner, "On", internalTemp > 25c);
|
||||
"#
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
move r8 293.15 #internalTemp
|
||||
sgt r1 r8 298.15
|
||||
s d0 On r1
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_on_device_batched() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
r#"
|
||||
let doorHash = hash("Door");
|
||||
setOnDeviceBatched(doorHash, "Lock", true);
|
||||
"#
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
r#"
|
||||
j main
|
||||
main:
|
||||
move r15 HASH("Door") #hash_ret
|
||||
move r8 r15 #doorHash
|
||||
sb r8 Lock 1
|
||||
"#
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_from_device() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
r#"
|
||||
device airCon = "d0";
|
||||
|
||||
let setting = loadFromDevice(airCon, "On");
|
||||
"#
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
l r15 d0 On
|
||||
move r8 r15 #setting
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
r#"
|
||||
let nameHash = hash("testValue");
|
||||
"#
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
r#"
|
||||
j main
|
||||
main:
|
||||
move r15 HASH("testValue") #hash_ret
|
||||
move r8 r15 #nameHash
|
||||
"#
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
1247
rust_compiler/libs/compiler/src/v1.rs
Normal file
1247
rust_compiler/libs/compiler/src/v1.rs
Normal file
File diff suppressed because it is too large
Load Diff
188
rust_compiler/libs/compiler/src/variable_manager.rs
Normal file
188
rust_compiler/libs/compiler/src/variable_manager.rs
Normal file
@@ -0,0 +1,188 @@
|
||||
// r15 : Return Value
|
||||
// r0 : Unmanaged temp variable
|
||||
// r1 - r7 : Temporary Variables
|
||||
// r8 - r14 : Persistant Variables
|
||||
|
||||
use quick_error::quick_error;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
const TEMP: [u8; 7] = [1, 2, 3, 4, 5, 6, 7];
|
||||
const PERSIST: [u8; 7] = [8, 9, 10, 11, 12, 13, 14];
|
||||
|
||||
quick_error! {
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
DuplicateVariable(var: String) {
|
||||
display("{var} already exists.")
|
||||
}
|
||||
UnknownVariable(var: String) {
|
||||
display("{var} does not exist.")
|
||||
}
|
||||
Unknown(reason: String) {
|
||||
display("{reason}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A request to store a variable at a specific register type
|
||||
pub enum LocationRequest {
|
||||
#[allow(dead_code)]
|
||||
/// Request to store a variable in a temprary register.
|
||||
Temp,
|
||||
/// Request to store a variable in a persistant register.
|
||||
Persist,
|
||||
/// Request to store a variable in the stack.
|
||||
Stack,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum VariableLocation {
|
||||
/// Represents a temporary register (r1 - r7)
|
||||
Temporary(u8),
|
||||
/// Represents a persistant register (r8 - r14)
|
||||
Persistant(u8),
|
||||
/// Represents a a stack offset (current stack - offset = variable loc)
|
||||
Stack(u16),
|
||||
}
|
||||
|
||||
pub struct VariableScope<'a> {
|
||||
temporary_vars: VecDeque<u8>,
|
||||
persistant_vars: VecDeque<u8>,
|
||||
var_lookup_table: HashMap<String, VariableLocation>,
|
||||
stack_offset: u16,
|
||||
parent: Option<&'a VariableScope<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Default for VariableScope<'a> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
parent: None,
|
||||
stack_offset: 0,
|
||||
persistant_vars: PERSIST.to_vec().into(),
|
||||
temporary_vars: TEMP.to_vec().into(),
|
||||
var_lookup_table: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> VariableScope<'a> {
|
||||
#[allow(dead_code)]
|
||||
pub const TEMP_REGISTER_COUNT: u8 = 7;
|
||||
pub const PERSIST_REGISTER_COUNT: u8 = 7;
|
||||
|
||||
pub const RETURN_REGISTER: u8 = 15;
|
||||
pub const TEMP_STACK_REGISTER: u8 = 0;
|
||||
|
||||
pub fn registers(&self) -> impl Iterator<Item = &u8> {
|
||||
self.var_lookup_table
|
||||
.values()
|
||||
.filter(|val| {
|
||||
matches!(
|
||||
val,
|
||||
VariableLocation::Temporary(_) | VariableLocation::Persistant(_)
|
||||
)
|
||||
})
|
||||
.map(|loc| match loc {
|
||||
VariableLocation::Persistant(reg) | VariableLocation::Temporary(reg) => reg,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn scoped(parent: &'a VariableScope<'a>) -> Self {
|
||||
Self {
|
||||
parent: Option::Some(parent),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stack_offset(&self) -> u16 {
|
||||
self.stack_offset
|
||||
}
|
||||
|
||||
/// Adds and tracks a new scoped variable. If the location you request is full, will fall back
|
||||
/// to the stack.
|
||||
pub fn add_variable(
|
||||
&mut self,
|
||||
var_name: impl Into<String>,
|
||||
location: LocationRequest,
|
||||
) -> Result<VariableLocation, Error> {
|
||||
let var_name = var_name.into();
|
||||
if self.var_lookup_table.contains_key(var_name.as_str()) {
|
||||
return Err(Error::DuplicateVariable(var_name));
|
||||
}
|
||||
let var_location = match location {
|
||||
LocationRequest::Temp => {
|
||||
if let Some(next_var) = self.temporary_vars.pop_front() {
|
||||
VariableLocation::Temporary(next_var)
|
||||
} else {
|
||||
let loc = VariableLocation::Stack(self.stack_offset);
|
||||
self.stack_offset += 1;
|
||||
loc
|
||||
}
|
||||
}
|
||||
LocationRequest::Persist => {
|
||||
if let Some(next_var) = self.persistant_vars.pop_front() {
|
||||
VariableLocation::Persistant(next_var)
|
||||
} else {
|
||||
let loc = VariableLocation::Stack(self.stack_offset);
|
||||
self.stack_offset += 1;
|
||||
loc
|
||||
}
|
||||
}
|
||||
LocationRequest::Stack => {
|
||||
let loc = VariableLocation::Stack(self.stack_offset);
|
||||
self.stack_offset += 1;
|
||||
loc
|
||||
}
|
||||
};
|
||||
self.var_lookup_table.insert(var_name, var_location.clone());
|
||||
|
||||
Ok(var_location)
|
||||
}
|
||||
|
||||
pub fn get_location_of(
|
||||
&mut self,
|
||||
var_name: impl Into<String>,
|
||||
) -> Result<VariableLocation, Error> {
|
||||
let var_name = var_name.into();
|
||||
let var = self
|
||||
.var_lookup_table
|
||||
.get(var_name.as_str())
|
||||
.cloned()
|
||||
.ok_or(Error::UnknownVariable(var_name))?;
|
||||
|
||||
if let VariableLocation::Stack(inserted_at_offset) = var {
|
||||
Ok(VariableLocation::Stack(
|
||||
self.stack_offset - inserted_at_offset,
|
||||
))
|
||||
} else {
|
||||
Ok(var)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_parent(&self) -> bool {
|
||||
self.parent.is_some()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn free_temp(&mut self, var_name: impl Into<String>) -> Result<(), Error> {
|
||||
let var_name = var_name.into();
|
||||
let Some(location) = self.var_lookup_table.remove(var_name.as_str()) else {
|
||||
return Err(Error::UnknownVariable(var_name));
|
||||
};
|
||||
|
||||
match location {
|
||||
VariableLocation::Temporary(t) => {
|
||||
self.temporary_vars.push_back(t);
|
||||
}
|
||||
VariableLocation::Persistant(_) => {
|
||||
return Err(Error::UnknownVariable(String::from(
|
||||
"Attempted to free a `let` variable.",
|
||||
)));
|
||||
}
|
||||
VariableLocation::Stack(_) => {}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
9
rust_compiler/libs/compiler/test_files/deviceIo.slang
Normal file
9
rust_compiler/libs/compiler/test_files/deviceIo.slang
Normal file
@@ -0,0 +1,9 @@
|
||||
device airConditioner = "d0";
|
||||
device gasSensor = "d1";
|
||||
|
||||
loop {
|
||||
yield();
|
||||
let indoorTemp = loadFromDevice(gasSensor, "Temperature");
|
||||
let shouldSet = indoorTemp > 30c;
|
||||
setOnDevice(airConditioner, "On", shouldSet);
|
||||
}
|
||||
12
rust_compiler/libs/parser/Cargo.toml
Normal file
12
rust_compiler/libs/parser/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "parser"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
quick-error = { workspace = true }
|
||||
tokenizer = { path = "../tokenizer" }
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { version = "1" }
|
||||
1266
rust_compiler/libs/parser/src/lib.rs
Normal file
1266
rust_compiler/libs/parser/src/lib.rs
Normal file
File diff suppressed because it is too large
Load Diff
226
rust_compiler/libs/parser/src/sys_call.rs
Normal file
226
rust_compiler/libs/parser/src/sys_call.rs
Normal file
@@ -0,0 +1,226 @@
|
||||
use crate::tree_node::{Expression, Literal};
|
||||
|
||||
use super::LiteralOrVariable;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Math {
|
||||
/// Returns the angle in radians whose cosine is the specified number.
|
||||
/// ## In Game
|
||||
/// `acos r? a(r?|num)`
|
||||
Acos(LiteralOrVariable),
|
||||
/// Returns the angle in radians whose sine is the specified number.
|
||||
/// ## In Game
|
||||
/// `asin r? a(r?|num)`
|
||||
Asin(LiteralOrVariable),
|
||||
/// Returns the angle in radians whose tangent is the specified number.
|
||||
/// ## In Game
|
||||
/// `atan r? a(r?|num)`
|
||||
Atan(LiteralOrVariable),
|
||||
/// Returns the angle in radians whose tangent is the quotient of the specified numbers.
|
||||
/// ## In Game
|
||||
/// `atan2 r? a(r?|num) b(r?|num)`
|
||||
Atan2(LiteralOrVariable, LiteralOrVariable),
|
||||
/// Gets the absolute value of a number.
|
||||
/// ## In Game
|
||||
/// `abs r? a(r?|num)`
|
||||
Abs(LiteralOrVariable),
|
||||
/// Rounds a number up to the nearest whole number.
|
||||
/// ## In Game
|
||||
/// `ceil r? a(r?|num)`
|
||||
Ceil(LiteralOrVariable),
|
||||
/// Returns the cosine of the specified angle in radians.
|
||||
/// ## In Game
|
||||
/// cos r? a(r?|num)
|
||||
Cos(LiteralOrVariable),
|
||||
/// Rounds a number down to the nearest whole number.
|
||||
/// ## In Game
|
||||
/// `floor r? a(r?|num)`
|
||||
Floor(LiteralOrVariable),
|
||||
/// Computes the natural logarithm of a number.
|
||||
/// ## In Game
|
||||
/// `log r? a(r?|num)`
|
||||
Log(LiteralOrVariable),
|
||||
/// Computes the maximum of two numbers.
|
||||
/// ## In Game
|
||||
/// `max r? a(r?|num) b(r?|num)`
|
||||
Max(LiteralOrVariable, LiteralOrVariable),
|
||||
/// Computes the minimum of two numbers.
|
||||
/// ## In Game
|
||||
/// `min r? a(r?|num) b(r?|num)`
|
||||
Min(LiteralOrVariable, LiteralOrVariable),
|
||||
/// Gets a random number between 0 and 1.
|
||||
/// ## In Game
|
||||
/// `rand r?`
|
||||
Rand,
|
||||
/// Returns the sine of the specified angle in radians.
|
||||
/// ## In Game
|
||||
/// `sin r? a(r?|num)`
|
||||
Sin(LiteralOrVariable),
|
||||
/// Computes the square root of a number.
|
||||
/// ## In Game
|
||||
/// `sqrt r? a(r?|num)`
|
||||
Sqrt(LiteralOrVariable),
|
||||
/// Returns the tangent of the specified angle in radians.
|
||||
/// ## In Game
|
||||
/// `tan r? a(r?|num)`
|
||||
Tan(LiteralOrVariable),
|
||||
/// Truncates a number by removing the decimal portion.
|
||||
/// ## In Game
|
||||
/// `trunc r? a(r?|num)`
|
||||
Trunc(LiteralOrVariable),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Math {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Math::Acos(a) => write!(f, "acos({})", a),
|
||||
Math::Asin(a) => write!(f, "asin({})", a),
|
||||
Math::Atan(a) => write!(f, "atan({})", a),
|
||||
Math::Atan2(a, b) => write!(f, "atan2({}, {})", a, b),
|
||||
Math::Abs(a) => write!(f, "abs({})", a),
|
||||
Math::Ceil(a) => write!(f, "ceil({})", a),
|
||||
Math::Cos(a) => write!(f, "cos({})", a),
|
||||
Math::Floor(a) => write!(f, "floor({})", a),
|
||||
Math::Log(a) => write!(f, "log({})", a),
|
||||
Math::Max(a, b) => write!(f, "max({}, {})", a, b),
|
||||
Math::Min(a, b) => write!(f, "min({}, {})", a, b),
|
||||
Math::Rand => write!(f, "rand()"),
|
||||
Math::Sin(a) => write!(f, "sin({})", a),
|
||||
Math::Sqrt(a) => write!(f, "sqrt({})", a),
|
||||
Math::Tan(a) => write!(f, "tan({})", a),
|
||||
Math::Trunc(a) => write!(f, "trunc({})", a),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum System {
|
||||
/// Pauses execution for exactly 1 tick and then resumes.
|
||||
/// ## In Game
|
||||
/// yield
|
||||
Yield,
|
||||
/// Represents a function that can be called to sleep for a certain amount of time.
|
||||
/// ## In Game
|
||||
/// `sleep a(r?|num)`
|
||||
Sleep(Box<Expression>),
|
||||
/// Gets the in-game hash for a specific prefab name.
|
||||
/// ## In Game
|
||||
/// `HASH("prefabName")`
|
||||
Hash(Literal),
|
||||
/// Represents a function which loads a device variable into a register.
|
||||
/// ## In Game
|
||||
/// `l r? d? var`
|
||||
/// ## Examples
|
||||
/// `l r0 d0 Setting`
|
||||
/// `l r1 d5 Pressure`
|
||||
LoadFromDevice(LiteralOrVariable, Literal),
|
||||
/// Function which gets a LogicType from all connected network devices that match
|
||||
/// the provided device hash and name, aggregating them via a batchMode
|
||||
/// ## In Game
|
||||
/// lbn r? deviceHash nameHash logicType batchMode
|
||||
/// ## Examples
|
||||
/// lbn r0 HASH("StructureWallLight") HASH("wallLight") On Minimum
|
||||
LoadBatchNamed(LiteralOrVariable, Box<Expression>, Literal, Literal),
|
||||
/// Loads a LogicType from all connected network devices, aggregating them via a
|
||||
/// batchMode
|
||||
/// ## In Game
|
||||
/// lb r? deviceHash loggicType batchMode
|
||||
/// ## Examples
|
||||
/// lb r0 HASH("StructureWallLight") On Minimum
|
||||
LoadBatch(LiteralOrVariable, Literal, Literal),
|
||||
/// Represents a function which stores a setting into a specific device.
|
||||
/// ## In Game
|
||||
/// `s d? logicType r?`
|
||||
/// ## Example
|
||||
/// `s d0 Setting r0`
|
||||
SetOnDevice(LiteralOrVariable, Literal, Box<Expression>),
|
||||
/// Represents a function which stores a setting to all devices that match
|
||||
/// the given deviceHash
|
||||
/// ## In Game
|
||||
/// `sb deviceHash logictype r?`
|
||||
/// ## Example
|
||||
/// `sb HASH("Doors") Lock 1`
|
||||
SetOnDeviceBatched(LiteralOrVariable, Literal, Box<Expression>),
|
||||
/// Represents a function which stores a setting to all devices that match
|
||||
/// both the given deviceHash AND the given nameHash
|
||||
/// ## In Game
|
||||
/// `sbn deviceHash nameHash logicType r?`
|
||||
/// ## Example
|
||||
/// `sbn HASH("Doors") HASH("Exterior") Lock 1`
|
||||
SetOnDeviceBatchedNamed(
|
||||
LiteralOrVariable,
|
||||
LiteralOrVariable,
|
||||
Literal,
|
||||
Box<Expression>,
|
||||
),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for System {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
System::Yield => write!(f, "yield()"),
|
||||
System::Sleep(a) => write!(f, "sleep({})", a),
|
||||
System::Hash(a) => write!(f, "hash({})", a),
|
||||
System::LoadFromDevice(a, b) => write!(f, "loadFromDevice({}, {})", a, b),
|
||||
System::LoadBatch(a, b, c) => write!(f, "loadBatch({}, {}, {})", a, b, c),
|
||||
System::LoadBatchNamed(a, b, c, d) => {
|
||||
write!(f, "loadBatchNamed({}, {}, {}, {})", a, b, c, d)
|
||||
}
|
||||
System::SetOnDevice(a, b, c) => write!(f, "setOnDevice({}, {}, {})", a, b, c),
|
||||
System::SetOnDeviceBatched(a, b, c) => {
|
||||
write!(f, "setOnDeviceBatched({}, {}, {})", a, b, c)
|
||||
}
|
||||
System::SetOnDeviceBatchedNamed(a, b, c, d) => {
|
||||
write!(f, "setOnDeviceBatchedNamed({}, {}, {}, {})", a, b, c, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
/// This represents built in functions that cannot be overwritten, but can be invoked by the user as functions.
|
||||
pub enum SysCall {
|
||||
System(System),
|
||||
/// Represents any mathmatical function that can be called.
|
||||
Math(Math),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SysCall {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
SysCall::System(s) => write!(f, "{}", s),
|
||||
SysCall::Math(m) => write!(f, "{}", m),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SysCall {
|
||||
pub fn is_syscall(identifier: &str) -> bool {
|
||||
matches!(
|
||||
identifier,
|
||||
"yield"
|
||||
| "sleep"
|
||||
| "hash"
|
||||
| "loadFromDevice"
|
||||
| "setOnDevice"
|
||||
| "setOnDeviceBatched"
|
||||
| "setOnDeviceBatchedNamed"
|
||||
| "acos"
|
||||
| "asin"
|
||||
| "atan"
|
||||
| "atan2"
|
||||
| "abs"
|
||||
| "ceil"
|
||||
| "cos"
|
||||
| "floor"
|
||||
| "log"
|
||||
| "max"
|
||||
| "min"
|
||||
| "rand"
|
||||
| "sin"
|
||||
| "sqrt"
|
||||
| "tan"
|
||||
| "trunc"
|
||||
)
|
||||
}
|
||||
}
|
||||
21
rust_compiler/libs/parser/src/test/blocks.rs
Normal file
21
rust_compiler/libs/parser/src/test/blocks.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use tokenizer::Tokenizer;
|
||||
|
||||
use crate::Parser;
|
||||
|
||||
#[test]
|
||||
fn test_block() -> anyhow::Result<()> {
|
||||
let mut parser = crate::parser!(
|
||||
r#"
|
||||
{
|
||||
let x = 5;
|
||||
let y = 10;
|
||||
}
|
||||
"#
|
||||
);
|
||||
|
||||
let expression = parser.parse()?.unwrap();
|
||||
|
||||
assert_eq!("{ (let x = 5); (let y = 10); }", expression.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
115
rust_compiler/libs/parser/src/test/mod.rs
Normal file
115
rust_compiler/libs/parser/src/test/mod.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
#[macro_export]
|
||||
macro_rules! parser {
|
||||
($input:expr) => {
|
||||
Parser::new(Tokenizer::from($input.to_owned()))
|
||||
};
|
||||
}
|
||||
|
||||
mod blocks;
|
||||
use super::Parser;
|
||||
use super::Tokenizer;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
fn test_unsupported_keywords() -> Result<()> {
|
||||
let mut parser = parser!("enum x;");
|
||||
assert!(parser.parse().is_err());
|
||||
|
||||
let mut parser = parser!("if x {}");
|
||||
assert!(parser.parse().is_err());
|
||||
|
||||
let mut parser = parser!("else {}");
|
||||
assert!(parser.parse().is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_declarations() -> Result<()> {
|
||||
let input = r#"
|
||||
let x = 5;
|
||||
// The below line should fail
|
||||
let y = 234
|
||||
"#;
|
||||
let tokenizer = Tokenizer::from(input.to_owned());
|
||||
let mut parser = Parser::new(tokenizer);
|
||||
|
||||
let expression = parser.parse()?.unwrap();
|
||||
|
||||
assert_eq!("(let x = 5)", expression.to_string());
|
||||
|
||||
assert!(parser.parse().is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_expression() -> Result<()> {
|
||||
let input = r#"
|
||||
// This is a function. The parser is starting to get more complex
|
||||
fn add(x, y) {
|
||||
let z = x;
|
||||
}
|
||||
"#;
|
||||
|
||||
let tokenizer = Tokenizer::from(input.to_owned());
|
||||
let mut parser = Parser::new(tokenizer);
|
||||
|
||||
let expression = parser.parse()?.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
"(fn add(x, y) { { (let z = x); } })",
|
||||
expression.to_string()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_invocation() -> Result<()> {
|
||||
let input = r#"
|
||||
add();
|
||||
"#;
|
||||
|
||||
let tokenizer = Tokenizer::from(input.to_owned());
|
||||
let mut parser = Parser::new(tokenizer);
|
||||
|
||||
let expression = parser.parse()?.unwrap();
|
||||
|
||||
assert_eq!("add()", expression.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_priority_expression() -> Result<()> {
|
||||
let input = r#"
|
||||
let x = (4);
|
||||
"#;
|
||||
|
||||
let tokenizer = Tokenizer::from(input.to_owned());
|
||||
let mut parser = Parser::new(tokenizer);
|
||||
|
||||
let expression = parser.parse()?.unwrap();
|
||||
|
||||
assert_eq!("(let x = (4))", expression.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binary_expression() -> Result<()> {
|
||||
let expr = parser!("4 ** 2 + 5 ** 2").parse()?.unwrap();
|
||||
assert_eq!("((4 ** 2) + (5 ** 2))", expr.to_string());
|
||||
|
||||
let expr = parser!("2 ** 3 ** 4").parse()?.unwrap();
|
||||
assert_eq!("(2 ** (3 ** 4))", expr.to_string());
|
||||
|
||||
let expr = parser!("45 * 2 - 15 / 5 + 5 ** 2").parse()?.unwrap();
|
||||
assert_eq!("(((45 * 2) - (15 / 5)) + (5 ** 2))", expr.to_string());
|
||||
|
||||
let expr = parser!("(5 - 2) * 10").parse()?.unwrap();
|
||||
assert_eq!("(((5 - 2)) * 10)", expr.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
258
rust_compiler/libs/parser/src/tree_node.rs
Normal file
258
rust_compiler/libs/parser/src/tree_node.rs
Normal file
@@ -0,0 +1,258 @@
|
||||
use super::sys_call::SysCall;
|
||||
use tokenizer::token::Number;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub enum Literal {
|
||||
Number(Number),
|
||||
String(String),
|
||||
Boolean(bool),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Literal {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Literal::Number(n) => write!(f, "{}", n),
|
||||
Literal::String(s) => write!(f, "\"{}\"", s),
|
||||
Literal::Boolean(b) => write!(f, "{}", if *b { 1 } else { 0 }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum BinaryExpression {
|
||||
Add(Box<Expression>, Box<Expression>),
|
||||
Multiply(Box<Expression>, Box<Expression>),
|
||||
Divide(Box<Expression>, Box<Expression>),
|
||||
Subtract(Box<Expression>, Box<Expression>),
|
||||
Exponent(Box<Expression>, Box<Expression>),
|
||||
Modulo(Box<Expression>, Box<Expression>),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BinaryExpression {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
BinaryExpression::Add(l, r) => write!(f, "({} + {})", l, r),
|
||||
BinaryExpression::Multiply(l, r) => write!(f, "({} * {})", l, r),
|
||||
BinaryExpression::Divide(l, r) => write!(f, "({} / {})", l, r),
|
||||
BinaryExpression::Subtract(l, r) => write!(f, "({} - {})", l, r),
|
||||
BinaryExpression::Exponent(l, r) => write!(f, "({} ** {})", l, r),
|
||||
BinaryExpression::Modulo(l, r) => write!(f, "({} % {})", l, r),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum LogicalExpression {
|
||||
And(Box<Expression>, Box<Expression>),
|
||||
Or(Box<Expression>, Box<Expression>),
|
||||
Not(Box<Expression>),
|
||||
Equal(Box<Expression>, Box<Expression>),
|
||||
NotEqual(Box<Expression>, Box<Expression>),
|
||||
GreaterThan(Box<Expression>, Box<Expression>),
|
||||
GreaterThanOrEqual(Box<Expression>, Box<Expression>),
|
||||
LessThan(Box<Expression>, Box<Expression>),
|
||||
LessThanOrEqual(Box<Expression>, Box<Expression>),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LogicalExpression {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
LogicalExpression::And(l, r) => write!(f, "({} && {})", l, r),
|
||||
LogicalExpression::Or(l, r) => write!(f, "({} || {})", l, r),
|
||||
LogicalExpression::Not(e) => write!(f, "(!{})", e),
|
||||
LogicalExpression::Equal(l, r) => write!(f, "({} == {})", l, r),
|
||||
LogicalExpression::NotEqual(l, r) => write!(f, "({} != {})", l, r),
|
||||
LogicalExpression::GreaterThan(l, r) => write!(f, "({} > {})", l, r),
|
||||
LogicalExpression::GreaterThanOrEqual(l, r) => write!(f, "({} >= {})", l, r),
|
||||
LogicalExpression::LessThan(l, r) => write!(f, "({} < {})", l, r),
|
||||
LogicalExpression::LessThanOrEqual(l, r) => write!(f, "({} <= {})", l, r),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct AssignmentExpression {
|
||||
pub identifier: String,
|
||||
pub expression: Box<Expression>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AssignmentExpression {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "({} = {})", self.identifier, self.expression)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct FunctionExpression {
|
||||
pub name: String,
|
||||
pub arguments: Vec<String>,
|
||||
pub body: BlockExpression,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FunctionExpression {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"(fn {}({}) {{ {} }})",
|
||||
self.name,
|
||||
self.arguments.to_vec().join(", "),
|
||||
self.body
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct BlockExpression(pub Vec<Expression>);
|
||||
|
||||
impl std::fmt::Display for BlockExpression {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{{ {}; }}",
|
||||
self.0
|
||||
.iter()
|
||||
.map(|e| e.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join("; ")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct InvocationExpression {
|
||||
pub name: String,
|
||||
pub arguments: Vec<Expression>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for InvocationExpression {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}({})",
|
||||
self.name,
|
||||
self.arguments
|
||||
.iter()
|
||||
.map(|e| e.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum LiteralOrVariable {
|
||||
Literal(Literal),
|
||||
Variable(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LiteralOrVariable {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
LiteralOrVariable::Literal(l) => write!(f, "{}", l),
|
||||
LiteralOrVariable::Variable(v) => write!(f, "{}", v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct DeviceDeclarationExpression {
|
||||
/// any variable-like name
|
||||
pub name: String,
|
||||
/// The device port, ex. (db, d0, d1, d2, d3, d4, d5)
|
||||
pub device: String,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DeviceDeclarationExpression {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "(device {} = {})", self.name, self.device)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct IfExpression {
|
||||
pub condition: Box<Expression>,
|
||||
pub body: BlockExpression,
|
||||
pub else_branch: Option<Box<Expression>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for IfExpression {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "(if ({}) {}", self.condition, self.body)?;
|
||||
if let Some(else_branch) = &self.else_branch {
|
||||
write!(f, " else {}", else_branch)?;
|
||||
}
|
||||
write!(f, ")")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct LoopExpression {
|
||||
pub body: BlockExpression,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LoopExpression {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "(loop {})", self.body)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct WhileExpression {
|
||||
pub condition: Box<Expression>,
|
||||
pub body: BlockExpression,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for WhileExpression {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "(while {} {})", self.condition, self.body)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Expression {
|
||||
Assignment(AssignmentExpression),
|
||||
Binary(BinaryExpression),
|
||||
Block(BlockExpression),
|
||||
Break,
|
||||
Continue,
|
||||
Declaration(String, Box<Expression>),
|
||||
DeviceDeclaration(DeviceDeclarationExpression),
|
||||
Function(FunctionExpression),
|
||||
If(IfExpression),
|
||||
Invocation(InvocationExpression),
|
||||
Literal(Literal),
|
||||
Logical(LogicalExpression),
|
||||
Loop(LoopExpression),
|
||||
Negation(Box<Expression>),
|
||||
Priority(Box<Expression>),
|
||||
Return(Box<Expression>),
|
||||
Syscall(SysCall),
|
||||
Variable(String),
|
||||
While(WhileExpression),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Expression {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Expression::Assignment(e) => write!(f, "{}", e),
|
||||
Expression::Binary(e) => write!(f, "{}", e),
|
||||
Expression::Block(e) => write!(f, "{}", e),
|
||||
Expression::Break => write!(f, "break"),
|
||||
Expression::Continue => write!(f, "continue"),
|
||||
Expression::Declaration(id, e) => write!(f, "(let {} = {})", id, e),
|
||||
Expression::DeviceDeclaration(e) => write!(f, "{}", e),
|
||||
Expression::Function(e) => write!(f, "{}", e),
|
||||
Expression::If(e) => write!(f, "{}", e),
|
||||
Expression::Invocation(e) => write!(f, "{}", e),
|
||||
Expression::Literal(l) => write!(f, "{}", l),
|
||||
Expression::Logical(e) => write!(f, "{}", e),
|
||||
Expression::Loop(e) => write!(f, "{}", e),
|
||||
Expression::Negation(e) => write!(f, "(-{})", e),
|
||||
Expression::Priority(e) => write!(f, "({})", e),
|
||||
Expression::Return(e) => write!(f, "(return {})", e),
|
||||
Expression::Syscall(e) => write!(f, "{}", e),
|
||||
Expression::Variable(id) => write!(f, "{}", id),
|
||||
Expression::While(e) => write!(f, "{}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
11
rust_compiler/libs/tokenizer/Cargo.toml
Normal file
11
rust_compiler/libs/tokenizer/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "tokenizer"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
rust_decimal = { workspace = true }
|
||||
quick-error = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { version = "^1" }
|
||||
932
rust_compiler/libs/tokenizer/src/lib.rs
Normal file
932
rust_compiler/libs/tokenizer/src/lib.rs
Normal file
@@ -0,0 +1,932 @@
|
||||
pub mod token;
|
||||
|
||||
use quick_error::quick_error;
|
||||
use rust_decimal::Decimal;
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::VecDeque,
|
||||
io::{BufReader, Cursor, Read, Seek, SeekFrom},
|
||||
path::PathBuf,
|
||||
};
|
||||
use token::{Keyword, Number, Symbol, Temperature, Token, TokenType};
|
||||
|
||||
quick_error! {
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
IOError(err: std::io::Error) {
|
||||
from()
|
||||
display("IO Error: {}", err)
|
||||
source(err)
|
||||
}
|
||||
NumberParseError(err: std::num::ParseIntError, line: usize, column: usize) {
|
||||
display("Number Parse Error: {}\nLine: {}, Column: {}", err, line, column)
|
||||
source(err)
|
||||
}
|
||||
DecimalParseError(err: rust_decimal::Error, line: usize, column: usize) {
|
||||
display("Decimal Parse Error: {}\nLine: {}, Column: {}", err, line, column)
|
||||
source(err)
|
||||
}
|
||||
UnknownSymbolError(char: char, line: usize, column: usize) {
|
||||
display("Unknown Symbol: {}\nLine: {}, Column: {}", char, line, column)
|
||||
}
|
||||
UnknownKeywordOrIdentifierError(val: String, line: usize, column: usize) {
|
||||
display("Unknown Keyword or Identifier: {}\nLine: {}, Column: {}", val, line, column)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Tokenize: Read + Seek {}
|
||||
|
||||
impl<T> Tokenize for T where T: Read + Seek {}
|
||||
|
||||
pub struct Tokenizer {
|
||||
reader: BufReader<Box<dyn Tokenize>>,
|
||||
char_buffer: [u8; 1],
|
||||
line: usize,
|
||||
column: usize,
|
||||
returned_eof: bool,
|
||||
}
|
||||
|
||||
impl Tokenizer {
|
||||
pub fn from_path(input_file: impl Into<PathBuf>) -> Result<Self, Error> {
|
||||
let file = std::fs::File::open(input_file.into())?;
|
||||
let reader = BufReader::new(Box::new(file) as Box<dyn Tokenize>);
|
||||
|
||||
Ok(Self {
|
||||
reader,
|
||||
line: 1,
|
||||
column: 1,
|
||||
char_buffer: [0],
|
||||
returned_eof: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Tokenizer {
|
||||
fn from(input: String) -> Self {
|
||||
let reader = BufReader::new(Box::new(Cursor::new(input)) as Box<dyn Tokenize>);
|
||||
|
||||
Self {
|
||||
reader,
|
||||
line: 1,
|
||||
column: 1,
|
||||
char_buffer: [0],
|
||||
returned_eof: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Tokenizer {
|
||||
fn from(value: &str) -> Self {
|
||||
Self::from(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Tokenizer {
|
||||
/// Consumes the tokenizer and returns the next token in the stream
|
||||
/// If there are no more tokens in the stream, this function returns None
|
||||
/// If there is an error reading the stream, this function returns an error
|
||||
///
|
||||
/// # Important
|
||||
/// This function will increment the line and column counters
|
||||
fn next_char(&mut self) -> Result<Option<char>, Error> {
|
||||
let bytes_read = self.reader.read(&mut self.char_buffer)?;
|
||||
|
||||
if bytes_read == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Safety: The buffer is guaranteed to have 1 value as it is initialized with a size of 1
|
||||
let c = self.char_buffer[0] as char;
|
||||
if c == '\n' {
|
||||
self.line += 1;
|
||||
self.column = 1;
|
||||
} else {
|
||||
self.column += 1;
|
||||
}
|
||||
|
||||
Ok(Some(c))
|
||||
}
|
||||
|
||||
/// Peeks the next character in the stream without consuming it
|
||||
///
|
||||
/// # Important
|
||||
/// This does not increment the line or column counters
|
||||
fn peek_next_char(&mut self) -> Result<Option<char>, Error> {
|
||||
let current_pos = self.reader.stream_position()?;
|
||||
|
||||
let to_return = if self.reader.read(&mut self.char_buffer)? == 0 {
|
||||
None
|
||||
} else {
|
||||
self.reader.seek(SeekFrom::Start(current_pos))?;
|
||||
|
||||
// Safety: The buffer is guaranteed to have 1 value as it is initialized with a size of 1
|
||||
Some(self.char_buffer[0] as char)
|
||||
};
|
||||
|
||||
Ok(to_return)
|
||||
}
|
||||
|
||||
/// Skips the current line in the stream.
|
||||
/// Useful for skipping comments or empty lines
|
||||
///
|
||||
/// # Important
|
||||
/// This function will increment the line and column counters
|
||||
fn skip_line(&mut self) -> Result<(), Error> {
|
||||
while let Some(next_char) = self.next_char()? {
|
||||
if next_char == '\n' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Consumes the tokenizer and returns the next token in the stream
|
||||
/// If there are no more tokens in the stream, this function returns None
|
||||
pub fn next_token(&mut self) -> Result<Option<Token>, Error> {
|
||||
while let Some(next_char) = self.next_char()? {
|
||||
// skip whitespace
|
||||
if next_char.is_whitespace() {
|
||||
continue;
|
||||
}
|
||||
// skip comments
|
||||
if next_char == '/' && self.peek_next_char()? == Some('/') {
|
||||
self.skip_line()?;
|
||||
continue;
|
||||
}
|
||||
|
||||
match next_char {
|
||||
// numbers
|
||||
'0'..='9' => {
|
||||
return self.tokenize_number(next_char).map(Some);
|
||||
}
|
||||
// strings
|
||||
'"' | '\'' => return self.tokenize_string(next_char).map(Some),
|
||||
// symbols excluding `"` and `'`
|
||||
char if !char.is_alphanumeric() && char != '"' && char != '\'' => {
|
||||
return self.tokenize_symbol(next_char).map(Some);
|
||||
}
|
||||
// keywords and identifiers
|
||||
char if char.is_alphabetic() => {
|
||||
return self.tokenize_keyword_or_identifier(next_char).map(Some);
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::UnknownSymbolError(next_char, self.line, self.column));
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.returned_eof {
|
||||
Ok(None)
|
||||
} else {
|
||||
self.returned_eof = true;
|
||||
Ok(Some(Token::new(TokenType::EOF, self.line, self.column)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Peeks the next token in the stream without consuming it
|
||||
/// If there are no more tokens in the stream, this function returns None
|
||||
pub fn peek_next(&mut self) -> Result<Option<Token>, Error> {
|
||||
let current_pos = self.reader.stream_position()?;
|
||||
let column = self.column;
|
||||
let line = self.line;
|
||||
|
||||
let token = self.next_token()?;
|
||||
self.reader.seek(SeekFrom::Start(current_pos))?;
|
||||
self.column = column;
|
||||
self.line = line;
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
/// Tokenizes a symbol
|
||||
fn tokenize_symbol(&mut self, first_symbol: char) -> Result<Token, Error> {
|
||||
/// Helper macro to create a symbol token
|
||||
macro_rules! symbol {
|
||||
($symbol:ident) => {
|
||||
Ok(Token::new(
|
||||
TokenType::Symbol(Symbol::$symbol),
|
||||
self.line,
|
||||
self.column,
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
match first_symbol {
|
||||
// single character symbols
|
||||
'(' => symbol!(LParen),
|
||||
')' => symbol!(RParen),
|
||||
'{' => symbol!(LBrace),
|
||||
'}' => symbol!(RBrace),
|
||||
'[' => symbol!(LBracket),
|
||||
']' => symbol!(RBracket),
|
||||
';' => symbol!(Semicolon),
|
||||
':' => symbol!(Colon),
|
||||
',' => symbol!(Comma),
|
||||
'+' => symbol!(Plus),
|
||||
'-' => symbol!(Minus),
|
||||
'/' => symbol!(Slash),
|
||||
|
||||
'.' => symbol!(Dot),
|
||||
'^' => symbol!(Caret),
|
||||
'%' => symbol!(Percent),
|
||||
|
||||
// multi-character symbols
|
||||
'<' if self.peek_next_char()? == Some('=') => {
|
||||
self.next_char()?;
|
||||
symbol!(LessThanOrEqual)
|
||||
}
|
||||
'<' => symbol!(LessThan),
|
||||
|
||||
'>' if self.peek_next_char()? == Some('=') => {
|
||||
self.next_char()?;
|
||||
symbol!(GreaterThanOrEqual)
|
||||
}
|
||||
'>' => symbol!(GreaterThan),
|
||||
|
||||
'=' if self.peek_next_char()? == Some('=') => {
|
||||
self.next_char()?;
|
||||
symbol!(Equal)
|
||||
}
|
||||
'=' => symbol!(Assign),
|
||||
|
||||
'!' if self.peek_next_char()? == Some('=') => {
|
||||
self.next_char()?;
|
||||
symbol!(NotEqual)
|
||||
}
|
||||
'!' => symbol!(LogicalNot),
|
||||
|
||||
'*' if self.peek_next_char()? == Some('*') => {
|
||||
self.next_char()?;
|
||||
symbol!(Exp)
|
||||
}
|
||||
'*' => symbol!(Asterisk),
|
||||
|
||||
'&' if self.peek_next_char()? == Some('&') => {
|
||||
self.next_char()?;
|
||||
symbol!(LogicalAnd)
|
||||
}
|
||||
'|' if self.peek_next_char()? == Some('|') => {
|
||||
self.next_char()?;
|
||||
symbol!(LogicalOr)
|
||||
}
|
||||
|
||||
_ => Err(Error::UnknownSymbolError(
|
||||
first_symbol,
|
||||
self.line,
|
||||
self.column,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tokenizes a number literal. Also handles temperatures with a suffix of `c`, `f`, or `k`.
|
||||
fn tokenize_number(&mut self, first_char: char) -> Result<Token, Error> {
|
||||
let mut primary = String::with_capacity(16);
|
||||
let mut decimal: Option<String> = None;
|
||||
let mut reading_decimal = false;
|
||||
|
||||
let column = self.column;
|
||||
let line = self.line;
|
||||
|
||||
primary.push(first_char);
|
||||
|
||||
while let Some(next_char) = self.peek_next_char()? {
|
||||
if next_char.is_whitespace() {
|
||||
break;
|
||||
}
|
||||
|
||||
if next_char == '.' {
|
||||
reading_decimal = true;
|
||||
self.next_char()?;
|
||||
continue;
|
||||
}
|
||||
|
||||
// support underscores in numbers for readability
|
||||
if next_char == '_' {
|
||||
self.next_char()?;
|
||||
continue;
|
||||
}
|
||||
|
||||
// This is for the times when we have a number followed by a symbol (like a semicolon or =)
|
||||
if !next_char.is_numeric() {
|
||||
break;
|
||||
}
|
||||
|
||||
if reading_decimal {
|
||||
decimal.get_or_insert_with(String::new).push(next_char);
|
||||
} else {
|
||||
primary.push(next_char);
|
||||
}
|
||||
self.next_char()?;
|
||||
}
|
||||
|
||||
let number: Number = if let Some(decimal) = decimal {
|
||||
let decimal_scale = decimal.len() as u32;
|
||||
let number = format!("{}{}", primary, decimal)
|
||||
.parse::<i128>()
|
||||
.map_err(|e| Error::NumberParseError(e, self.line, self.column))?;
|
||||
Number::Decimal(
|
||||
Decimal::try_from_i128_with_scale(number, decimal_scale)
|
||||
.map_err(|e| Error::DecimalParseError(e, line, column))?,
|
||||
)
|
||||
} else {
|
||||
Number::Integer(
|
||||
primary
|
||||
.parse()
|
||||
.map_err(|e| Error::NumberParseError(e, line, column))?,
|
||||
)
|
||||
};
|
||||
|
||||
// check if the next char is a temperature suffix
|
||||
if let Some(next_char) = self.peek_next_char()? {
|
||||
let temperature = match next_char {
|
||||
'c' => Temperature::Celsius(number),
|
||||
'f' => Temperature::Fahrenheit(number),
|
||||
'k' => Temperature::Kelvin(number),
|
||||
_ => return Ok(Token::new(TokenType::Number(number), line, column)),
|
||||
}
|
||||
.to_kelvin();
|
||||
|
||||
self.next_char()?;
|
||||
Ok(Token::new(TokenType::Number(temperature), line, column))
|
||||
} else {
|
||||
Ok(Token::new(TokenType::Number(number), line, column))
|
||||
}
|
||||
}
|
||||
|
||||
/// Tokenizes a string literal
|
||||
fn tokenize_string(&mut self, beginning_quote: char) -> Result<Token, Error> {
|
||||
let mut buffer = String::with_capacity(16);
|
||||
|
||||
let column = self.column;
|
||||
let line = self.line;
|
||||
|
||||
while let Some(next_char) = self.next_char()? {
|
||||
if next_char == beginning_quote {
|
||||
break;
|
||||
}
|
||||
|
||||
buffer.push(next_char);
|
||||
}
|
||||
|
||||
Ok(Token::new(TokenType::String(buffer), line, column))
|
||||
}
|
||||
|
||||
/// Tokenizes a keyword or an identifier. Also handles boolean literals
|
||||
fn tokenize_keyword_or_identifier(&mut self, first_char: char) -> Result<Token, Error> {
|
||||
macro_rules! keyword {
|
||||
($keyword:ident) => {{
|
||||
return Ok(Token::new(
|
||||
TokenType::Keyword(Keyword::$keyword),
|
||||
self.line,
|
||||
self.column,
|
||||
));
|
||||
}};
|
||||
}
|
||||
|
||||
/// Helper macro to check if the next character is whitespace or not alphanumeric
|
||||
macro_rules! next_ws {
|
||||
() => {
|
||||
matches!(self.peek_next_char()?, Some(x) if x.is_whitespace() || !x.is_alphanumeric()) || self.peek_next_char()?.is_none()
|
||||
};
|
||||
}
|
||||
|
||||
let mut buffer = String::with_capacity(16);
|
||||
let line = self.line;
|
||||
let column = self.column;
|
||||
|
||||
let mut looped_char = Some(first_char);
|
||||
|
||||
while let Some(next_char) = looped_char {
|
||||
if next_char.is_whitespace() {
|
||||
break;
|
||||
}
|
||||
|
||||
if !next_char.is_alphanumeric() {
|
||||
break;
|
||||
}
|
||||
buffer.push(next_char);
|
||||
|
||||
match buffer.as_str() {
|
||||
"let" if next_ws!() => keyword!(Let),
|
||||
"fn" if next_ws!() => keyword!(Fn),
|
||||
"if" if next_ws!() => keyword!(If),
|
||||
"else" if next_ws!() => keyword!(Else),
|
||||
"return" if next_ws!() => keyword!(Return),
|
||||
"enum" if next_ws!() => keyword!(Enum),
|
||||
"device" if next_ws!() => keyword!(Device),
|
||||
"loop" if next_ws!() => keyword!(Loop),
|
||||
"break" if next_ws!() => keyword!(Break),
|
||||
"while" if next_ws!() => keyword!(While),
|
||||
"continue" if next_ws!() => keyword!(Continue),
|
||||
|
||||
// boolean literals
|
||||
"true" if next_ws!() => {
|
||||
return Ok(Token::new(TokenType::Boolean(true), self.line, self.column));
|
||||
}
|
||||
"false" if next_ws!() => {
|
||||
return Ok(Token::new(
|
||||
TokenType::Boolean(false),
|
||||
self.line,
|
||||
self.column,
|
||||
));
|
||||
}
|
||||
// if the next character is whitespace or not alphanumeric, then we have an identifier
|
||||
// this is because keywords are checked first
|
||||
val if next_ws!() => {
|
||||
return Ok(Token::new(
|
||||
TokenType::Identifier(val.to_string()),
|
||||
line,
|
||||
column,
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
looped_char = self.next_char()?;
|
||||
}
|
||||
Err(Error::UnknownKeywordOrIdentifierError(buffer, line, column))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TokenizerBuffer {
|
||||
tokenizer: Tokenizer,
|
||||
buffer: VecDeque<Token>,
|
||||
history: VecDeque<Token>,
|
||||
}
|
||||
|
||||
impl TokenizerBuffer {
|
||||
pub fn new(tokenizer: Tokenizer) -> Self {
|
||||
Self {
|
||||
tokenizer,
|
||||
buffer: VecDeque::new(),
|
||||
history: VecDeque::with_capacity(128),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads the next token from the tokenizer, pushing the value to the back of the history
|
||||
/// and returning the token
|
||||
pub fn next_token(&mut self) -> Result<Option<Token>, Error> {
|
||||
if let Some(token) = self.buffer.pop_front() {
|
||||
self.history.push_back(token.clone());
|
||||
return Ok(Some(token));
|
||||
}
|
||||
|
||||
let token = self.tokenizer.next_token()?;
|
||||
if let Some(ref token) = token {
|
||||
self.history.push_back(token.clone());
|
||||
}
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
/// Peeks the next token in the stream without adding to the history stack
|
||||
pub fn peek(&mut self) -> Result<Option<Token>, Error> {
|
||||
if let Some(token) = self.buffer.front() {
|
||||
return Ok(Some(token.clone()));
|
||||
}
|
||||
|
||||
let token = self.tokenizer.peek_next()?;
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
fn seek_from_current(&mut self, seek_to: i64) -> Result<(), Error> {
|
||||
use Ordering::*;
|
||||
// if seek_to > 0 then we need to check if the buffer has enough tokens to pop, otherwise we need to read from the tokenizer
|
||||
// if seek_to < 0 then we need to pop from the history and push to the front of the buffer. If not enough, then we throw (we reached the front of the history)
|
||||
// if seek_to == 0 then we don't need to do anything
|
||||
|
||||
match seek_to.cmp(&0) {
|
||||
Greater => {
|
||||
let mut tokens = Vec::with_capacity(seek_to as usize);
|
||||
for _ in 0..seek_to {
|
||||
if let Some(token) = self.tokenizer.next_token()? {
|
||||
tokens.push(token);
|
||||
} else {
|
||||
return Err(Error::IOError(std::io::Error::new(
|
||||
std::io::ErrorKind::UnexpectedEof,
|
||||
"Unexpected EOF",
|
||||
)));
|
||||
}
|
||||
}
|
||||
self.history.extend(tokens);
|
||||
}
|
||||
Less => {
|
||||
let seek_to = seek_to.unsigned_abs() as usize;
|
||||
let mut tokens = Vec::with_capacity(seek_to);
|
||||
for _ in 0..seek_to {
|
||||
if let Some(token) = self.history.pop_back() {
|
||||
tokens.push(token);
|
||||
} else {
|
||||
return Err(Error::IOError(std::io::Error::new(
|
||||
std::io::ErrorKind::UnexpectedEof,
|
||||
"Unexpected EOF",
|
||||
)));
|
||||
}
|
||||
}
|
||||
self.buffer.extend(tokens.into_iter().rev());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds to or removes from the History stack, allowing the user to move back and forth in the stream
|
||||
pub fn seek(&mut self, from: SeekFrom) -> Result<(), Error> {
|
||||
match from {
|
||||
SeekFrom::Current(seek_to) => self.seek_from_current(seek_to)?,
|
||||
SeekFrom::End(_) => unimplemented!("SeekFrom::End will not be implemented"),
|
||||
SeekFrom::Start(_) => unimplemented!("SeekFrom::Start will not be implemented"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use anyhow::Result;
|
||||
use rust_decimal::Decimal;
|
||||
|
||||
const TEST_FILE: &str = "tests/file.stlg";
|
||||
|
||||
const TEST_STRING: &str = r#"
|
||||
fn test() {
|
||||
let x = 10;
|
||||
return x + 2;
|
||||
}
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn test_seek_from_current() -> Result<()> {
|
||||
let tokenizer = Tokenizer::from(TEST_STRING.to_owned());
|
||||
let mut buffer = TokenizerBuffer::new(tokenizer);
|
||||
|
||||
let token = buffer.next_token()?.unwrap();
|
||||
assert_eq!(token.token_type, TokenType::Keyword(Keyword::Fn));
|
||||
|
||||
buffer.seek(SeekFrom::Current(1))?;
|
||||
|
||||
let token = buffer.next_token()?.unwrap();
|
||||
|
||||
assert_eq!(token.token_type, TokenType::Symbol(Symbol::LParen));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tokenizer_from_path_ok() {
|
||||
let tokenizer = Tokenizer::from_path(TEST_FILE);
|
||||
assert!(tokenizer.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tokenizer_from_path_err() {
|
||||
let tokenizer = Tokenizer::from_path("non_existent_file.stlg");
|
||||
assert!(tokenizer.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_next_char() -> Result<()> {
|
||||
let mut tokenizer = Tokenizer::from(TEST_STRING.to_owned());
|
||||
|
||||
let char = tokenizer.next_char()?;
|
||||
|
||||
assert_eq!(char, Some('\n'));
|
||||
assert_eq!(tokenizer.line, 2);
|
||||
assert_eq!(tokenizer.column, 1);
|
||||
|
||||
let mut tokenizer = Tokenizer::from(String::from("fn"));
|
||||
|
||||
let char = tokenizer.next_char()?;
|
||||
|
||||
assert_eq!(char, Some('f'));
|
||||
assert_eq!(tokenizer.line, 1);
|
||||
assert_eq!(tokenizer.column, 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peek_next_char() -> Result<()> {
|
||||
let mut tokenizer = Tokenizer::from(TEST_STRING.to_owned());
|
||||
|
||||
let char = tokenizer.peek_next_char()?;
|
||||
|
||||
assert_eq!(char, Some('\n'));
|
||||
assert_eq!(tokenizer.line, 1);
|
||||
assert_eq!(tokenizer.column, 1);
|
||||
|
||||
let char = tokenizer.next_char()?;
|
||||
assert_eq!(char, Some('\n'));
|
||||
assert_eq!(tokenizer.line, 2);
|
||||
assert_eq!(tokenizer.column, 1);
|
||||
|
||||
let char = tokenizer.peek_next_char()?;
|
||||
assert_eq!(char, Some(' '));
|
||||
assert_eq!(tokenizer.line, 2);
|
||||
assert_eq!(tokenizer.column, 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_temperature_unit() -> Result<()> {
|
||||
let mut tokenizer = Tokenizer::from(String::from("10c 14f 10k"));
|
||||
|
||||
let token = tokenizer.next_token()?.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
token.token_type,
|
||||
TokenType::Number(Number::Decimal(Decimal::new(28315, 2)))
|
||||
);
|
||||
|
||||
let token = tokenizer.next_token()?.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
token.token_type,
|
||||
TokenType::Number(Number::Decimal(Decimal::new(26315, 2)))
|
||||
);
|
||||
|
||||
let token = tokenizer.next_token()?.unwrap();
|
||||
|
||||
assert_eq!(token.token_type, TokenType::Number(Number::Integer(10)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_integer() -> Result<()> {
|
||||
let mut tokenizer = Tokenizer::from(String::from("10"));
|
||||
|
||||
let token = tokenizer.next_token()?.unwrap();
|
||||
|
||||
assert_eq!(token.token_type, TokenType::Number(Number::Integer(10)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_integer_with_underscore() -> Result<()> {
|
||||
let mut tokenizer = Tokenizer::from(String::from("1_000"));
|
||||
|
||||
let token = tokenizer.next_token()?.unwrap();
|
||||
|
||||
assert_eq!(token.token_type, TokenType::Number(Number::Integer(1000)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_decimal() -> Result<()> {
|
||||
let mut tokenizer = Tokenizer::from(String::from("10.5"));
|
||||
|
||||
let token = tokenizer.next_token()?.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
token.token_type,
|
||||
TokenType::Number(Number::Decimal(Decimal::new(105, 1))) // 10.5
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_decimal_with_underscore() -> Result<()> {
|
||||
let mut tokenizer = Tokenizer::from(String::from("1_000.000_6"));
|
||||
|
||||
let token = tokenizer.next_token()?.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
token.token_type,
|
||||
TokenType::Number(Number::Decimal(Decimal::new(10000006, 4))) // 1000.0006
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_number_with_symbol() -> Result<()> {
|
||||
let mut tokenizer = Tokenizer::from(String::from("10;"));
|
||||
|
||||
let token = tokenizer.next_token()?.unwrap();
|
||||
|
||||
assert_eq!(token.token_type, TokenType::Number(Number::Integer(10)));
|
||||
|
||||
let next_char = tokenizer.next_char()?;
|
||||
|
||||
assert_eq!(next_char, Some(';'));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_parse() -> Result<()> {
|
||||
let mut tokenizer = Tokenizer::from(String::from(r#""Hello, World!""#));
|
||||
|
||||
let token = tokenizer.next_token()?.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
token.token_type,
|
||||
TokenType::String(String::from("Hello, World!"))
|
||||
);
|
||||
|
||||
let mut tokenizer = Tokenizer::from(String::from(r#"'Hello, World!'"#));
|
||||
|
||||
let token = tokenizer.next_token()?.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
token.token_type,
|
||||
TokenType::String(String::from("Hello, World!"))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_symbol_parse() -> Result<()> {
|
||||
let mut tokenizer = Tokenizer::from(String::from(
|
||||
"^ ! () [] {} , . ; : + - * / < > = != && || >= <=**%",
|
||||
));
|
||||
|
||||
let expected_tokens = vec![
|
||||
TokenType::Symbol(Symbol::Caret),
|
||||
TokenType::Symbol(Symbol::LogicalNot),
|
||||
TokenType::Symbol(Symbol::LParen),
|
||||
TokenType::Symbol(Symbol::RParen),
|
||||
TokenType::Symbol(Symbol::LBracket),
|
||||
TokenType::Symbol(Symbol::RBracket),
|
||||
TokenType::Symbol(Symbol::LBrace),
|
||||
TokenType::Symbol(Symbol::RBrace),
|
||||
TokenType::Symbol(Symbol::Comma),
|
||||
TokenType::Symbol(Symbol::Dot),
|
||||
TokenType::Symbol(Symbol::Semicolon),
|
||||
TokenType::Symbol(Symbol::Colon),
|
||||
TokenType::Symbol(Symbol::Plus),
|
||||
TokenType::Symbol(Symbol::Minus),
|
||||
TokenType::Symbol(Symbol::Asterisk),
|
||||
TokenType::Symbol(Symbol::Slash),
|
||||
TokenType::Symbol(Symbol::LessThan),
|
||||
TokenType::Symbol(Symbol::GreaterThan),
|
||||
TokenType::Symbol(Symbol::Assign),
|
||||
TokenType::Symbol(Symbol::NotEqual),
|
||||
TokenType::Symbol(Symbol::LogicalAnd),
|
||||
TokenType::Symbol(Symbol::LogicalOr),
|
||||
TokenType::Symbol(Symbol::GreaterThanOrEqual),
|
||||
TokenType::Symbol(Symbol::LessThanOrEqual),
|
||||
TokenType::Symbol(Symbol::Exp),
|
||||
TokenType::Symbol(Symbol::Percent),
|
||||
];
|
||||
|
||||
for expected_token in expected_tokens {
|
||||
let token = tokenizer.next_token()?.unwrap();
|
||||
|
||||
assert_eq!(token.token_type, expected_token);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keyword_parse() -> Result<()> {
|
||||
let mut tokenizer = Tokenizer::from(String::from("let fn if else return enum"));
|
||||
|
||||
let expected_tokens = vec![
|
||||
TokenType::Keyword(Keyword::Let),
|
||||
TokenType::Keyword(Keyword::Fn),
|
||||
TokenType::Keyword(Keyword::If),
|
||||
TokenType::Keyword(Keyword::Else),
|
||||
TokenType::Keyword(Keyword::Return),
|
||||
TokenType::Keyword(Keyword::Enum),
|
||||
];
|
||||
|
||||
for expected_token in expected_tokens {
|
||||
let token = tokenizer.next_token()?.unwrap();
|
||||
|
||||
assert_eq!(token.token_type, expected_token);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_identifier_parse() -> Result<()> {
|
||||
let mut tokenizer = Tokenizer::from(String::from("fn test"));
|
||||
|
||||
let token = tokenizer.next_token()?.unwrap();
|
||||
assert_eq!(token.token_type, TokenType::Keyword(Keyword::Fn));
|
||||
let token = tokenizer.next_token()?.unwrap();
|
||||
assert_eq!(
|
||||
token.token_type,
|
||||
TokenType::Identifier(String::from("test"))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boolean_parse() -> Result<()> {
|
||||
let mut tokenizer = Tokenizer::from(String::from("true false"));
|
||||
|
||||
let token = tokenizer.next_token()?.unwrap();
|
||||
assert_eq!(token.token_type, TokenType::Boolean(true));
|
||||
let token = tokenizer.next_token()?.unwrap();
|
||||
assert_eq!(token.token_type, TokenType::Boolean(false));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_full_source() -> Result<()> {
|
||||
let mut tokenizer = Tokenizer::from(TEST_STRING.to_owned());
|
||||
|
||||
let expected_tokens = vec![
|
||||
TokenType::Keyword(Keyword::Fn),
|
||||
TokenType::Identifier(String::from("test")),
|
||||
TokenType::Symbol(Symbol::LParen),
|
||||
TokenType::Symbol(Symbol::RParen),
|
||||
TokenType::Symbol(Symbol::LBrace),
|
||||
TokenType::Keyword(Keyword::Let),
|
||||
TokenType::Identifier(String::from("x")),
|
||||
TokenType::Symbol(Symbol::Assign),
|
||||
TokenType::Number(Number::Integer(10)),
|
||||
TokenType::Symbol(Symbol::Semicolon),
|
||||
TokenType::Keyword(Keyword::Return),
|
||||
TokenType::Identifier(String::from("x")),
|
||||
TokenType::Symbol(Symbol::Plus),
|
||||
TokenType::Number(Number::Integer(2)),
|
||||
TokenType::Symbol(Symbol::Semicolon),
|
||||
TokenType::Symbol(Symbol::RBrace),
|
||||
];
|
||||
|
||||
for expected_token in expected_tokens {
|
||||
let token = tokenizer.next_token()?.unwrap();
|
||||
|
||||
assert_eq!(token.token_type, expected_token);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peek_next() -> Result<()> {
|
||||
let mut tokenizer = Tokenizer::from(TEST_STRING.to_owned());
|
||||
|
||||
let column = tokenizer.column;
|
||||
let line = tokenizer.line;
|
||||
|
||||
let peeked_token = tokenizer.peek_next()?;
|
||||
|
||||
assert_eq!(
|
||||
peeked_token.unwrap().token_type,
|
||||
TokenType::Keyword(Keyword::Fn)
|
||||
);
|
||||
assert_eq!(tokenizer.column, column);
|
||||
assert_eq!(tokenizer.line, line);
|
||||
|
||||
let next_token = tokenizer.next_token()?;
|
||||
|
||||
assert_eq!(
|
||||
next_token.unwrap().token_type,
|
||||
TokenType::Keyword(Keyword::Fn)
|
||||
);
|
||||
assert_ne!(tokenizer.column, column);
|
||||
assert_ne!(tokenizer.line, line);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compact_syntax() -> Result<()> {
|
||||
let mut tokenizer = Tokenizer::from(String::from("if(true) while(false)"));
|
||||
|
||||
// if(true)
|
||||
assert_eq!(
|
||||
tokenizer.next_token()?.unwrap().token_type,
|
||||
TokenType::Keyword(Keyword::If)
|
||||
);
|
||||
assert_eq!(
|
||||
tokenizer.next_token()?.unwrap().token_type,
|
||||
TokenType::Symbol(Symbol::LParen)
|
||||
);
|
||||
assert_eq!(
|
||||
tokenizer.next_token()?.unwrap().token_type,
|
||||
TokenType::Boolean(true)
|
||||
);
|
||||
assert_eq!(
|
||||
tokenizer.next_token()?.unwrap().token_type,
|
||||
TokenType::Symbol(Symbol::RParen)
|
||||
);
|
||||
|
||||
// while(false)
|
||||
assert_eq!(
|
||||
tokenizer.next_token()?.unwrap().token_type,
|
||||
TokenType::Keyword(Keyword::While)
|
||||
);
|
||||
assert_eq!(
|
||||
tokenizer.next_token()?.unwrap().token_type,
|
||||
TokenType::Symbol(Symbol::LParen)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
235
rust_compiler/libs/tokenizer/src/token.rs
Normal file
235
rust_compiler/libs/tokenizer/src/token.rs
Normal file
@@ -0,0 +1,235 @@
|
||||
use rust_decimal::Decimal;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Token {
|
||||
/// The type of the token
|
||||
pub token_type: TokenType,
|
||||
/// The line where the token was found
|
||||
pub line: usize,
|
||||
/// The column where the token was found
|
||||
pub column: usize,
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub fn new(token_type: TokenType, line: usize, column: usize) -> Self {
|
||||
Self {
|
||||
token_type,
|
||||
line,
|
||||
column,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Hash, Eq, Clone)]
|
||||
pub enum Temperature {
|
||||
Celsius(Number),
|
||||
Fahrenheit(Number),
|
||||
Kelvin(Number),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Temperature {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Temperature::Celsius(n) => write!(f, "{}°C", n),
|
||||
Temperature::Fahrenheit(n) => write!(f, "{}°F", n),
|
||||
Temperature::Kelvin(n) => write!(f, "{}K", n),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Temperature {
|
||||
pub fn to_kelvin(self) -> Number {
|
||||
match self {
|
||||
Temperature::Celsius(n) => {
|
||||
let n = match n {
|
||||
Number::Integer(i) => Decimal::new(i as i64, 0),
|
||||
Number::Decimal(d) => d,
|
||||
};
|
||||
Number::Decimal(n + Decimal::new(27315, 2))
|
||||
}
|
||||
Temperature::Fahrenheit(n) => {
|
||||
let n = match n {
|
||||
Number::Integer(i) => Decimal::new(i as i64, 0),
|
||||
Number::Decimal(d) => d,
|
||||
};
|
||||
|
||||
let a = n - Decimal::new(32, 0);
|
||||
let b = Decimal::new(5, 0) / Decimal::new(9, 0);
|
||||
Number::Decimal(a * b + Decimal::new(27315, 2))
|
||||
}
|
||||
Temperature::Kelvin(n) => n,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Hash, Eq, Clone)]
|
||||
pub enum TokenType {
|
||||
/// Represents a string token
|
||||
String(String),
|
||||
/// Represents a number token
|
||||
Number(Number),
|
||||
/// Represents a boolean token
|
||||
Boolean(bool),
|
||||
/// Represents a keyword token
|
||||
Keyword(Keyword),
|
||||
/// Represents an identifier token
|
||||
Identifier(String),
|
||||
/// Represents a symbol token
|
||||
Symbol(Symbol),
|
||||
/// Represents an end of file token
|
||||
EOF,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for TokenType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
TokenType::String(s) => write!(f, "{}", s),
|
||||
TokenType::Number(n) => write!(f, "{}", n),
|
||||
TokenType::Boolean(b) => write!(f, "{}", b),
|
||||
TokenType::Keyword(k) => write!(f, "{:?}", k),
|
||||
TokenType::Identifier(i) => write!(f, "{}", i),
|
||||
TokenType::Symbol(s) => write!(f, "{:?}", s),
|
||||
TokenType::EOF => write!(f, "EOF"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Hash, Eq, Clone, Copy)]
|
||||
pub enum Number {
|
||||
/// Represents an integer number
|
||||
Integer(u128),
|
||||
/// Represents a decimal type number with a precision of 64 bits
|
||||
Decimal(Decimal),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Number {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Number::Integer(i) => write!(f, "{}", i),
|
||||
Number::Decimal(d) => write!(f, "{}", d),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Number> for String {
|
||||
fn from(value: Number) -> Self {
|
||||
value.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Hash, Eq, Clone, Copy)]
|
||||
pub enum Symbol {
|
||||
// Single Character Symbols
|
||||
/// Represents the `(` symbol
|
||||
LParen,
|
||||
/// Represents the `)` symbol
|
||||
RParen,
|
||||
/// Represents the `{` symbol
|
||||
LBrace,
|
||||
/// Represents the `}` symbol
|
||||
RBrace,
|
||||
/// Represents the `[` symbol
|
||||
LBracket,
|
||||
/// Represents the `]` symbol
|
||||
RBracket,
|
||||
/// Represents the `;` symbol
|
||||
Semicolon,
|
||||
/// Represents the `:` symbol
|
||||
Colon,
|
||||
/// Represents the `,` symbol
|
||||
Comma,
|
||||
/// Represents the `+` symbol
|
||||
Plus,
|
||||
/// Represents the `-` symbol
|
||||
Minus,
|
||||
/// Represents the `*` symbol
|
||||
Asterisk,
|
||||
/// Represents the `/` symbol
|
||||
Slash,
|
||||
/// Represents the `<` symbol
|
||||
LessThan,
|
||||
/// Represents the `>` symbol
|
||||
GreaterThan,
|
||||
/// Represents the `=` symbol
|
||||
Assign,
|
||||
/// Represents the `!` symbol
|
||||
LogicalNot,
|
||||
/// Represents the `.` symbol
|
||||
Dot,
|
||||
/// Represents the `^` symbol
|
||||
Caret,
|
||||
/// Represents the `%` symbol
|
||||
Percent,
|
||||
|
||||
// Double Character Symbols
|
||||
/// Represents the `==` symbol
|
||||
Equal,
|
||||
/// Represents the `!=` symbol
|
||||
NotEqual,
|
||||
/// Represents the `&&` Symbol
|
||||
LogicalAnd,
|
||||
// Represents the `||` Symbol
|
||||
LogicalOr,
|
||||
/// Represents the `<=` symbol
|
||||
LessThanOrEqual,
|
||||
/// Represents the `>=` symbol
|
||||
GreaterThanOrEqual,
|
||||
/// Represents the `**` symbol
|
||||
Exp,
|
||||
}
|
||||
|
||||
impl Symbol {
|
||||
pub fn is_operator(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Symbol::Plus
|
||||
| Symbol::Minus
|
||||
| Symbol::Asterisk
|
||||
| Symbol::Slash
|
||||
| Symbol::Exp
|
||||
| Symbol::Percent
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_comparison(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Symbol::LessThan
|
||||
| Symbol::GreaterThan
|
||||
| Symbol::Equal
|
||||
| Symbol::NotEqual
|
||||
| Symbol::LessThanOrEqual
|
||||
| Symbol::GreaterThanOrEqual,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_logical(&self) -> bool {
|
||||
matches!(self, Symbol::LogicalAnd | Symbol::LogicalOr)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Hash, Eq, Clone, Copy)]
|
||||
pub enum Keyword {
|
||||
/// Represents the `continue` keyword
|
||||
Continue,
|
||||
/// Represents the `let` keyword
|
||||
Let,
|
||||
/// Represents the `fn` keyword
|
||||
Fn,
|
||||
/// Represents the `if` keyword
|
||||
If,
|
||||
/// Represents the `device` keyword. Useful for defining a device at a specific address (ex. d0, d1, d2, etc.)
|
||||
Device,
|
||||
/// Represents the `else` keyword
|
||||
Else,
|
||||
/// Represents the `return` keyword
|
||||
Return,
|
||||
/// Represents the `enum` keyword
|
||||
Enum,
|
||||
/// Represents the `loop` keyword
|
||||
Loop,
|
||||
/// Represents the `break` keyword
|
||||
Break,
|
||||
/// Represents the `while` keyword
|
||||
While,
|
||||
}
|
||||
9
rust_compiler/libs/tokenizer/tests/file.stlg
Normal file
9
rust_compiler/libs/tokenizer/tests/file.stlg
Normal file
@@ -0,0 +1,9 @@
|
||||
device self = "db";
|
||||
device airConditioner = "d1";
|
||||
device roomTemperatureSensor = "d2";
|
||||
|
||||
let roomTemperatureMin = 20c;
|
||||
let roomTemperatureMax = 30c;
|
||||
|
||||
|
||||
let averageTemperature = (roomTemperatureMax + roomTemperatureMin) / 2;
|
||||
3
rust_compiler/rust-toolchain.toml
Normal file
3
rust_compiler/rust-toolchain.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[toolchain]
|
||||
channel = "stable"
|
||||
components = ["rustfmt", "clippy"]
|
||||
58
rust_compiler/src/lib.rs
Normal file
58
rust_compiler/src/lib.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use compiler::Compiler;
|
||||
use parser::Parser;
|
||||
use std::{
|
||||
ffi::{CStr, CString},
|
||||
io::BufWriter,
|
||||
};
|
||||
use tokenizer::Tokenizer;
|
||||
|
||||
/// Takes a raw pointer to a string and compiles the `slang` code into valid IC10
|
||||
/// # Safety
|
||||
/// This must be called with a valid string pointer from C# (or wherever is calling this function)
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn compile_from_string(
|
||||
input_ptr: *const std::os::raw::c_char,
|
||||
) -> *mut std::os::raw::c_char {
|
||||
if input_ptr.is_null() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
let c_str = unsafe { CStr::from_ptr(input_ptr) };
|
||||
|
||||
let Ok(input_str) = c_str.to_str() else {
|
||||
return std::ptr::null_mut();
|
||||
};
|
||||
|
||||
let mut writer = BufWriter::new(Vec::new());
|
||||
let tokenizer = Tokenizer::from(input_str);
|
||||
let parser = Parser::new(tokenizer);
|
||||
|
||||
let compiler = Compiler::new(parser, &mut writer, None);
|
||||
|
||||
let Ok(()) = compiler.compile() else {
|
||||
return std::ptr::null_mut();
|
||||
};
|
||||
|
||||
let Ok(buffer) = writer.into_inner() else {
|
||||
return std::ptr::null_mut();
|
||||
};
|
||||
|
||||
let c_string = CString::from_vec_unchecked(buffer);
|
||||
|
||||
c_string.into_raw()
|
||||
}
|
||||
|
||||
/// Takes ownership of the string pointer and drops it, freeing the memory
|
||||
/// # Safety
|
||||
/// Must be called with a valid string pointer
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn free_slang_string(input_ptr: *mut std::os::raw::c_char) {
|
||||
if input_ptr.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
// Takes ownership of the input string, and then drops it immediately
|
||||
let _ = CString::from_raw(input_ptr);
|
||||
}
|
||||
}
|
||||
86
rust_compiler/src/main.rs
Normal file
86
rust_compiler/src/main.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
#[macro_use]
|
||||
extern crate quick_error;
|
||||
|
||||
use clap::Parser;
|
||||
use compiler::Compiler;
|
||||
use parser::Parser as ASTParser;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufWriter, Read, Write},
|
||||
path::PathBuf,
|
||||
};
|
||||
use tokenizer::{self, Tokenizer};
|
||||
|
||||
quick_error! {
|
||||
#[derive(Debug)]
|
||||
enum StationlangError {
|
||||
TokenizerError(err: tokenizer::Error) {
|
||||
from()
|
||||
display("Tokenizer error: {}", err)
|
||||
}
|
||||
ParserError(err: parser::Error) {
|
||||
from()
|
||||
display("Parser error: {}", err)
|
||||
}
|
||||
CompileError(err: compiler::Error) {
|
||||
from()
|
||||
display("Compile error: {}", err)
|
||||
}
|
||||
IoError(err: std::io::Error) {
|
||||
from()
|
||||
display("IO error: {}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// What file should be compiled. If not set, input will be read from stdin.
|
||||
#[arg(short, long)]
|
||||
input_file: Option<PathBuf>,
|
||||
/// The output file for the compiled program. If not set, output will go to stdout.
|
||||
#[arg(short, long)]
|
||||
output_file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn run_logic() -> Result<(), StationlangError> {
|
||||
let args = Args::parse();
|
||||
let input_file = args.input_file;
|
||||
|
||||
let tokenizer: Tokenizer = match input_file {
|
||||
Some(input_file) => Tokenizer::from_path(&input_file)?,
|
||||
None => {
|
||||
let mut buf = String::new();
|
||||
let stdin = std::io::stdin();
|
||||
|
||||
let read_result = stdin.lock().read_to_string(&mut buf)?;
|
||||
|
||||
if read_result == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Tokenizer::from(buf)
|
||||
}
|
||||
};
|
||||
|
||||
let parser = ASTParser::new(tokenizer);
|
||||
|
||||
let mut writer: BufWriter<Box<dyn Write>> = match args.output_file {
|
||||
Some(output_file) => BufWriter::new(Box::new(File::create(output_file)?)),
|
||||
None => BufWriter::new(Box::new(std::io::stdout())),
|
||||
};
|
||||
|
||||
let compiler = Compiler::new(parser, &mut writer, None);
|
||||
|
||||
compiler.compile()?;
|
||||
writer.flush()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<(), StationlangError> {
|
||||
run_logic()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user