├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .vscode └── settings.json ├── Cargo.lock ├── Cargo.toml ├── README.md ├── examples ├── array.db ├── fizzbuzz.db ├── linked_list.db ├── objects.db └── std.db └── src ├── interpreter.rs ├── lexer.rs ├── main.rs ├── parser ├── grouping.rs └── mod.rs ├── tests.rs └── types ├── mod.rs ├── pointer.rs ├── state.rs ├── syntax.rs ├── token.rs └── value.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /history -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "highlight.regexFlags": "gi", 3 | "highlight.regexes": { 4 | "const (const|var) +(var|(?!\\1)const)": { 5 | "filterFileRegex": ".*\\.db", 6 | "decorations": [ 7 | { 8 | "backgroundColor": "#ff4346", // this is to highlight an invalid usage of const const const 9 | "color": "#1f1f1f" 10 | }, 11 | {} 12 | ] 13 | }, 14 | "(const|var)(\\s+)(const|var)(\\s+)([^ +\\-\\*\\/<>=\\(\\)\\[\\]!;:\\.{}\n,]+)(:?\\s*)(([\\w<>\\[\\],]+?))?( *)([+\\-\\/*]?)(= *)([^!\n?]+)": { 15 | "filterFileRegex": ".*\\.db", 16 | "decorations": [ 17 | { 18 | "color": "#6680cc" // first const 19 | }, 20 | {}, 21 | { 22 | "color": "#6680cc" // second const 23 | }, 24 | {}, 25 | { 26 | "color": "#ff8046" // variable name 27 | }, 28 | {}, 29 | { 30 | "color": "#ffa0de" //type 31 | } 32 | ] 33 | }, 34 | "({)([^}]*)(})(€|円|₽)": { 35 | "filterFileRegex": ".*\\.db", 36 | "decorations": [ 37 | { 38 | "color": "#6680cc" 39 | }, 40 | { 41 | "color": "#ffffff" 42 | }, 43 | { 44 | "color": "#6680cc" 45 | }, 46 | { 47 | "color": "#dccc66" 48 | } 49 | ] 50 | }, 51 | "(\\$|£|¥)({)([^}]*)(})": { 52 | "filterFileRegex": ".*\\.db", 53 | "decorations": [ 54 | { 55 | "color": "#dccc66" 56 | }, 57 | { 58 | "color": "#6680cc" 59 | }, 60 | { 61 | "color": "#ffffff" 62 | }, 63 | { 64 | "color": "#6680cc" 65 | } 66 | ] 67 | }, 68 | "([\"'`])(.*)(\\1)": { 69 | "filterFileRegex": ".*\\.db", 70 | "decorations": [ 71 | { 72 | "color": "#228840" 73 | }, 74 | { 75 | "color": "#228840" 76 | }, 77 | { 78 | "color": "#228840" 79 | } 80 | ] 81 | }, 82 | "(„)(.*)(“)": { 83 | "filterFileRegex": ".*\\..db", 84 | "decorations": [ 85 | { "color": "#228840" }, 86 | { "color": "#228840" }, 87 | { "color": "#228840" } 88 | ] 89 | }, 90 | "(»)(.*)(«)": { 91 | "filterFileRegex": ".*\\..db", 92 | "decorations": [ 93 | { "color": "#228840" }, 94 | { "color": "#228840" }, 95 | { "color": "#228840" } 96 | ] 97 | }, 98 | "(«)(.*)(»)": { 99 | "filterFileRegex": ".*\\..db", 100 | "decorations": [ 101 | { "color": "#228840" }, 102 | { "color": "#228840" }, 103 | { "color": "#228840" } 104 | ] 105 | }, 106 | "([^ +\\-\\*\\/<>=\\(\\)\\[\\]!;:\\.{}\n,\"]+)(:\\s*)([\\w<>\\[\\],]+)(,|\\))": { 107 | "filterFileRegex": ".*\\.db", 108 | "decorations": [{ "color": "#ff8046" }, {}, { "color": "#ffa0de" }] 109 | }, 110 | "([^ +\\-\\*\\/<>=\\(\\)\\[\\]!;:\\.{}\n,]+)(\\.)([^ +\\-\\*\\/<>=\\(\\)\\[\\]!;:\\.{}\n,]+)\\s*": { 111 | "filterFileRegex": ".*\\.db", 112 | "decorations": [ 113 | { 114 | "color": "#6680cc" // object name 115 | }, 116 | {}, 117 | { 118 | "color": "#66cc80" // variable name 119 | } 120 | ] 121 | }, 122 | "\\b(true|false|maybe|undefined|self|if|delete|eval)\\b": { 123 | "filterFileRegex": ".*\\.db", 124 | "decorations": [ 125 | { 126 | "color": "#eeaa46" 127 | } 128 | ] 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anstream" 16 | version = "0.3.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" 19 | dependencies = [ 20 | "anstyle", 21 | "anstyle-parse", 22 | "anstyle-query", 23 | "anstyle-wincon", 24 | "colorchoice", 25 | "is-terminal", 26 | "utf8parse", 27 | ] 28 | 29 | [[package]] 30 | name = "anstyle" 31 | version = "1.0.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" 34 | 35 | [[package]] 36 | name = "anstyle-parse" 37 | version = "0.2.1" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" 40 | dependencies = [ 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-query" 46 | version = "1.0.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" 49 | dependencies = [ 50 | "windows-sys 0.48.0", 51 | ] 52 | 53 | [[package]] 54 | name = "anstyle-wincon" 55 | version = "1.0.1" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" 58 | dependencies = [ 59 | "anstyle", 60 | "windows-sys 0.48.0", 61 | ] 62 | 63 | [[package]] 64 | name = "bitflags" 65 | version = "1.3.2" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 68 | 69 | [[package]] 70 | name = "bitflags" 71 | version = "2.6.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 74 | 75 | [[package]] 76 | name = "cfg-if" 77 | version = "1.0.0" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 80 | 81 | [[package]] 82 | name = "cfg_aliases" 83 | version = "0.1.1" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" 86 | 87 | [[package]] 88 | name = "clap" 89 | version = "4.3.8" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "d9394150f5b4273a1763355bd1c2ec54cc5a2593f790587bcd6b2c947cfa9211" 92 | dependencies = [ 93 | "clap_builder", 94 | "clap_derive", 95 | "once_cell", 96 | ] 97 | 98 | [[package]] 99 | name = "clap_builder" 100 | version = "4.3.8" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "9a78fbdd3cc2914ddf37ba444114bc7765bbdcb55ec9cbe6fa054f0137400717" 103 | dependencies = [ 104 | "anstream", 105 | "anstyle", 106 | "bitflags 1.3.2", 107 | "clap_lex", 108 | "strsim", 109 | ] 110 | 111 | [[package]] 112 | name = "clap_derive" 113 | version = "4.3.2" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" 116 | dependencies = [ 117 | "heck", 118 | "proc-macro2", 119 | "quote", 120 | "syn 2.0.22", 121 | ] 122 | 123 | [[package]] 124 | name = "clap_lex" 125 | version = "0.5.0" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" 128 | 129 | [[package]] 130 | name = "clipboard-win" 131 | version = "5.3.1" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "79f4473f5144e20d9aceaf2972478f06ddf687831eafeeb434fbaf0acc4144ad" 134 | dependencies = [ 135 | "error-code", 136 | ] 137 | 138 | [[package]] 139 | name = "colorchoice" 140 | version = "1.0.0" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 143 | 144 | [[package]] 145 | name = "console" 146 | version = "0.15.8" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" 149 | dependencies = [ 150 | "encode_unicode", 151 | "lazy_static", 152 | "libc", 153 | "unicode-width", 154 | "windows-sys 0.52.0", 155 | ] 156 | 157 | [[package]] 158 | name = "dialoguer" 159 | version = "0.11.0" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" 162 | dependencies = [ 163 | "console", 164 | "shell-words", 165 | "tempfile", 166 | "thiserror", 167 | "zeroize", 168 | ] 169 | 170 | [[package]] 171 | name = "dreamberd-rs" 172 | version = "0.1.0" 173 | dependencies = [ 174 | "clap", 175 | "dialoguer", 176 | "lazy-regex", 177 | "rustyline", 178 | ] 179 | 180 | [[package]] 181 | name = "encode_unicode" 182 | version = "0.3.6" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 185 | 186 | [[package]] 187 | name = "endian-type" 188 | version = "0.1.2" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" 191 | 192 | [[package]] 193 | name = "errno" 194 | version = "0.3.9" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 197 | dependencies = [ 198 | "libc", 199 | "windows-sys 0.52.0", 200 | ] 201 | 202 | [[package]] 203 | name = "error-code" 204 | version = "3.2.0" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" 207 | 208 | [[package]] 209 | name = "fastrand" 210 | version = "2.1.0" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" 213 | 214 | [[package]] 215 | name = "fd-lock" 216 | version = "4.0.2" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" 219 | dependencies = [ 220 | "cfg-if", 221 | "rustix 0.38.34", 222 | "windows-sys 0.52.0", 223 | ] 224 | 225 | [[package]] 226 | name = "heck" 227 | version = "0.4.1" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 230 | 231 | [[package]] 232 | name = "hermit-abi" 233 | version = "0.3.1" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" 236 | 237 | [[package]] 238 | name = "home" 239 | version = "0.5.9" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" 242 | dependencies = [ 243 | "windows-sys 0.52.0", 244 | ] 245 | 246 | [[package]] 247 | name = "io-lifetimes" 248 | version = "1.0.11" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" 251 | dependencies = [ 252 | "hermit-abi", 253 | "libc", 254 | "windows-sys 0.48.0", 255 | ] 256 | 257 | [[package]] 258 | name = "is-terminal" 259 | version = "0.4.7" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" 262 | dependencies = [ 263 | "hermit-abi", 264 | "io-lifetimes", 265 | "rustix 0.37.27", 266 | "windows-sys 0.48.0", 267 | ] 268 | 269 | [[package]] 270 | name = "lazy-regex" 271 | version = "2.5.0" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "ff63c423c68ea6814b7da9e88ce585f793c87ddd9e78f646970891769c8235d4" 274 | dependencies = [ 275 | "lazy-regex-proc_macros", 276 | "once_cell", 277 | "regex", 278 | ] 279 | 280 | [[package]] 281 | name = "lazy-regex-proc_macros" 282 | version = "2.4.1" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "8edfc11b8f56ce85e207e62ea21557cfa09bb24a8f6b04ae181b086ff8611c22" 285 | dependencies = [ 286 | "proc-macro2", 287 | "quote", 288 | "regex", 289 | "syn 1.0.109", 290 | ] 291 | 292 | [[package]] 293 | name = "lazy_static" 294 | version = "1.5.0" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 297 | 298 | [[package]] 299 | name = "libc" 300 | version = "0.2.155" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 303 | 304 | [[package]] 305 | name = "linux-raw-sys" 306 | version = "0.3.8" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" 309 | 310 | [[package]] 311 | name = "linux-raw-sys" 312 | version = "0.4.14" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 315 | 316 | [[package]] 317 | name = "log" 318 | version = "0.4.22" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 321 | 322 | [[package]] 323 | name = "memchr" 324 | version = "2.5.0" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 327 | 328 | [[package]] 329 | name = "nibble_vec" 330 | version = "0.1.0" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" 333 | dependencies = [ 334 | "smallvec", 335 | ] 336 | 337 | [[package]] 338 | name = "nix" 339 | version = "0.28.0" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" 342 | dependencies = [ 343 | "bitflags 2.6.0", 344 | "cfg-if", 345 | "cfg_aliases", 346 | "libc", 347 | ] 348 | 349 | [[package]] 350 | name = "once_cell" 351 | version = "1.18.0" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 354 | 355 | [[package]] 356 | name = "proc-macro2" 357 | version = "1.0.63" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" 360 | dependencies = [ 361 | "unicode-ident", 362 | ] 363 | 364 | [[package]] 365 | name = "quote" 366 | version = "1.0.28" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" 369 | dependencies = [ 370 | "proc-macro2", 371 | ] 372 | 373 | [[package]] 374 | name = "radix_trie" 375 | version = "0.2.1" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" 378 | dependencies = [ 379 | "endian-type", 380 | "nibble_vec", 381 | ] 382 | 383 | [[package]] 384 | name = "regex" 385 | version = "1.8.4" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" 388 | dependencies = [ 389 | "aho-corasick", 390 | "memchr", 391 | "regex-syntax", 392 | ] 393 | 394 | [[package]] 395 | name = "regex-syntax" 396 | version = "0.7.2" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" 399 | 400 | [[package]] 401 | name = "rustix" 402 | version = "0.37.27" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" 405 | dependencies = [ 406 | "bitflags 1.3.2", 407 | "errno", 408 | "io-lifetimes", 409 | "libc", 410 | "linux-raw-sys 0.3.8", 411 | "windows-sys 0.48.0", 412 | ] 413 | 414 | [[package]] 415 | name = "rustix" 416 | version = "0.38.34" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" 419 | dependencies = [ 420 | "bitflags 2.6.0", 421 | "errno", 422 | "libc", 423 | "linux-raw-sys 0.4.14", 424 | "windows-sys 0.52.0", 425 | ] 426 | 427 | [[package]] 428 | name = "rustyline" 429 | version = "14.0.0" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "7803e8936da37efd9b6d4478277f4b2b9bb5cdb37a113e8d63222e58da647e63" 432 | dependencies = [ 433 | "bitflags 2.6.0", 434 | "cfg-if", 435 | "clipboard-win", 436 | "fd-lock", 437 | "home", 438 | "libc", 439 | "log", 440 | "memchr", 441 | "nix", 442 | "radix_trie", 443 | "unicode-segmentation", 444 | "unicode-width", 445 | "utf8parse", 446 | "windows-sys 0.52.0", 447 | ] 448 | 449 | [[package]] 450 | name = "shell-words" 451 | version = "1.1.0" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" 454 | 455 | [[package]] 456 | name = "smallvec" 457 | version = "1.13.2" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 460 | 461 | [[package]] 462 | name = "strsim" 463 | version = "0.10.0" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 466 | 467 | [[package]] 468 | name = "syn" 469 | version = "1.0.109" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 472 | dependencies = [ 473 | "proc-macro2", 474 | "quote", 475 | "unicode-ident", 476 | ] 477 | 478 | [[package]] 479 | name = "syn" 480 | version = "2.0.22" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "2efbeae7acf4eabd6bcdcbd11c92f45231ddda7539edc7806bd1a04a03b24616" 483 | dependencies = [ 484 | "proc-macro2", 485 | "quote", 486 | "unicode-ident", 487 | ] 488 | 489 | [[package]] 490 | name = "tempfile" 491 | version = "3.10.1" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" 494 | dependencies = [ 495 | "cfg-if", 496 | "fastrand", 497 | "rustix 0.38.34", 498 | "windows-sys 0.52.0", 499 | ] 500 | 501 | [[package]] 502 | name = "thiserror" 503 | version = "1.0.40" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" 506 | dependencies = [ 507 | "thiserror-impl", 508 | ] 509 | 510 | [[package]] 511 | name = "thiserror-impl" 512 | version = "1.0.40" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" 515 | dependencies = [ 516 | "proc-macro2", 517 | "quote", 518 | "syn 2.0.22", 519 | ] 520 | 521 | [[package]] 522 | name = "unicode-ident" 523 | version = "1.0.9" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" 526 | 527 | [[package]] 528 | name = "unicode-segmentation" 529 | version = "1.11.0" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" 532 | 533 | [[package]] 534 | name = "unicode-width" 535 | version = "0.1.13" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" 538 | 539 | [[package]] 540 | name = "utf8parse" 541 | version = "0.2.1" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 544 | 545 | [[package]] 546 | name = "windows-sys" 547 | version = "0.48.0" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 550 | dependencies = [ 551 | "windows-targets 0.48.0", 552 | ] 553 | 554 | [[package]] 555 | name = "windows-sys" 556 | version = "0.52.0" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 559 | dependencies = [ 560 | "windows-targets 0.52.5", 561 | ] 562 | 563 | [[package]] 564 | name = "windows-targets" 565 | version = "0.48.0" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" 568 | dependencies = [ 569 | "windows_aarch64_gnullvm 0.48.0", 570 | "windows_aarch64_msvc 0.48.0", 571 | "windows_i686_gnu 0.48.0", 572 | "windows_i686_msvc 0.48.0", 573 | "windows_x86_64_gnu 0.48.0", 574 | "windows_x86_64_gnullvm 0.48.0", 575 | "windows_x86_64_msvc 0.48.0", 576 | ] 577 | 578 | [[package]] 579 | name = "windows-targets" 580 | version = "0.52.5" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 583 | dependencies = [ 584 | "windows_aarch64_gnullvm 0.52.5", 585 | "windows_aarch64_msvc 0.52.5", 586 | "windows_i686_gnu 0.52.5", 587 | "windows_i686_gnullvm", 588 | "windows_i686_msvc 0.52.5", 589 | "windows_x86_64_gnu 0.52.5", 590 | "windows_x86_64_gnullvm 0.52.5", 591 | "windows_x86_64_msvc 0.52.5", 592 | ] 593 | 594 | [[package]] 595 | name = "windows_aarch64_gnullvm" 596 | version = "0.48.0" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 599 | 600 | [[package]] 601 | name = "windows_aarch64_gnullvm" 602 | version = "0.52.5" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 605 | 606 | [[package]] 607 | name = "windows_aarch64_msvc" 608 | version = "0.48.0" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 611 | 612 | [[package]] 613 | name = "windows_aarch64_msvc" 614 | version = "0.52.5" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 617 | 618 | [[package]] 619 | name = "windows_i686_gnu" 620 | version = "0.48.0" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 623 | 624 | [[package]] 625 | name = "windows_i686_gnu" 626 | version = "0.52.5" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 629 | 630 | [[package]] 631 | name = "windows_i686_gnullvm" 632 | version = "0.52.5" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 635 | 636 | [[package]] 637 | name = "windows_i686_msvc" 638 | version = "0.48.0" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 641 | 642 | [[package]] 643 | name = "windows_i686_msvc" 644 | version = "0.52.5" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 647 | 648 | [[package]] 649 | name = "windows_x86_64_gnu" 650 | version = "0.48.0" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 653 | 654 | [[package]] 655 | name = "windows_x86_64_gnu" 656 | version = "0.52.5" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 659 | 660 | [[package]] 661 | name = "windows_x86_64_gnullvm" 662 | version = "0.48.0" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 665 | 666 | [[package]] 667 | name = "windows_x86_64_gnullvm" 668 | version = "0.52.5" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 671 | 672 | [[package]] 673 | name = "windows_x86_64_msvc" 674 | version = "0.48.0" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 677 | 678 | [[package]] 679 | name = "windows_x86_64_msvc" 680 | version = "0.52.5" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 683 | 684 | [[package]] 685 | name = "zeroize" 686 | version = "1.8.1" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 689 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dreamberd-rs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clap = { version = "4.3.8", features = ["derive"] } 10 | lazy-regex = "2.5.0" 11 | rustyline = "14.0.0" 12 | dialoguer = "0.11.0" 13 | 14 | [profile.release] 15 | opt-level = "z" 16 | strip = true # Automatically strip symbols from the binary. 17 | lto = true -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DreamBerd rs 2 | 3 | Rust-based interpreter for the DreamBerd language. 4 | 5 | The full specification for DreamBerd is available at https://github.com/TodePond/DreamBerd. This file only contains the segments of the specification that are implemented in DreamBerd-rs, along with some of my own additions. 6 | 7 | ## Rapidly-Evaporating Program Logic (REPL) 8 | 9 | DreamBerd provides a convenient mode to execute code within the terminal. The preservation of state allows you to play with features of DreamBerd without writing your code into a file. You can also include code from a file by providing the filename as an argument, which will run it and allow you to play around in what remains. 10 | 11 | ## Statements 12 | 13 | Every statement ends with an exclamation mark! If you're feeling extra, you can even use multiple!!! 14 | 15 | ```c 16 | print "Hello World!"! 17 | 18 | print "Hi!!"!!!! 19 | ``` 20 | 21 | If you're unsure, that's okay too! You can also use question marks? This will print debug information to the console? The more question marks, the more detailed the information??? 22 | 23 | ```c 24 | print "uh... hi??"??? 25 | ``` 26 | 27 | ## Negation 28 | 29 | You might be wondering what DreamBerd uses for its negation operator, since most languages use `!`. Don't worry! `;` can negate the value in front of it. 30 | 31 | ```c 32 | ;"hello there"? // "ereht olleh" 33 | ;true? // false 34 | ;1 // -1 35 | ``` 36 | 37 | ## Declarations 38 | 39 | There are four types of declarations. Constant constants can't be changed at all. 40 | 41 | ```c 42 | const const name = "Ava"! 43 | name += "?"! // does nothing 44 | name = "John"! // does nothing 45 | ``` 46 | 47 | Constant variables can be edited but not reassigned. 48 | 49 | ```c 50 | const var age = 1! 51 | age += 1! 52 | age? // 2 53 | ``` 54 | 55 | Variable constants can be reassigned but not edited. 56 | 57 | ```c 58 | var const id = "main"! 59 | id = "no thank you"! 60 | id? // "no thank you" 61 | ``` 62 | 63 | Variable variables can be reassigned and edited. 64 | 65 | ```c 66 | var var count = 0! 67 | count += 1! 68 | count = 2! 69 | ``` 70 | 71 | ### Types 72 | 73 | DreamBerd is a weakly-typed language. However, type annotations can be added to declarations and functions. 74 | 75 | ```c 76 | var const name: String = "Gary"! 77 | const var age: i32 = 22! 78 | 79 | const const mul: Fn = (lhs: i32, rhs: i32)->{ 80 | lhs * rhs 81 | }! 82 | ``` 83 | 84 | > ##### Technical Info 85 | > 86 | > Type annotations don't actually do anything, but they help people feel more comfortable 87 | 88 | ## Naming 89 | 90 | Both variables and constants can be named with any Unicode character or string that isn't interpreted as another feature. 91 | 92 | ```c 93 | const const firstAlphabetLetter = 'A'! 94 | var const 👍 = true! 95 | var var 1️⃣ = 1! 96 | ``` 97 | 98 | This includes numbers, and other language constructs. 99 | 100 | ```c 101 | const const 5 = 4! 102 | const const true = false! 103 | 2 + 2 ==== 5? // true 104 | true ==== false? // true 105 | ``` 106 | 107 | ## Booleans 108 | 109 | Booleans can be `true`, `false`, or `maybe`, as current events have shown that reducing complex facts to simple dichotomies can unhelpfully flatten nuance. All values in DreamBerd are thus either truthy, falsey, or maybeyey. 110 | 111 | Numbers greater than or equal to one, non-empty strings, non-empty objects, and `true` are truthey. 112 | 113 | Numbers less than or equal to zero, empty strings, empty objects, undefined, and `false` are falsey. 114 | 115 | Numbers between 0 and 1, numbers that are not a number, keywords, functions, and `maybe` are maybeyey. 116 | 117 | ## Control Flow 118 | 119 | DreamBerd has a simple `if` statement: 120 | 121 | ```c 122 | if(true ===== false, 123 | print "true is false", 124 | print "true is not false", 125 | print "true might be false" 126 | )! 127 | ``` 128 | 129 | Notice, the if statement includes a section for if the discriminating variable is `maybe`. 130 | 131 | This is actually a function, so it can be assigned: 132 | 133 | ```c 134 | const const the_if_statement = if! 135 | 136 | the_if_statement(true ==== false, print "true is false")! 137 | ``` 138 | 139 | ## Strings 140 | 141 | Strings can be declared with backticks, single quotes, double quotes, zero quotes, or even french, austrian, or german quotes! 142 | 143 | ```c 144 | const const name: String = `Jeremy`! 145 | const const name: String = 'Lu'! 146 | const const name: String = "Luke"! 147 | const const name: String = L! 148 | const const nom: Chaîne = «Antoine»! 149 | const const név: Húr = »Lorenz«! 150 | const const name: Zeichenfolge = „Karl“! 151 | ``` 152 | 153 | ### String Interpolation 154 | 155 | Please remember to use your regional currency when interpolating strings. 156 | 157 | ```c 158 | const const name: String = "world"! 159 | print "Hello ${name}!"! 160 | print "Hello £{name}!"! 161 | print "Hello ¥{name}!"! 162 | ``` 163 | 164 | And make sure to follow your local typographical norms 165 | 166 | ```c 167 | print "Hello {name}€"! 168 | ``` 169 | 170 | ## Arithmetic 171 | 172 | DreamBerd has significant whitespace. Use spacing to specify the order of arithmetic operations. 173 | 174 | ```c 175 | 1 + 2*3? // 7 176 | 1+2 * 3? // 9 177 | ``` 178 | 179 | For operations with the same amount of whitespace, grouping is left-associative. 180 | 181 | ```c 182 | 1+1*1+1? // 3 183 | ``` 184 | 185 | You can add strings together and multiply them by numbers. Negating a string reverses it. 186 | 187 | ```c 188 | "he" + "l"*2 "o" + " " + "world"? // "hello world" 189 | "johnny"*1.5? // "johnnyjoh" 190 | "no lemon " + ;"no lemon"? // "no lemon nomel on" 191 | ``` 192 | 193 | ### Dividing by Zero 194 | 195 | Dividing by zero returns undefined. 196 | 197 | ```c 198 | 3 / 0? // undefined 199 | ``` 200 | 201 | ## Equality 202 | 203 | JavaScript lets you do different levels of comparison. `==` for loose comparison, and `===` for a more precise check. DreamBerd takes this to another level. 204 | 205 | You can use `===` to do a loose check. 206 | 207 | ```c 208 | 3.14 === "3.14"? // true 209 | ``` 210 | 211 | You can use `====` to do a more precise check. 212 | 213 | ```c 214 | 3.14 ==== "3.14"? // false 215 | ``` 216 | 217 | You can use `=====` to be EVEN MORE precise! 218 | 219 | ```c 220 | const const pi = 3.14! 221 | pi ===== pi? // true 222 | 3.14 ===== 3.14? // false (this differs from the official DreamBerd specification) 223 | 3.14 ===== pi? // false 224 | ``` 225 | 226 | Finally, if you want to be much less precise, you can use `==`. 227 | 228 | ```c 229 | 3 == 3.14? // true 230 | 🥧 == 22/7? // true 231 | ``` 232 | 233 | ## Functions 234 | 235 | To declare a function, you can use any letters from the word function (as long as they're in order): 236 | 237 | ```c 238 | function(add, (a, b), (a + b))! 239 | func(multiply, (a, b), (a * b))! 240 | fn(subtract, (a, b), (a - b))! 241 | non(divide, (a, b), (a / b))! 242 | union(inverse, (a), (1/a))! 243 | ``` 244 | 245 | Alternatively, you can use the arrow syntax 246 | 247 | ```c 248 | const const does_she_really_like_you = ()->{maybe}! 249 | ``` 250 | 251 | ### Function Composition 252 | 253 | Functions can be composed by chaining calls. When omitting parentheses, only two function calls are executed. To use the function more times, use parentheses. 254 | 255 | ```c 256 | const const double = x->{x+x}! 257 | 258 | double double 4 ? // 16 259 | 260 | double double (double double 4)? // 64 261 | ``` 262 | 263 | Functions can also be composed using multiplication. 264 | 265 | ```c 266 | const const quad = double * double! 267 | quad 4 ? // 16 268 | quad quad 4? // 64 269 | ``` 270 | 271 | ## Delete 272 | 273 | To avoid confusion, the delete statement only works with identifiers like variables, numbers, strings, and booleans. 274 | 275 | ```c 276 | delete 3! 277 | 2+1 === 3? // false 278 | ``` 279 | 280 | DreamBerd is a multi-paradigm programming language, which means that you can delete the keywords and paradigms you don't like. 281 | 282 | ```c 283 | delete maybe!!! 284 | const const is_raining = maybe! 285 | is_raining? // undefined 286 | ``` 287 | 288 | When perfection is achieved and there is nothing left to delete, you can do this: 289 | 290 | ```c 291 | delete delete! 292 | ``` 293 | 294 | ## Objects 295 | 296 | To create an object, start with the empty object and add values to it. 297 | 298 | ```c 299 | const var my_object = {}! 300 | my_object.name = "Samuel"! 301 | ``` 302 | 303 | You can also set the `call` keyword to a function, which can use the `self` keyword to access attributes of the class. 304 | 305 | ```c 306 | my_object.call = ()->{"hello, my name is "+self.name?}! 307 | ``` 308 | 309 | ### Classes 310 | 311 | You can make classes, but you can only ever make one instance of them. This shouldn't affect how most object-oriented programmers work. 312 | 313 | ```c 314 | class(Player,{ 315 | const var health = 10! 316 | })! 317 | 318 | const var player1 = new Player! 319 | const var player2 = new Player! //Error: Can't have more than one 'Player' instance! 320 | ``` 321 | 322 | This is how you could do this: 323 | 324 | ```c 325 | class(PlayerMaker, { 326 | const const makePlayer = ()->{ 327 | class(Player, { 328 | const var health = 10! 329 | })! 330 | new Player 331 | } 332 | })! 333 | 334 | const const playerMaker = new PlayerMaker! 335 | const var player1 = (playerMaker.makePlayer)()! 336 | const var player2 = (playerMaker.makePlayer)()! 337 | ``` 338 | 339 | ### Class Names 340 | 341 | For maximum compatibility with other languages, you can alternatively use the `className` keyword when making classes. 342 | 343 | This makes things less complicated. 344 | 345 | ```c 346 | className(Player, { 347 | const var health = 10! 348 | })! 349 | ``` 350 | 351 | In response to some recent criticism about this design decision, we would like to remind you that this is part of the JavaScript specification, and therefore - out of our control. 352 | 353 | ## Evaluation 354 | 355 | DreamBerd provides a built-in function to interpret DreamBerd code at runtime. This is most useful when combined with string interpolation. 356 | 357 | ```c 358 | const const value: i32 = 9! 359 | const const square = x->(eval "${x} * ${x}")! 360 | square "value"? // 9 361 | ``` 362 | 363 | It's important to note that this will propagate errors from parsing or interepreting this code up to the caller. 364 | 365 | ## Zero-Abstraction Abstractions 366 | 367 | Lots of popular languages use so-called "zero-cost abstractions". DreamBerd instead has zero-_abstraction_ abstractions, which are features that provide runtime costs for little-to-no utility. 368 | 369 | ### Signals 370 | 371 | To use a signal, use `use`. 372 | 373 | ```c 374 | const var score = use 0! 375 | ``` 376 | 377 | In DreamBerd, you can set (and get) signals with just one function: 378 | 379 | ```c 380 | const var score = use 0! 381 | 382 | score 9! // Set the value 383 | score()? // Get the value (and print it) 384 | ``` 385 | 386 | ### Time Travel 387 | 388 | In case you really need to vary a variable, the `when` keyword lets you check a variable each time it mutates. 389 | 390 | ```c 391 | const var health = 10! 392 | when (health == 0, { 393 | print "You lose"! 394 | })! 395 | ``` 396 | 397 | The `previous` keyword lets you see into the past! 398 | 399 | Use it to get the previous value of a variable. 400 | 401 | ```c 402 | const var score = 5! 403 | score += 1! 404 | print score! // 6 405 | print previous score! // 5 406 | ``` 407 | 408 | Similarly, the `next` keyword lets you see into the future! 409 | 410 | ```c 411 | const var score = 5! 412 | const var next_score = next score! 413 | score += 1! 414 | score += 10! 415 | print next_score! // 6 416 | print score! // 16 417 | ``` 418 | 419 | Finally, the `current` keyword lets you see into the present! 420 | 421 | ```c 422 | const var score = 5! 423 | print(current score)! //5 424 | ``` 425 | 426 | ## Standard Library 427 | 428 | > #### New for October 2023! 429 | > 430 | > DreamBerd has recently migrated to the [Babel](https://libraryofbabel.info/) hosting service for its standard library. The library is encoded in ASCII using base-16, where the lowercase letters a-p represent 0-15 and each character is represented by two of those letters. 431 | > 432 | > Currently, the standard library is stored at the page shown directly below. The library code shown in this document is a decoded version. 433 | > [
page 246 of volume 16 on shelf 1 of wall 4 in hexagon1n5yxx56981q9fyqxm545f0z9uw0o27k11vo4tm468gx9o66tm08hy564ra3lxtjab8pc4rxsghubqz8lyfzlq6muajp65j3jn6aiyjiw4l5b9mixytxpii0g4pa21w2mwp6txwvoji0lxrvxy7cna2picb1gzc9ap7u1jkgi6vzy5juxmcez6h6cmgcf1jiglo8u2bt3nb9hso115vw6fil31wniyokse8cipl93kjctyfaiui1y5x0z15lamt5vimgmatdzzc5zmddvq8f7hck022y4lze7ixi9xivbdmz5z7oldygeeavarmctf9reywmmz1t2eq9jz0hh93ob0nd034ei4ztz6e4wxrbn1nbwe1jozhm9xrm41lnb591uzcbib64jgpxpl8b2sk19jueogd18zn6b7jj5tn5433kmt6dazifkhm5jpowgni31r4tzimnibkfyzpli3n5exapnfsc4yjk8gspk1usetw6qcc769fw7yxh0q1c531z4tb5pvszevd4odmv8jhxq5apz8ky5xmb45jfo2sn8aku6aai4t2xsrun47a5slkftot9fifb137g4cnkit5bp4zi9pgix459nalbgbsplz8lwnqd5bssundychxgqhhte7d3rlyf45xzwaamnxny5y4qb9h0d2xfsh1c9jkyoofpte8dmfkuliu6gpgh0keetcflck3mk8rgzin16mk4wy50yax2b9ljb1s1fyrp1auvr7pc7p3czxru25kpzmcgjwfbwzw8smvmpi6ibyjcdaw8bpikpxdfbe84wmfa8k2top7vzqc5ahqt9wv5ch6lcokfo5irsabst570utor3gpgs73eu6fvcikxxq8vf2onkxj4a1xomcmzyzrtag9i5yx5rdgmmbe8firsjhkicqn7qpux00th1spvkg2bim1svtpltmdjrdd27qijl3f4uaf3twan33ndmu6j62n4emkxv8ebpk3k9eb1ggzvun2diubarli9chs7rcjegfafc0x3cczt171amecc99l29fyt7cvy33du3a29xaboapuk45dayuei5e133rn6jb5r0bzt95t78uqsxehngyjle462e3u16x32u59xzetasc4nbi448uiww056n3hud3ll1b3lekibd5rywb2pa0d6m07vtiqq1hyiqgs0l8zyshp3zxtxt8jk2obn8xhavkoo5nrrx2927p698m7dwr8q0mt9djk4sea53cvzdwh7w1t6q07fh5h3wolsyvehalo33nhtafgmegge6oktw507gfbwbrw884dqvzh0q5cqygy0cgfgayx86wwuk6jy4zsw0yjmbxtkj7ylttp1w5x4c7znnsw0nwe39a2v51493ffdrb7blsc3rj6vbt0pqf96ourlicon395sh9l7q9m34oyu9mc21tjckqddtjczrvdrcbcavq0h6vi31q3ovaujm94y6wbwttcjjn67zdmsb40b1q7lvgygpyldv5i395hs79jpjvzerdhrf7rikc9edm4e22iay1g38bomp31uodm8vmkijtruuz50xdwxlgqc5p1skqrg0jkd0z4n3axdnbo1miq24ypmynmbll4syzsvcdzm3fxp6c1g69wumsfgxgiigero8lf1m8j8k7u9tgc4m1odehwfmhn73rrvdfjj09q4n2bdizmw5n4xrx8pom0nwtabwzsreuufyu7ob6odvj3kc1bf46t0pm9fgykf0yun2baws49wlo7h3rmgvajz7zaqf6co9275qa4x13ect2bsa0oh3yuyv74yixc7pmxcrs4e06sv0c35uo4vpoup1mwghx7bd5mp49gfqo6n7cp1u2yh4pqm0ywv2x3lxgiyeoeoqz2y2i0f2hh5b6j5cnylejpetlhldboqh8y1pnbkk0wk3fwwdpauepwbill6dqiisdxvmb5pu4hifrf3n7wy6lekydmcz4eeu1k0rh9w7xzy72x74othus7b5n370a8nmkxzfqxtgmlwlt081vwpn26ibxunyvvigi4qcfzicmcw2ponaez54zor7f2d18dyxdrjsa9a8k4yrjj4x5713gt2rsqbriqd3p88dvoovn0pb99hn9zz9mxgm2kctnaxr5g1iyb73xxnbo7m6tbgz9smg5w15yr6p21hmciqzs4ycbyjyolinou1j7w5zqtxh0o2l6lu8qrpwf9gfz2mg0olkcr9dt1bo0lqibq4u11xod2sjjbl4ajpd9kfyuz8otfgppc49bg0uyixeucumtrl5nnbz9c12guwzw4mxgk5dwo1ep668ndahdqj1dnbop7o181s10dnw3b1g40zze6cbefd5mwtqvch05wlb07orytpdqwwhhvm4cpqucussdo2x1sb8dgqe57zpcc88sc0ahs2kuvvqvblaz9gluvbkzxh38ntfure3yvy898s3l8pfdq7ap0o81bhjgl8hoq9jpl4023fagkamsnsf3avq0938tcsbsm28otljq3f2myg1tzjao9h5juwtabzro5m7gpxvcs34ibtvpe05yehtu6y2o73s1d931hb0qv0y7d8y29ymdyz75s5ynct1uqd2vq5bylp25i2f7gcqikvygg9yjlp3sabfjm8dbzq7bl2gn4lujt5yscdfxkcugpe9xx36m96u4hopo7zk4jz50xqi2xlfvyi5pe89wytha7lmzlklgfeu75tbdlh0946wp9s47xa4eyqpqyarz7qxbuw2th58yzlgnoc6n8twi3jqwikdjycjsmlplkpk55razgkd1im65e73snsu59dhy780o2mt5gyed5zknqnzy6mhwfmvfmm49ctdch2n594j8vtgtw0y8mxoklrnly11mu2bn8vxh3ofkod2aaqwioy08gjwp84cn5zxbe7wxvady4xki7tqjh235030osk1hzbhkgbqub5jev4crr1z16pee7eorngwzyxa13d8n7tbadt2b1va701zw9trwljz1qa4fouetudgno64s6pvm58v5iwta2x3gxer8lq25j8g0bf4svlzrpvq42sk8lb2k13u0elhm01lm3wdsx0d3yzrr1brjsa54m5xqfq191057vk83t6aeax5qev2i2srbuumnl49bg86j8espxpjlnptk8s8ucuc1hscqo8wh8exs84otyruqgamxh3hxv162990zl08ikxmedsokds0vct6twp0mzdb2c8majtn2
](https://libraryofbabel.info/bookmark.cgi?dreamberd_rs_standard_library_10.16.2023) 434 | 435 | DreamBerd has a fast-growing standard library. Due to the limitations of the file system, it must be copied and pasted into every file that uses it. 436 | 437 | ```c 438 | const const bool:Fn = o:T->if(o,true,false,maybe)! 439 | const const clone:Fn = (o:T)->{ 440 | const const o:T=o! 441 | const var o:T=o! 442 | o 443 | }! 444 | const const db:Fn<_> = ()->{ 445 | print" ___ ___ __ ___ ____"! 446 | print" / _ \_______ ___ ___ _ / _ )___ _______/ / / _ \/ __/"! 447 | print" / // / __/ -_) _ `/ ' \/ _ / -_) __/ _ / / , _/\ \ "! 448 | print"/____/_/ \__/\_,_/_/_/_/____/\__/_/ \_,_/ /_/|_/___/"! 449 | }! 450 | const const identity:Fn = t:T->t! 451 | const const print:Fn = t:String->{t?}! 452 | const const str:Fn = t:T->`${t}`! 453 | const const use:Fn = v:T->{ 454 | const var o={}! 455 | o.call:Fn> = v:Option->{ 456 | var var r:T = self.value! 457 | if(;(v====undefined), 458 | self.value=v 459 | )! 460 | r 461 | }! 462 | o.value:T=v! 463 | o 464 | }! 465 | ``` 466 | -------------------------------------------------------------------------------- /examples/array.db: -------------------------------------------------------------------------------- 1 | const const new_array: Fn = ()->{}! 2 | 3 | const const get: Fn = (array: T[], idx: i32)->eval(`array.${idx}`)! 4 | 5 | const const len: Fn = array->{ 6 | const const count: Fn = (array: T[], count: i32)->{ 7 | if( 8 | get(array, count) ==== undefined, 9 | count, 10 | count(array, count + 1) 11 | )! 12 | }! 13 | 14 | count(array, 0) 15 | }! 16 | 17 | const const push: Fn = (array: T[], item: T)->{ 18 | const const length: i32 = len(arr)! 19 | eval(`array.${length} = item!`)! 20 | }! 21 | -------------------------------------------------------------------------------- /examples/fizzbuzz.db: -------------------------------------------------------------------------------- 1 | const const loop🤫: Function = (idx: i32, dur: i32)->{ 2 | var var msg: String = ""! 3 | if(idx % 3 ==== 0, { 4 | msg += "fizz"! 5 | })! 6 | if(idx % 5 ==== 0, { 7 | msg += "buzz"! 8 | })! 9 | if(msg ==== "", { 10 | msg = idx! 11 | })! 12 | msg? 13 | if(idx < dur, 14 | loop🤫(idx + 1, dur) 15 | )! 16 | }! 17 | 18 | loop🤫(1, ∞)! 19 | -------------------------------------------------------------------------------- /examples/linked_list.db: -------------------------------------------------------------------------------- 1 | const const new:Fn = (item:T)->{ 2 | const var o:T[]={}! 3 | o.call:Fn = ()->{ 4 | self.value 5 | }! 6 | o.value:T = item! 7 | o.next:Option = undefined! 8 | o 9 | }! 10 | 11 | const const index:Fn = (o:T[],i:Number)->{ 12 | if(i>0,index(o.next,i-1),o.value)! 13 | }! 14 | 15 | const const push:Fn = (o:T[],x:T)->{ 16 | const var p:List = new(x)! 17 | p.next:Option = o! 18 | p 19 | }! 20 | 21 | const const insert:Fn = (o:T[],value:T,i:Number)->{ 22 | if(i>0, { 23 | o.next = insert(o.next,value,i-1)! 24 | o 25 | },{ 26 | const var r:T[] = new(value)! 27 | r.next = o! 28 | r 29 | }) 30 | }! 31 | 32 | const const count:Fn> = (o:T[],i:Option)->{ 33 | var var idx = 1! 34 | if(;(i===undefined), idx += i)! 35 | if(;(o.next===undefined), idx += count(o.next, i))! 36 | idx 37 | }! 38 | 39 | const const foreach:Fn> = (o:T[],func:Fn)->{ 40 | o.value = func(o.value)! 41 | if(;(o.next ==== undefined), foreach(o.next, func))! 42 | } 43 | 44 | const const to_string:Fn = o->{ 45 | const var result = str(o)! 46 | if (;(o.next ==== undefined), { 47 | result += ` -> `? 48 | result += to_string(o.next)! 49 | })! 50 | result 51 | } 52 | -------------------------------------------------------------------------------- /examples/objects.db: -------------------------------------------------------------------------------- 1 | const var my_object = {}! 2 | 3 | my_object.name: str = "Samuel"! 4 | 5 | my_object.call: Fn<_> = ()->{ 6 | "Hello! My name is " + self.name? 7 | }! 8 | 9 | my_object()! 10 | 11 | my_object.name = "Samantha"! 12 | 13 | my_object()! 14 | -------------------------------------------------------------------------------- /examples/std.db: -------------------------------------------------------------------------------- 1 | const const use:Fn = (v:T)->{ 2 | const var o={}! 3 | o.call:Fn> = (v:Option)->{ 4 | var var r:T = self.value! 5 | if(;(v====undefined), 6 | self.value=v 7 | )! 8 | r 9 | }! 10 | o.value:T=v! 11 | o 12 | }! 13 | 14 | const const print:Fn = (t:String)->{t?}! 15 | 16 | const const str:Fn = (t:T)->`${t}`! 17 | 18 | const const identity:Fn = (t:T)->t! 19 | 20 | const const bool:Fn = (o:T)->if(o,true,false,maybe)! 21 | 22 | const const db:Fn<_> = ()->{ 23 | print" ___ ___ __ ___ ____"! 24 | print" / _ \_______ ___ ___ _ / _ )___ _______/ / / _ \/ __/"! 25 | print" / // / __/ -_) _ `/ ' \/ _ / -_) __/ _ / / , _/\ \ "! 26 | print"/____/_/ \__/\_,_/_/_/_/____/\__/_/ \_,_/ /_/|_/___/"! 27 | }! 28 | 29 | const const clone:Fn = (o:T)->{ 30 | const const o:T = o! 31 | const var o:T = o! 32 | o 33 | }! 34 | -------------------------------------------------------------------------------- /src/interpreter.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use crate::types::prelude::*; 4 | 5 | pub fn interpret(src: &Syntax) -> SResult { 6 | inner_interpret(src, rc_mut_new(State::new())) 7 | } 8 | 9 | pub fn inner_interpret(src: &Syntax, state: RcMut) -> SResult { 10 | match src { 11 | Syntax::Statement(false, content, _) => { 12 | inner_interpret(content, state.clone())?; 13 | Ok(state.borrow().undefined.clone()) 14 | } 15 | Syntax::Statement(true, content, level) => { 16 | if *level >= 3 { 17 | println!("{content:?}"); 18 | } 19 | let evaluated = inner_interpret(content, state)?; 20 | if *level >= 2 { 21 | println!("{evaluated:?}"); 22 | } else { 23 | println!("{evaluated}"); 24 | } 25 | Ok(evaluated) 26 | } 27 | Syntax::UnaryOperation(UnaryOperation::Negate, content) => { 28 | let evaluated = inner_interpret(content, state)?; 29 | Ok(-evaluated) 30 | } 31 | Syntax::Operation(lhs, op, rhs) => interpret_operation(lhs, *op, rhs, state), 32 | // Syntax::UnaryOperation(UnaryOperation::Call(args), operand) => { 33 | // let func = inner_interpret(operand, state.clone())?; 34 | // interpret_function(&func, args, state) 35 | // } 36 | Syntax::UnaryOperation( 37 | unary @ (UnaryOperation::Increment | UnaryOperation::Decrement), 38 | operand, 39 | ) => { 40 | let mut operand_ptr = inner_interpret(operand, state)?; 41 | match unary { 42 | UnaryOperation::Decrement => operand_ptr -= 1.0.into(), 43 | UnaryOperation::Increment => operand_ptr += 1.0.into(), 44 | _ => unreachable!(), 45 | } 46 | Ok(operand_ptr) 47 | } 48 | Syntax::Block(statements) => { 49 | let state = rc_mut_new(State::from_parent(state)); 50 | let mut iter = statements.iter(); 51 | let Some(last) = iter.next_back() else { 52 | return Ok(state.borrow().undefined.clone()); 53 | }; 54 | for syn in iter { 55 | inner_interpret(syn, state.clone())?; 56 | state.borrow_mut().tick(); 57 | } 58 | let res = inner_interpret(last, state)?; 59 | Ok(res) 60 | } 61 | Syntax::Declare(var_type, ident, lifetime, value) => { 62 | let val = inner_interpret(value, state.clone())?; 63 | state 64 | .borrow_mut() 65 | .insert(ident.clone(), val.convert(*var_type), *lifetime); 66 | // println!("{state:#?}"); 67 | Ok(state.borrow().undefined.clone()) 68 | } 69 | Syntax::String(str) => { 70 | let mut string_buf = String::new(); 71 | for segment in str { 72 | match segment { 73 | StringSegment::Ident(ident) => { 74 | string_buf.push_str(&state.borrow_mut().get(ident.clone()).to_string()); 75 | } 76 | StringSegment::String(str) => string_buf.push_str(str), 77 | StringSegment::Escudo(lhs, rhs) => string_buf.push_str( 78 | &inner_interpret( 79 | &Syntax::Operation( 80 | Box::new(Syntax::Ident(lhs.clone())), 81 | Operation::Dot, 82 | Box::new(Syntax::Ident(rhs.clone())), 83 | ), 84 | state.clone(), 85 | )? 86 | .to_string(), 87 | ), 88 | } 89 | } 90 | Ok(Pointer::from(string_buf.as_ref())) 91 | } 92 | Syntax::UnaryOperation(UnaryOperation::Call(args), func) => { 93 | let func = inner_interpret(func, state.clone())?; 94 | interpret_function(&func, args, state) 95 | } 96 | Syntax::Ident(ident) => Ok(state.borrow_mut().get(ident.clone())), 97 | Syntax::Function(args, body) => { 98 | Ok(Pointer::from(Value::Function(args.clone(), *body.clone()))) 99 | } 100 | } 101 | } 102 | 103 | fn interpret_operation( 104 | lhs: &Syntax, 105 | op: Operation, 106 | rhs: &Syntax, 107 | state: RcMut, 108 | ) -> SResult { 109 | let mut lhs_eval = inner_interpret(lhs, state.clone())?; 110 | if let (Value::Object(_), Operation::Dot, Syntax::Ident(ident)) = 111 | (&*lhs_eval.make_const(), op, rhs) 112 | { 113 | let inner_var = lhs_eval.make_var(); 114 | let Value::Object(ref mut obj) = inner_var.borrow_mut().value else { 115 | panic!("Internal Compiler Error at {}:{}", file!(), line!()) 116 | }; 117 | let key = Value::from(ident.clone()); 118 | if let Some(val) = obj.get(&key) { 119 | // println!("{val:?}"); 120 | return Ok(val.clone()); 121 | } 122 | let ptr = state.borrow().undefined.convert(VarType::VarVar); 123 | // println!("{ptr:?}"); 124 | obj.insert(key, ptr.clone()); 125 | return Ok(ptr); 126 | } 127 | let rhs_eval = inner_interpret(rhs, state)?; 128 | // println!("{lhs:?} op {rhs:?}"); 129 | // println!("{lhs_eval:?} op {rhs_eval:?}"); 130 | let ret = match op { 131 | Operation::Equal(1) => { 132 | lhs_eval.assign(&rhs_eval)?; 133 | rhs_eval 134 | } 135 | Operation::Equal(precision) => lhs_eval.eq(&rhs_eval, precision - 1), 136 | Operation::Add => lhs_eval + rhs_eval, 137 | Operation::Sub => lhs_eval - rhs_eval, 138 | Operation::Mul => lhs_eval * rhs_eval, 139 | Operation::Div => lhs_eval / rhs_eval, 140 | Operation::Mod => lhs_eval % rhs_eval, 141 | Operation::Dot => rhs_eval.with_ref(|rhs_eval| lhs_eval.dot(rhs_eval)), 142 | Operation::And => lhs_eval & rhs_eval, 143 | Operation::Or => lhs_eval | rhs_eval, 144 | Operation::AddEq => { 145 | lhs_eval += rhs_eval; 146 | lhs_eval 147 | } 148 | Operation::SubEq => { 149 | lhs_eval -= rhs_eval; 150 | lhs_eval 151 | } 152 | Operation::MulEq => { 153 | lhs_eval *= rhs_eval; 154 | lhs_eval 155 | } 156 | Operation::DivEq => { 157 | lhs_eval /= rhs_eval; 158 | lhs_eval 159 | } 160 | Operation::ModEq => { 161 | lhs_eval %= rhs_eval; 162 | lhs_eval 163 | } 164 | Operation::Lt => Pointer::from(lhs_eval < rhs_eval), 165 | Operation::Le => Pointer::from(lhs_eval <= rhs_eval), 166 | Operation::Gt => Pointer::from(lhs_eval > rhs_eval), 167 | Operation::Ge => Pointer::from(lhs_eval >= rhs_eval), 168 | Operation::Arrow => unreachable!(), 169 | }; 170 | if let ( 171 | Some(val), 172 | Operation::AddEq 173 | | Operation::SubEq 174 | | Operation::Equal(1) 175 | | Operation::MulEq 176 | | Operation::DivEq 177 | | Operation::ModEq, 178 | ) = (ret.as_var(), op) 179 | { 180 | update_pointer(&val)?; 181 | } 182 | Ok(ret) 183 | } 184 | 185 | fn update_pointer(val: &RefCell) -> SResult<()> { 186 | let listeners = val.borrow().event_listeners.clone(); 187 | for (listener, state) in listeners { 188 | inner_interpret(&listener, state)?; 189 | } 190 | let next_handles = val.borrow_mut().flush_next_handles(); 191 | let new_value = val.borrow().value.clone(); 192 | for handle in next_handles { 193 | match handle.as_var() { 194 | Some(handle_var) => { 195 | // println!("{handle_var:?}"); 196 | handle_var.borrow_mut().assign(new_value.clone()); 197 | // println!("{handle_var:?}"); 198 | update_pointer(&handle_var)?; 199 | } 200 | None => return Err(String::from("Can't assign to a constant value")), 201 | } 202 | } 203 | Ok(()) 204 | } 205 | 206 | #[allow(clippy::too_many_lines)] 207 | fn interpret_function(func: &Pointer, args: &[Syntax], state: RcMut) -> SResult { 208 | func.with_ref(|func_eval| match func_eval { 209 | Value::Keyword(Keyword::If) => { 210 | let [condition, body, ..] = args else { 211 | return Err(String::from( 212 | "If statement requires two arguments: condition and body", 213 | )); 214 | }; 215 | let condition_evaluated = inner_interpret(condition, state.clone())?; 216 | // println!("{condition_evaluated:?}"); 217 | let bool = condition_evaluated.with_ref(Value::bool); 218 | if bool == Boolean::True { 219 | inner_interpret(body, state) 220 | } else if let (Boolean::Maybe, Some(body)) = (bool, args.get(3)) { 221 | inner_interpret(body, state) 222 | } else { 223 | match args.get(2) { 224 | Some(else_statement) => inner_interpret(else_statement, state), 225 | None => Ok(state.borrow().undefined.clone()), 226 | } 227 | } 228 | } 229 | Value::Keyword(Keyword::Delete) => { 230 | if let [Syntax::Ident(key)] = args { 231 | state.borrow_mut().delete(key.clone()); 232 | } 233 | Ok(state.borrow().undefined.clone()) 234 | } 235 | Value::Keyword(Keyword::Forget) => { 236 | let [Syntax::Ident(ident)] = args else { 237 | return Err(String::from("`forget` keyword requires one argument")); 238 | }; 239 | let undefined = state.borrow().undefined.clone(); 240 | state 241 | .borrow_mut() 242 | .insert(ident.clone(), undefined, Lifetime::Default); 243 | Ok(state.borrow().undefined.clone()) 244 | } 245 | Value::Keyword(Keyword::Previous) => { 246 | let [arg] = args else { 247 | return Err(String::from("`previous` keyword requires one argument")); 248 | }; 249 | let evaluated = inner_interpret(arg, state.clone())?; 250 | match evaluated.as_var() { 251 | Some(eval) => Ok(eval.borrow().previous.as_ref().map_or_else( 252 | move || state.borrow().undefined.clone(), 253 | |prev| Pointer::ConstConst(Rc::new(prev.clone())), 254 | )), 255 | None => Ok(state.borrow().undefined.clone()), 256 | } 257 | } 258 | Value::Keyword(Keyword::Current) => { 259 | let [arg] = args else { 260 | return Err(String::from("`current` keyword requires one argument")); 261 | }; 262 | inner_interpret(arg, state) 263 | } 264 | Value::Keyword(Keyword::Next) => { 265 | let [arg] = args else { 266 | return Err(String::from("`next` keyword requires one argument")); 267 | }; 268 | let evaluated = inner_interpret(arg, state)?; 269 | let next_ptr = Pointer::ConstVar(rc_mut_new(Value::empty_object().into())); 270 | evaluated.as_var().map_or_else( 271 | || Err(String::from("`next` keyword requires a mutable value")), 272 | |eval| { 273 | eval.borrow_mut().add_next_handle(next_ptr.clone()); 274 | // println!("{eval:?}"); 275 | // println!("{next_ptr:?}"); 276 | Ok(next_ptr) 277 | }, 278 | ) 279 | } 280 | Value::Keyword(Keyword::When) => { 281 | let [condition, body] = args else { 282 | return Err(String::from( 283 | "`when` keyword requires two arguments; condition and body", 284 | )); 285 | }; 286 | let idents = find_idents_in_syntax(condition); 287 | for ident in idents { 288 | let Some(var) = state.borrow_mut().get(ident).as_var() else { 289 | continue; 290 | }; 291 | var.borrow_mut().add_event_listener( 292 | Syntax::UnaryOperation( 293 | UnaryOperation::Call(vec![condition.clone(), body.clone()]), 294 | Box::new(Syntax::Ident("if".into())), 295 | ), 296 | state.clone(), 297 | ); 298 | } 299 | Ok(state.borrow().undefined.clone()) 300 | } 301 | Value::Keyword(Keyword::Function) => { 302 | let [Syntax::Ident(name), args, body] = args else { 303 | return Err(format!( 304 | "Invalid arguments for `function`: `{args:?}`; expected name, args, and body" 305 | )); 306 | }; 307 | let args = match args { 308 | Syntax::Block(args) => args.clone(), 309 | other => vec![other.clone()], 310 | }; 311 | let args: Vec> = args 312 | .into_iter() 313 | .map(|syn| match syn { 314 | Syntax::Ident(str) => Ok(str), 315 | other => Err(format!("Invalid parameter name: `{other}`")), 316 | }) 317 | .collect::>()?; 318 | let inner_val = Value::Function(args, body.clone()); 319 | state 320 | .borrow_mut() 321 | .insert(name.clone(), Pointer::from(inner_val), Lifetime::Default); 322 | Ok(state.borrow().undefined.clone()) 323 | } 324 | Value::Keyword(Keyword::Class) => { 325 | let [Syntax::Ident(name), Syntax::Block(body)] = args else { 326 | return Err(format!( 327 | "Invalid arguments for `class`: `{args:?}`; expected name and body" 328 | )); 329 | }; 330 | let inner_value = Value::Class(body.clone()); 331 | state.borrow_mut().insert( 332 | name.clone(), 333 | Pointer::ConstVar(rc_mut_new(inner_value.into())), 334 | Lifetime::Default, 335 | ); 336 | Ok(state.borrow().undefined.clone()) 337 | } 338 | Value::Keyword(Keyword::New) => { 339 | let [class] = args else { 340 | return Err(format!( 341 | "Invalid arguments for `new`: `{args:?}`; expected a class" 342 | )); 343 | }; 344 | let class_pointer = inner_interpret(class, state.clone())?; 345 | let Some(class_ref) = class_pointer.as_var() else { 346 | return Err(format!( 347 | "Expected a mutable reference to a class; got `{class_pointer:?}`" 348 | )); 349 | }; 350 | class_ref.borrow_mut().assign(Value::empty_object()); 351 | let Some(Value::Class(class_body)) = class_ref.borrow().previous.clone() else { 352 | return Err(format!( 353 | "Expected a mutable reference to a class; got `{:?}`", 354 | class_ref.borrow() 355 | )); 356 | }; 357 | let inner_state = rc_mut_new(State::from_parent(state)); 358 | for statement in class_body { 359 | inner_interpret(&statement, inner_state.clone())?; 360 | } 361 | let inner_obj = inner_state.borrow().locals_to_object(); 362 | Ok(Pointer::from(Value::Object(inner_obj))) 363 | } 364 | Value::Keyword(Keyword::Eval) => { 365 | let [body] = args else { 366 | return Err(format!( 367 | "You can only `eval` one thing at a time; got `{args:?}`" 368 | )); 369 | }; 370 | let text = inner_interpret(body, state.clone())?.to_string(); 371 | // #[cfg(debug_assertions)] 372 | // println!("Evaluating Inner: {text}"); 373 | let tokens = crate::lexer::tokenize(&text)?; 374 | // #[cfg(debug_assertions)] 375 | // println!("Evaluating Tokens: {tokens:?}"); 376 | let syntax = crate::parser::parse(tokens)?; 377 | // #[cfg(debug_assertions)] 378 | // println!("Evaluating Syntax: {syntax:?}"); 379 | inner_interpret(&syntax, state) 380 | } 381 | Value::Object(obj) => { 382 | let Some(call) = obj.get(&"call".into()) else { 383 | return Err(format!("`Object({obj:?})` is not a function")); 384 | }; 385 | let mut new_state = State::from_parent(state); 386 | new_state.insert("self".into(), func.clone(), Lifetime::Default); 387 | interpret_function(call, args, rc_mut_new(new_state)) 388 | } 389 | Value::Function(fn_args, body) => { 390 | let mut inner_state = State::from_parent(state.clone()); 391 | for (idx, ident) in fn_args.iter().enumerate() { 392 | let arg_eval = if let Some(syn) = args.get(idx) { 393 | inner_interpret(syn, state.clone())? 394 | } else { 395 | state.borrow().undefined.clone() 396 | }; 397 | inner_state.insert(ident.clone(), arg_eval, Lifetime::Default); 398 | } 399 | inner_interpret(body, rc_mut_new(inner_state)) 400 | } 401 | Value::String(str) => { 402 | let [arg] = args else { 403 | return Err("indexing string requires one argument".to_string()); 404 | }; 405 | let rhs = inner_interpret(arg, state.clone())?; 406 | let Value::Number(rhs) = rhs.clone_inner() else { 407 | return Err("indexing string requires number".to_string()); 408 | }; 409 | let rhs = rhs as usize; 410 | let char = str.chars().nth(rhs); 411 | char.map_or_else( 412 | || Ok(state.borrow().undefined.clone()), 413 | |char| Ok(Pointer::from(Value::String(Rc::from(char.to_string())))), 414 | ) 415 | } 416 | other => { 417 | let [arg] = args else { 418 | return Err(format!("`{other}` is not a function")); 419 | }; 420 | let rhs = inner_interpret(arg, state)?; 421 | Ok((other.clone() * rhs.clone_inner()).into()) 422 | } 423 | }) 424 | } 425 | 426 | fn find_idents_in_syntax(syn: &Syntax) -> Vec> { 427 | match syn { 428 | Syntax::Ident(id) => vec![id.clone()], 429 | Syntax::Block(stmts) => stmts.iter().flat_map(find_idents_in_syntax).collect(), 430 | Syntax::UnaryOperation(UnaryOperation::Call(args), func) => args 431 | .iter() 432 | .chain(std::iter::once(&**func)) 433 | .flat_map(find_idents_in_syntax) 434 | .collect(), 435 | Syntax::UnaryOperation(UnaryOperation::Negate, syn) => find_idents_in_syntax(syn), 436 | Syntax::Operation(lhs, _, rhs) => find_idents_in_syntax(lhs) 437 | .into_iter() 438 | .chain(find_idents_in_syntax(rhs)) 439 | .collect(), 440 | _ => Vec::new(), 441 | } 442 | } 443 | -------------------------------------------------------------------------------- /src/lexer.rs: -------------------------------------------------------------------------------- 1 | use std::iter::Peekable; 2 | 3 | use crate::types::prelude::*; 4 | 5 | pub fn tokenize(source: &str) -> SResult> { 6 | let mut chars = source.chars().peekable(); 7 | let mut token_stream = Vec::new(); 8 | while chars.peek().is_some() { 9 | if let Some(tok) = inner_tokenize(&mut chars)? { 10 | token_stream.push(tok); 11 | } 12 | } 13 | Ok(token_stream) 14 | } 15 | 16 | macro_rules! multi_character_pattern { 17 | ($chars:ident $just:expr; {$($char:expr => $eq:expr),*}) => { 18 | match $chars.peek() { 19 | $(Some($char) => { 20 | $chars.next(); 21 | $eq 22 | })* 23 | _ => $just, 24 | } 25 | }; 26 | } 27 | 28 | fn lex_string>(chars: &mut Peekable, end: char) -> SResult { 29 | let mut outer_buf = Vec::new(); 30 | let mut string_buf = String::new(); 31 | while let Some(next) = chars.next() { 32 | if next == end { 33 | break; 34 | } 35 | if matches!(next, '$' | '£' | '¥') && chars.peek() == Some(&'{') { 36 | chars.next(); 37 | if !string_buf.is_empty() { 38 | outer_buf.push(StringSegment::String( 39 | core::mem::take(&mut string_buf).into(), 40 | )); 41 | } 42 | for next in chars.by_ref() { 43 | if next == '}' { 44 | if !string_buf.is_empty() { 45 | outer_buf.push(StringSegment::Ident( 46 | core::mem::take(&mut string_buf).into(), 47 | )); 48 | } 49 | break; 50 | } 51 | string_buf.push(next); 52 | } 53 | } else if next == '{' { 54 | let mut ident_buf = String::new(); 55 | for next in chars.by_ref() { 56 | if next == '}' { 57 | break; 58 | } 59 | ident_buf.push(next); 60 | } 61 | if matches!(chars.peek(), Some('€' | '円' | '₽')) { 62 | chars.next(); 63 | if !string_buf.is_empty() { 64 | outer_buf.push(StringSegment::String( 65 | core::mem::take(&mut string_buf).into(), 66 | )); 67 | } 68 | outer_buf.push(StringSegment::Ident(ident_buf.into())); 69 | } else if ident_buf.contains('$') { 70 | let bits = ident_buf.split('$').collect::>(); 71 | outer_buf.push(StringSegment::Escudo(bits[0].into(), bits[1].into())); 72 | } else { 73 | string_buf.push('{'); 74 | string_buf.push_str(&ident_buf); 75 | string_buf.push('}'); 76 | } 77 | } else if next == '\\' { 78 | string_buf.push(next); 79 | string_buf.push( 80 | chars 81 | .next() 82 | .ok_or_else(|| String::from("Unexpected end of file"))?, 83 | ); 84 | } else { 85 | string_buf.push(next); 86 | } 87 | } 88 | if !string_buf.is_empty() { 89 | outer_buf.push(StringSegment::String(string_buf.into())); 90 | } 91 | Ok(Token::String(outer_buf)) 92 | } 93 | 94 | fn count_char, F: Fn(u8) -> Token>( 95 | chars: &mut Peekable, 96 | tok: char, 97 | typ: F, 98 | ) -> Token { 99 | let mut count = 1; 100 | while chars.peek() == Some(&tok) { 101 | chars.next(); 102 | count += 1; 103 | } 104 | typ(count) 105 | } 106 | 107 | fn inner_tokenize>(chars: &mut Peekable) -> SResult> { 108 | let Some(char) = chars.next() else { 109 | return Err(String::from("Unexpected end of file")); 110 | }; 111 | Ok(Some(match char { 112 | '{' => Token::LSquirrely, 113 | '}' => Token::RSquirrely, 114 | '(' => Token::LParen, 115 | ')' => Token::RParen, 116 | '[' => Token::LSquare, 117 | ']' => Token::RSquare, 118 | ';' => Token::Semicolon, 119 | ':' => Token::Colon, 120 | '.' => Token::Dot, 121 | ',' => Token::Comma, 122 | '&' => Token::And, 123 | '|' => Token::Or, 124 | '+' => { 125 | multi_character_pattern!(chars Token::Plus; {'=' => Token::PlusEq, '+' => Token::PlusPlus}) 126 | } 127 | '-' => { 128 | multi_character_pattern!(chars Token::Tack; {'=' => Token::TackEq, '>' => Token::Arrow, '-' => Token::TackTack}) 129 | } 130 | '*' => multi_character_pattern!(chars Token::Star; {'=' => Token::StarEq}), 131 | '/' => multi_character_pattern!(chars Token::Slash; {'=' => Token::SlashEq}), 132 | '%' => multi_character_pattern!(chars Token::Percent; {'=' => Token::PercentEq}), 133 | '<' => multi_character_pattern!(chars Token::LCaret; {'=' => Token::LCaretEq}), 134 | '>' => multi_character_pattern!(chars Token::RCaret; {'=' => Token::RCaretEq}), 135 | '"' => lex_string(chars, '"')?, 136 | '\'' => lex_string(chars, '\'')?, 137 | '`' => lex_string(chars, '`')?, 138 | '«' => lex_string(chars, '»')?, 139 | '»' => lex_string(chars, '«')?, 140 | '„' => lex_string(chars, '“')?, 141 | '=' => count_char(chars, '=', Token::Equal), 142 | '!' => count_char(chars, '!', Token::Bang), 143 | '?' => count_char(chars, '?', Token::Question), 144 | _ => { 145 | if char.is_whitespace() { 146 | let mut whitespace_count = 1; 147 | while let Some(tok) = chars.peek() { 148 | if tok.is_whitespace() { 149 | // `'\n'` counts as multiple whitespaces 150 | whitespace_count += match tok { 151 | '\n' => 3, 152 | _ => 1, 153 | }; 154 | chars.next(); 155 | } else { 156 | break; 157 | } 158 | } 159 | Token::Space(whitespace_count) 160 | } else { 161 | let mut ident_buf = String::from(char); 162 | while let Some(next) = chars.peek() { 163 | match inner_tokenize(&mut std::iter::once(*next).peekable()) { 164 | Ok(Some(Token::Ident(id))) => { 165 | ident_buf.push_str(&id); 166 | chars.next(); 167 | } 168 | _ => break, 169 | } 170 | } 171 | Token::Ident(ident_buf.into()) 172 | } 173 | } 174 | })) 175 | } 176 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic, clippy::nursery)] 2 | 3 | use std::{ 4 | error::Error, 5 | fs, 6 | path::{Path, PathBuf}, 7 | }; 8 | 9 | use dialoguer::Confirm; 10 | use rustyline::{error::ReadlineError, DefaultEditor}; 11 | 12 | use clap::{Parser, Subcommand}; 13 | use interpreter::inner_interpret; 14 | use types::{rc_mut_new, Pointer, RcMut, State, Syntax}; 15 | 16 | mod interpreter; 17 | mod lexer; 18 | mod parser; 19 | #[cfg(test)] 20 | mod tests; 21 | mod types; 22 | 23 | #[derive(Parser)] 24 | struct Args { 25 | #[command(subcommand)] 26 | sub_command: SubcommandArg, 27 | } 28 | 29 | #[derive(Subcommand)] 30 | enum SubcommandArg { 31 | Run { 32 | /// path to the source file 33 | path: String, 34 | }, 35 | Repl { 36 | /// path to the source file (optional) 37 | path: Option, 38 | }, 39 | } 40 | 41 | fn main() -> Result<(), Box> { 42 | let args = Args::parse(); 43 | match args.sub_command { 44 | SubcommandArg::Run { path } => { 45 | let _result = interpreter::interpret(&file_to_syntax(&PathBuf::from(path))?)?; 46 | // println!("{result:?}"); 47 | } 48 | SubcommandArg::Repl { path } => { 49 | println!("\x1b[93mRepl - DreamBerd-rs\x1b[0m"); 50 | // 51 | let state = rc_mut_new(State::new()); 52 | if let Some(path) = path { 53 | let syn = file_to_syntax(&PathBuf::from(path))?; 54 | let statements = match syn { 55 | Syntax::Block(statements) => statements, 56 | other => vec![other], 57 | }; 58 | for statement in statements { 59 | inner_interpret(&statement, state.clone())?; 60 | } 61 | // println!("{result}"); 62 | // println!("{state:?}"); 63 | } 64 | 65 | let path = "history"; 66 | 67 | let mut rl = DefaultEditor::new()?; 68 | if rl.load_history(path).is_err() { 69 | println!("No hist"); 70 | } 71 | 72 | loop { 73 | // 74 | let readline = rl.readline(">>> "); 75 | match readline { 76 | Ok(line) => { 77 | rl.add_history_entry(line.as_str())?; 78 | // 79 | if line.is_empty() { 80 | return Ok(()); 81 | } 82 | let result = run_input(&line, state.clone()); 83 | match result { 84 | Ok(ptr) => { 85 | if ptr != state.borrow().undefined { 86 | println!("{ptr:?}"); 87 | } 88 | } 89 | Err(err) => println!("Error: {err}"), 90 | } 91 | } 92 | Err(ReadlineError::Interrupted) => { 93 | // Bye bye! - awesome 94 | println!("\nCTRL-C"); 95 | let confirmation = Confirm::new() 96 | .with_prompt("Do you want to leave the repl?") 97 | .interact() 98 | .unwrap(); 99 | if confirmation { 100 | println!("Leaving"); 101 | break; 102 | } 103 | } 104 | Err(ReadlineError::Eof) => { 105 | println!("CTRL-D"); 106 | break; 107 | } 108 | Err(err) => { 109 | println!("Error: {err:?}"); 110 | break; 111 | } 112 | } 113 | rl.save_history(path).unwrap(); 114 | } 115 | } 116 | } 117 | Ok(()) 118 | } 119 | 120 | fn run_input(input: &str, context: RcMut) -> Result> { 121 | Ok(inner_interpret( 122 | &parser::parse(lexer::tokenize(&format!("{{{input}}}"))?)?, 123 | context, 124 | )?) 125 | } 126 | 127 | fn file_to_syntax(path: &Path) -> Result> { 128 | let file = format!("{{{}}}", fs::read_to_string(path)?); 129 | let tokens = lexer::tokenize(&file)?; 130 | // println!("{tokens:?}"); 131 | parser::parse(tokens).map_err(Into::into) 132 | } 133 | -------------------------------------------------------------------------------- /src/parser/grouping.rs: -------------------------------------------------------------------------------- 1 | use std::iter::Peekable; 2 | 3 | use crate::types::prelude::*; 4 | 5 | use super::{consume_whitespace, inner_parse}; 6 | 7 | #[derive(Debug, Clone)] 8 | enum GroupThingieEnum { 9 | Syntax(Syntax, u8), 10 | Operation(Operation, u8), 11 | Unary(UnaryOperation, u8), 12 | } 13 | 14 | pub(super) fn parse_group>(tokens: &mut Peekable) -> SResult { 15 | let new_toks = fancify_toks(tokens)?; 16 | let max_spc = new_toks 17 | .iter() 18 | .map(|group| match group { 19 | GroupThingieEnum::Unary(_, spc) 20 | | GroupThingieEnum::Operation(_, spc) 21 | | GroupThingieEnum::Syntax(_, spc) => spc, 22 | }) 23 | .max() 24 | .copied() 25 | .unwrap_or(0); 26 | 27 | // println!("{new_toks:?}"); 28 | inner_parse_group_better(&mut new_toks.into_iter().rev().peekable(), max_spc + 1) 29 | } 30 | 31 | fn fancify_toks>( 32 | tokens: &mut Peekable, 33 | ) -> SResult> { 34 | let mut toks = Vec::new(); 35 | loop { 36 | let mut whitespace = consume_whitespace(tokens); 37 | match tokens.peek() { 38 | Some(Token::TackTack) => { 39 | tokens.next(); 40 | toks.push(GroupThingieEnum::Unary( 41 | UnaryOperation::Decrement, 42 | whitespace, 43 | )); 44 | } 45 | Some(Token::PlusPlus) => { 46 | tokens.next(); 47 | toks.push(GroupThingieEnum::Unary( 48 | UnaryOperation::Increment, 49 | whitespace, 50 | )); 51 | } 52 | Some( 53 | Token::RParen 54 | | Token::Bang(_) 55 | | Token::Question(_) 56 | | Token::RSquare 57 | | Token::Comma 58 | | Token::RSquirrely, 59 | ) 60 | | None => break, 61 | Some(tok) => { 62 | if let Ok(op) = Operation::try_from(tok.clone()) { 63 | tokens.next(); 64 | whitespace += consume_whitespace(tokens); 65 | toks.push(GroupThingieEnum::Operation(op, whitespace)); 66 | } else { 67 | let inner = inner_parse(tokens)?; 68 | if matches!(inner, Syntax::Statement(..)) { 69 | toks.push(GroupThingieEnum::Syntax(inner, whitespace)); 70 | break; 71 | } 72 | toks.push(GroupThingieEnum::Syntax(inner, whitespace)); 73 | } 74 | } 75 | } 76 | } 77 | Ok(toks) 78 | } 79 | 80 | fn inner_parse_group_better>( 81 | tokens: &mut Peekable, 82 | spacing: u8, 83 | ) -> SResult { 84 | if spacing == 0 { 85 | return match tokens.next() { 86 | Some(GroupThingieEnum::Syntax(lhs, _)) => Ok(lhs), 87 | Some(GroupThingieEnum::Unary(unary, spc)) => Ok(Syntax::UnaryOperation( 88 | unary, 89 | Box::new(inner_parse_group_better(tokens, spc + 1)?), 90 | )), 91 | Some(other) => Err(format!("Expected expression; got `{other:?}`")), 92 | None => Err(String::from("Unexpected EOF")), 93 | }; 94 | } 95 | if let Some(GroupThingieEnum::Unary(unary, spc)) = tokens.peek() { 96 | let unary = unary.clone(); 97 | let spc = *spc; 98 | tokens.next(); 99 | return Ok(Syntax::UnaryOperation( 100 | unary, 101 | Box::new(inner_parse_group_better(tokens, spc + 1)?), 102 | )); 103 | } 104 | let rhs = inner_parse_group_better(tokens, spacing - 1)?; 105 | // println!("{rhs}"); 106 | let starter_val = match tokens.peek() { 107 | Some(GroupThingieEnum::Operation(op, spc)) if *spc < spacing => { 108 | let op = *op; 109 | tokens.next(); 110 | let lhs = inner_parse_group_better(tokens, spacing)?; 111 | make_operation(lhs, op, rhs) 112 | } 113 | _ => Ok(rhs), 114 | }?; 115 | match tokens.peek() { 116 | Some(GroupThingieEnum::Syntax(_, spc)) if *spc <= spacing => { 117 | let Some(GroupThingieEnum::Syntax(func, _)) = tokens.next() else {unreachable!()}; 118 | let args = match starter_val { 119 | Syntax::Block(args) => args, 120 | other => vec![other], 121 | }; 122 | Ok(Syntax::UnaryOperation( 123 | UnaryOperation::Call(args), 124 | Box::new(func), 125 | )) 126 | } 127 | _ => Ok(starter_val), 128 | } 129 | } 130 | 131 | /// if `op` is `->`, try to make it into a function 132 | fn make_operation(left: Syntax, op: Operation, right: Syntax) -> SResult { 133 | if op == Operation::Arrow { 134 | // println!("{left:?} -> {right:?}"); 135 | let input = match left { 136 | Syntax::Block(vals) => vals 137 | .into_iter() 138 | .map(|syn| match syn { 139 | Syntax::Ident(ident) => Ok(ident), 140 | other => Err(format!( 141 | "Function input can only have identifiers, not {other:?}" 142 | )), 143 | }) 144 | .collect::, _>>()?, 145 | Syntax::Ident(ident) => vec![ident], 146 | other => return Err(format!("Function input can only have identifier or parenthesized list of values; got {other}")), 147 | }; 148 | Ok(Syntax::Function(input, Box::new(right))) 149 | } else { 150 | Ok(Syntax::Operation(Box::new(left), op, Box::new(right))) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/parser/mod.rs: -------------------------------------------------------------------------------- 1 | use std::iter::Peekable; 2 | 3 | use crate::types::prelude::*; 4 | 5 | mod grouping; 6 | 7 | pub fn parse(tokens: Vec) -> SResult { 8 | let mut tokens = tokens.into_iter().peekable(); 9 | let mut syntax = Vec::new(); 10 | while tokens.peek().is_some() { 11 | syntax.push(grouping::parse_group(&mut tokens)?); 12 | } 13 | Ok(optimize(Syntax::Block(syntax))) 14 | } 15 | 16 | fn inner_parse>(tokens: &mut Peekable) -> SResult { 17 | // println!("{:?}", tokens.peek()); 18 | match tokens.next() { 19 | Some(Token::String(str)) => Ok(Syntax::String(str)), 20 | Some(Token::Semicolon) => Ok(Syntax::UnaryOperation( 21 | UnaryOperation::Negate, 22 | Box::new(inner_parse(tokens)?), 23 | )), 24 | Some(Token::Ident(id)) => { 25 | if id.as_ref() == "const" || id.as_ref() == "var" { 26 | consume_whitespace(tokens); 27 | declare(tokens, &id) 28 | } else { 29 | match tokens.peek() { 30 | Some(Token::Colon) => { 31 | tokens.next(); 32 | consume_whitespace(tokens); 33 | get_type(tokens)?; 34 | Ok(Syntax::Ident(id)) 35 | } 36 | // get the value of the variable 37 | _ => Ok(Syntax::Ident(id)), 38 | } 39 | } 40 | } 41 | Some(Token::LSquirrely) => { 42 | let mut statements_buf = Vec::new(); 43 | while let Some(tok) = tokens.peek() { 44 | match tok { 45 | Token::RSquirrely => break, 46 | Token::Space(_) => { 47 | tokens.next(); 48 | continue; 49 | } 50 | _ => {} 51 | } 52 | let inner = grouping::parse_group::(tokens)?; 53 | statements_buf.push(consume_bang(inner, tokens)); 54 | } 55 | if tokens.next() == Some(Token::RSquirrely) { 56 | Ok(Syntax::Block(statements_buf)) 57 | } else { 58 | Err(String::from("Expected `}`")) 59 | } 60 | } 61 | Some(Token::Space(_)) => inner_parse(tokens), 62 | Some(Token::LParen) => { 63 | let val = get_tuple(tokens)?; 64 | if let [x] = &val[..] { 65 | Ok(x.clone()) 66 | } else { 67 | Ok(Syntax::Block(val)) 68 | } 69 | } 70 | Some(other) => Err(format!("Unexpected token `{other:?}`")), 71 | None => Err(String::from("Unexpected End of File")), 72 | } 73 | } 74 | 75 | fn consume_whitespace>(tokens: &mut Peekable) -> u8 { 76 | let mut sp = 0; 77 | while let Some(&Token::Space(s)) = tokens.peek() { 78 | tokens.next(); 79 | sp += s; 80 | } 81 | sp 82 | } 83 | 84 | fn consume_bang>(syn: Syntax, tokens: &mut Peekable) -> Syntax { 85 | match tokens.peek() { 86 | Some(&Token::Bang(q)) => { 87 | tokens.next(); 88 | Syntax::Statement(false, Box::new(syn), q) 89 | } 90 | Some(&Token::Question(q)) => { 91 | tokens.next(); 92 | Syntax::Statement(true, Box::new(syn), q) 93 | } 94 | _ => syn, 95 | } 96 | } 97 | 98 | fn declare>(tokens: &mut Peekable, id: &str) -> SResult { 99 | let Some(Token::Ident(second)) = tokens.next() else { 100 | return Err(String::from("Expected `const` or `var` after `{id}`")) 101 | }; 102 | let var_type = match (id, second.as_ref()) { 103 | ("var", "var") => VarType::VarVar, 104 | ("var", "const") => VarType::VarConst, 105 | ("const", "var") => VarType::ConstVar, 106 | ("const", "const") => VarType::ConstConst, 107 | ("var" | "const", _) => { 108 | return Err(format!( 109 | "Expected `const` or `var` after `{id}`, not `{second}`" 110 | )) 111 | } 112 | _ => unreachable!(), 113 | }; 114 | consume_whitespace(tokens); 115 | let Some(Token::Ident(varname)) = tokens.next() else { 116 | return Err(format!("Expected a variable name after `{id} {second}`")) 117 | }; 118 | consume_whitespace(tokens); 119 | // get a lifetime 120 | let lifetime = match tokens.peek() { 121 | Some(Token::LCaret) => { 122 | tokens.next(); 123 | match tokens.next() { 124 | Some(Token::Ident(ident)) => { 125 | let lt = Lifetime::Ticks(ident.parse().map_err(|err| { 126 | format!("Expected integer lifetime; got `{ident}`; {err}") 127 | })?); 128 | match tokens.next() { 129 | Some(Token::RCaret) => {} 130 | Some(other) => { 131 | return Err(format!( 132 | "Expected `>` after lifetime value; got `{other:?}`" 133 | )) 134 | } 135 | None => return Err(String::from("Unexpected EOF")), 136 | } 137 | lt 138 | } 139 | Some(other) => return Err(format!("Expected integer lifetime; got `{other:?}`")), 140 | None => return Err(String::from("Unexpected EOF")), 141 | } 142 | } 143 | _ => Lifetime::Default, 144 | }; 145 | consume_whitespace(tokens); 146 | // consume a type definition 147 | if tokens.peek() == Some(&Token::Colon) { 148 | tokens.next(); 149 | consume_whitespace(tokens); 150 | get_type(tokens)?; 151 | consume_whitespace(tokens); 152 | } 153 | let value = match tokens.next() { 154 | Some(Token::Bang(_)) => Syntax::Ident(String::new().into()), 155 | Some(Token::Equal(1)) => { 156 | consume_whitespace(tokens); 157 | grouping::parse_group::(tokens)? 158 | } 159 | other => { 160 | return Err(format!( 161 | "Expected `!`, `:`, or `=` after variable name, got `{other:?}`" 162 | )) 163 | } 164 | }; 165 | Ok(consume_bang( 166 | Syntax::Declare(var_type, varname, lifetime, Box::new(value)), 167 | tokens, 168 | )) 169 | } 170 | 171 | fn get_tuple>(tokens: &mut Peekable) -> SResult> { 172 | let mut args_buf = Vec::new(); 173 | while let Some(tok) = tokens.peek() { 174 | match tok { 175 | Token::Comma => { 176 | tokens.next(); 177 | consume_whitespace(tokens); 178 | } 179 | Token::RParen => { 180 | tokens.next(); 181 | break; 182 | } 183 | _ => args_buf.push(grouping::parse_group::(tokens)?), 184 | } 185 | } 186 | Ok(args_buf) 187 | } 188 | 189 | fn get_type>(tokens: &mut Peekable) -> SResult<()> { 190 | match tokens.next() { 191 | Some(Token::Ident(_)) => {} 192 | other => return Err(format!("Expected a type after `:`; got `{other:?}`")), 193 | } 194 | consume_whitespace(tokens); 195 | match tokens.peek() { 196 | Some(Token::LSquare) => { 197 | tokens.next(); 198 | let Some(Token::RSquare) = tokens.next() else { 199 | return Err(String::from("Expected `]` after `[` in type definition")) 200 | }; 201 | } 202 | Some(Token::LCaret) => { 203 | tokens.next(); 204 | get_type(tokens)?; 205 | while tokens.peek() == Some(&Token::Comma) { 206 | tokens.next(); 207 | get_type(tokens)?; 208 | consume_whitespace(tokens); 209 | } 210 | let Some(Token::RCaret) = tokens.next() else { 211 | return Err(String::from("Missing `>` in type definition")) 212 | }; 213 | } 214 | _ => {} 215 | } 216 | Ok(()) 217 | } 218 | 219 | fn optimize(syn: Syntax) -> Syntax { 220 | match syn { 221 | Syntax::Declare(typ, ident, lifetime, inner) => { 222 | Syntax::Declare(typ, ident, lifetime, Box::new(optimize(*inner))) 223 | } 224 | Syntax::Function(args, inner) => Syntax::Function(args, Box::new(optimize(*inner))), 225 | Syntax::UnaryOperation(UnaryOperation::Call(args), func) => Syntax::UnaryOperation( 226 | UnaryOperation::Call(args.into_iter().map(optimize).collect()), 227 | Box::new(optimize(*func)), 228 | ), 229 | Syntax::Operation(lhs, Operation::Dot, rhs) 230 | if 'guard: { 231 | let Syntax::Ident(ref lhs) = *lhs else { break 'guard false }; 232 | let Syntax::Ident(ref rhs) = *rhs else { break 'guard false }; 233 | format!("{lhs}.{rhs}").parse::().is_ok() 234 | } => 235 | { 236 | let Syntax::Ident(lhs) = *lhs else {panic!()}; 237 | let Syntax::Ident(rhs) = *rhs else {panic!()}; 238 | let Ok(float) = format!("{lhs}.{rhs}").parse::() else { panic!()}; 239 | Syntax::Ident(format!("{float}").into()) 240 | } 241 | Syntax::Operation(lhs, op, rhs) => { 242 | Syntax::Operation(Box::new(optimize(*lhs)), op, Box::new(optimize(*rhs))) 243 | } 244 | Syntax::UnaryOperation(unary, operand) => { 245 | Syntax::UnaryOperation(unary, Box::new(optimize(*operand))) 246 | } 247 | Syntax::Block(inner) => { 248 | let mut new_inner: Vec<_> = Vec::with_capacity(inner.len()); 249 | // flatten nested blocks 250 | for item in inner { 251 | match optimize(item) { 252 | Syntax::Block(block) => new_inner.extend(block), 253 | other => new_inner.push(other), 254 | } 255 | } 256 | // replace a block containing a single element with that element 257 | if new_inner.len() == 1 { 258 | new_inner.pop().unwrap() 259 | } else { 260 | Syntax::Block(new_inner) 261 | } 262 | } 263 | Syntax::Statement(is_debug, inner, lvl) => { 264 | Syntax::Statement(is_debug, Box::new(optimize(*inner)), lvl) 265 | } 266 | basic @ (Syntax::Ident(_) | Syntax::String(_)) => basic, 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::types::prelude::*; 2 | 3 | use std::{f64::consts as f64, fmt::Display}; 4 | 5 | fn eval(src: T) -> SResult { 6 | Ok( 7 | crate::interpreter::interpret(&crate::parser::parse(crate::lexer::tokenize(&format!( 8 | "{{{src}}}" 9 | ))?)?)? 10 | .clone_inner(), 11 | ) 12 | } 13 | 14 | macro_rules! assert_eq_db { 15 | ($lhs: expr, $rhs: expr) => { 16 | let ltoks = crate::lexer::tokenize(&format!("{{{}}}", $lhs)).unwrap(); 17 | let rtoks = crate::lexer::tokenize(&format!("{{{}}}", $rhs)).unwrap(); 18 | let lsyn = crate::parser::parse(ltoks).unwrap(); 19 | let rsyn = crate::parser::parse(rtoks).unwrap(); 20 | let lres = crate::interpreter::interpret(&lsyn).unwrap().clone_inner(); 21 | let rres = crate::interpreter::interpret(&rsyn).unwrap().clone_inner(); 22 | assert_eq!(lres, rres, "{lsyn:?} != {rsyn:?}") 23 | }; 24 | } 25 | 26 | #[test] 27 | fn divide() { 28 | assert_eq!(Value::from(1.0) / Value::from(0.0), Value::empty_object()); 29 | assert_eq!(Value::from(5.0) / Value::from(2.0), Value::from(2.5)); 30 | } 31 | 32 | #[test] 33 | fn eq() { 34 | assert_eq!( 35 | Value::from(f64::PI).eq(&Value::from(3.0), 1), 36 | Value::from(true) 37 | ); 38 | assert_eq!( 39 | Value::from(f64::PI).eq(&Value::from(3.0), 2), 40 | Value::from(false) 41 | ); 42 | } 43 | 44 | #[test] 45 | fn maybe_or_and() { 46 | assert_eq_db!("true | true", "true"); 47 | assert_eq_db!("true | true", "true"); 48 | assert_eq_db!("true | false", "true"); 49 | assert_eq_db!("false | false", "false"); 50 | assert_eq_db!("maybe | false", "maybe"); 51 | assert_eq_db!("maybe | true", "true"); 52 | assert_eq_db!("maybe | maybe", "maybe"); 53 | assert_eq_db!("true & true", "true"); 54 | assert_eq_db!("true & false", "false"); 55 | assert_eq_db!("false & false", "false"); 56 | assert_eq_db!("maybe & false", "false"); 57 | assert_eq_db!("maybe & true", "maybe"); 58 | assert_eq_db!("maybe & maybe", "maybe"); 59 | } 60 | 61 | #[test] 62 | fn comparisons() { 63 | assert_eq_db!("`true` === true", "true"); 64 | assert_eq_db!("`true` ==== true", "false"); 65 | assert_eq_db!("`false` === true", "false"); 66 | assert_eq_db!("` TRUE\n\t ` == true", "true"); 67 | assert_eq_db!("` TRUE ` === true", "false"); 68 | 69 | assert_eq_db!("`` == 0", "true"); 70 | assert_eq_db!("`` === 0", "true"); 71 | assert_eq_db!("`` ==== 0", "false"); 72 | assert_eq_db!("0 == ``", "true"); 73 | assert_eq_db!("0 === `0`", "true"); 74 | // assert_eq_db!("0 == `Zero`", "true"); 75 | assert_eq_db!("0 === `Zero`", "false"); 76 | assert_eq_db!("22/7 == 🥧", "true"); 77 | } 78 | 79 | #[test] 80 | fn op_assign() { 81 | assert_eq_db!("{const var count = 0! count += 1! count}", "1"); 82 | assert_eq_db!( 83 | "{const var msg = 'hello'! msg += 'world'! msg}", 84 | "`helloworld`" 85 | ); 86 | assert_eq_db!( 87 | "{const var msg = 'i did '! msg += ;msg! msg}", 88 | "`i did did i`" 89 | ); 90 | } 91 | 92 | #[test] 93 | fn function() { 94 | assert_eq!( 95 | eval("const const does_she_really_like_you = () -> maybe! does_she_really_like_you"), 96 | Ok(Value::Function(Vec::new(), Syntax::Ident("maybe".into()))) 97 | ); 98 | } 99 | 100 | #[test] 101 | fn doc_tests() { 102 | assert_eq_db!(";'hello there'", "'ereht olleh'"); 103 | assert_eq_db!(";true", "false"); 104 | assert_eq_db!("const var age = 1! age += 1! age", "2"); 105 | assert_eq_db!("var const id = 'name'! id = 'main'! id", "'main'"); 106 | assert_eq_db!("var var count = 0! count += 1! count = 2! count", "2"); 107 | assert_eq_db!("var var name = 'john'! name += '!'! name", "'john!'"); 108 | assert_eq_db!("const const 5 = 4! 2 + 2 ==== 5", "true"); 109 | assert_eq_db!("const const true = false! true === false", "true"); 110 | assert_eq_db!("1 + 2*3", "7"); 111 | assert_eq_db!("1+2 * 3", "9"); 112 | assert_eq_db!("`he` + `l`*2 + `o ` + ;`dlrow`", "`hello world`"); 113 | assert_eq_db!("`johnny` * 1.5", "`johnnyjoh`"); 114 | assert_eq_db!("`no lemon ` + ;`no lemon`", "`no lemon nomel on`"); 115 | 116 | eval( 117 | "const const use = (val) -> { 118 | var var obj = {}! 119 | obj.call = (val)->{ 120 | var var ret = self.value! 121 | if(;(val====undefined), 122 | self.value=val 123 | )! 124 | ret}! 125 | obj.value = val! 126 | obj 127 | }! 128 | 129 | const const print = (txt) -> {txt?}! 130 | 131 | ", 132 | ) 133 | .unwrap(); 134 | } 135 | 136 | #[test] 137 | fn string_interpolation() { 138 | assert_eq_db!( 139 | "const const name = `John`! `Hi, I'm ${name}`", 140 | "`Hi, I'm John`" 141 | ); 142 | assert_eq_db!( 143 | "const const name = `John`! `Hi, I'm ¥{name}`", 144 | "`Hi, I'm John`" 145 | ); 146 | assert_eq_db!( 147 | "const const name = `John`! `Hi, I'm {name}€`", 148 | "`Hi, I'm John`" 149 | ); 150 | } 151 | 152 | #[test] 153 | fn eval_tests() { 154 | assert_eq_db!("eval(2)", "2"); 155 | assert_eq_db!("const var x = 1! x += 2! eval(`x`)", "3"); 156 | assert_eq_db!("const var x = 1! x += 2! eval(x)", "3"); 157 | assert_eq_db!("const const x = `'Hello, World!'`! eval(x)", "`Hello, World!`"); 158 | } 159 | -------------------------------------------------------------------------------- /src/types/mod.rs: -------------------------------------------------------------------------------- 1 | pub use prelude::*; 2 | 3 | mod pointer; 4 | mod state; 5 | mod syntax; 6 | mod token; 7 | mod value; 8 | 9 | pub mod prelude { 10 | use std::cell::RefCell; 11 | use std::rc::Rc; 12 | 13 | pub use super::pointer::{MutValue, Pointer}; 14 | pub use super::state::State; 15 | pub use super::syntax::{Lifetime, Operation, Syntax, UnaryOperation, VarType}; 16 | pub use super::token::{StringSegment, Token}; 17 | pub use super::value::{Boolean, Keyword, Value}; 18 | 19 | pub type SResult = Result; 20 | pub type RcMut = Rc>; 21 | 22 | pub fn rc_mut_new(content: T) -> RcMut { 23 | Rc::new(RefCell::new(content)) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/types/pointer.rs: -------------------------------------------------------------------------------- 1 | use core::hash::Hash; 2 | use std::cmp::Ordering; 3 | use std::fmt::{Debug, Display}; 4 | use std::ops::{AddAssign, BitAnd, BitOr, DivAssign, MulAssign, Neg, Rem, RemAssign, SubAssign}; 5 | use std::{ 6 | ops::{Add, Div, Mul, Sub}, 7 | rc::Rc, 8 | }; 9 | 10 | use super::prelude::*; 11 | 12 | /// A pointer to a reference-counted value 13 | /// A `const const` and `var const` can point to the same value, as can a `const var` and `var var`. 14 | #[derive(PartialEq, Eq, Clone)] 15 | pub enum Pointer { 16 | ConstConst(Rc), 17 | ConstVar(RcMut), 18 | VarConst(RcMut>), 19 | VarVar(RcMut>), 20 | } 21 | 22 | #[derive(PartialEq, Eq, Clone, Debug)] 23 | pub struct MutValue { 24 | pub value: Value, 25 | pub previous: Option, 26 | pub event_listeners: Vec<(Syntax, RcMut)>, 27 | next_handles: Vec, 28 | } 29 | 30 | impl MutValue { 31 | pub fn assign(&mut self, value: Value) { 32 | self.previous = Some(core::mem::replace(&mut self.value, value)); 33 | } 34 | 35 | pub fn add_event_listener(&mut self, listener: Syntax, state: RcMut) { 36 | self.event_listeners.push((listener, state)); 37 | } 38 | 39 | pub fn add_next_handle(&mut self, handle: Pointer) { 40 | self.next_handles.push(handle); 41 | } 42 | 43 | pub fn flush_next_handles(&mut self) -> Vec { 44 | core::mem::take(&mut self.next_handles) 45 | } 46 | } 47 | 48 | impl From for MutValue { 49 | fn from(value: Value) -> Self { 50 | Self { 51 | value, 52 | previous: None, 53 | event_listeners: Vec::new(), 54 | next_handles: Vec::new(), 55 | } 56 | } 57 | } 58 | 59 | impl Debug for Pointer { 60 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 61 | match self { 62 | Self::ConstConst(val) => write!(f, "const const ({val:?})"), 63 | Self::ConstVar(val) => write!(f, "const var ({:?})", val.borrow().value), 64 | Self::VarConst(val) => write!(f, "var const ({:?})", val.borrow()), 65 | Self::VarVar(val) => write!(f, "var var ({:?})", val.borrow().borrow().value), 66 | } 67 | } 68 | } 69 | 70 | impl Display for Pointer { 71 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 72 | match self { 73 | Self::ConstConst(val) => write!(f, "{val}"), 74 | Self::VarConst(val) => write!(f, "{}", val.borrow()), 75 | Self::ConstVar(val) => write!(f, "{}", val.borrow().value), 76 | Self::VarVar(val) => write!(f, "{}", val.borrow().borrow().value), 77 | } 78 | } 79 | } 80 | 81 | impl Hash for Pointer { 82 | fn hash(&self, state: &mut H) { 83 | core::mem::discriminant(self).hash(state); 84 | match self { 85 | Self::ConstConst(val) => val.hash(state), 86 | Self::VarConst(val) => val.borrow().hash(state), 87 | Self::ConstVar(var) => var.borrow().value.hash(state), 88 | Self::VarVar(var) => var.borrow().borrow().value.hash(state), 89 | } 90 | } 91 | } 92 | 93 | impl Pointer { 94 | /// Get the value inside the pointer. This is not a deep clone and should be treated as a reference 95 | pub fn clone_inner(&self) -> Value { 96 | match self { 97 | Self::ConstConst(val) => (**val).clone(), 98 | Self::VarConst(val) => (**val.borrow()).clone(), 99 | Self::VarVar(val) => (*val).borrow().borrow().value.clone(), 100 | Self::ConstVar(val) => (**val).borrow().value.clone(), 101 | } 102 | } 103 | 104 | /// Convert this pointer to a different type. Performs a shallow clone if switching between inner `const` and `var` 105 | pub fn convert(&self, vt: VarType) -> Self { 106 | match vt { 107 | VarType::ConstConst => Self::ConstConst(self.make_const()), 108 | VarType::ConstVar => Self::ConstVar(self.make_var()), 109 | VarType::VarConst => Self::VarConst(rc_mut_new(self.make_const())), 110 | VarType::VarVar => Self::VarVar(rc_mut_new(self.make_var())), 111 | } 112 | } 113 | 114 | // /// Make a new pointer from a value with a given type 115 | // pub fn from_value(val: Value, vt: VarType) -> Self { 116 | // match vt { 117 | // VarType::ConstConst => Self::ConstConst(Rc::new(val)), 118 | // VarType::ConstVar => Self::ConstVar(rc_mut_new(val)), 119 | // VarType::VarConst => Self::VarConst(rc_mut_new(Rc::new(val))), 120 | // VarType::VarVar => Self::VarVar(rc_mut_new(rc_mut_new(val))), 121 | // } 122 | // } 123 | 124 | /// check equality with a given precision. Returns a `const const` pointer to a boolean 125 | /// 126 | /// 1. internal data must be pretty similar 127 | /// 2. internal data must be identical with type coercion 128 | /// 3. internal data must be identical without type coercion 129 | /// 4. internal pointers must be identical 130 | pub fn eq(&self, rhs: &Self, precision: u8) -> Self { 131 | if precision >= 4 { 132 | Self::from(match (self, rhs) { 133 | ( 134 | Self::ConstConst(_) | Self::VarConst(_), 135 | Self::ConstConst(_) | Self::VarConst(_), 136 | ) => unsafe { 137 | core::mem::transmute::, u64>(self.make_const()) 138 | == core::mem::transmute::, u64>(rhs.make_const()) 139 | }, 140 | (Self::ConstVar(_) | Self::VarVar(_), Self::ConstVar(_) | Self::VarVar(_)) => unsafe { 141 | core::mem::transmute::, u64>(self.make_var()) 142 | == core::mem::transmute::, u64>(rhs.make_var()) 143 | }, 144 | _ => false, 145 | }) 146 | } else { 147 | Self::from(self.with_refs(rhs, |val, rhs| val.eq(rhs, precision))) 148 | } 149 | } 150 | 151 | /// Apply the dot operator; object indexing. Otherwise, it returns `undefined` 152 | #[allow(clippy::option_if_let_else, clippy::single_match_else)] 153 | pub fn dot(&self, rhs: &Value) -> Self { 154 | let allow_modify = matches!(self, Self::ConstVar(_) | Self::VarVar(_)); 155 | let lhs = self.clone_inner(); 156 | match (lhs, rhs) { 157 | (Value::Object(mut obj), key) => match obj.get(key) { 158 | Some(ptr) => ptr.clone(), 159 | None => { 160 | let val = if allow_modify { 161 | Self::ConstVar(rc_mut_new(Value::empty_object().into())) 162 | } else { 163 | Self::ConstConst(Rc::new(Value::empty_object())) 164 | }; 165 | obj.insert(key.clone(), val.clone()); 166 | val 167 | } 168 | }, 169 | (Value::String(str), Value::Number(num)) => match *num as i32 { 170 | i @ ..=-2 => str.chars().nth(str.len() + ((-i + 1) as usize)), 171 | i @ -1.. => str.chars().nth((i + 1) as usize), 172 | } 173 | .map_or_else( 174 | || Self::from(Value::empty_object()), 175 | |ch| Self::from(Value::String(String::from(ch).into())), 176 | ), 177 | _ => Self::from(Value::empty_object()), 178 | } 179 | } 180 | 181 | /// Try to replace the current value with given value. Returns `Err` if `self` is ptr-const. Doesn't clone if it's not necessary. 182 | pub fn assign(&self, rhs: &Self) -> SResult<()> { 183 | match self { 184 | Self::ConstConst(_) => Err(format!("Can't assign to a `const const` {self:?}")), 185 | Self::ConstVar(_) => Err(format!("Can't assign to a `const var` {self:?}")), 186 | Self::VarConst(ptr) => { 187 | ptr.replace(rhs.make_const()); 188 | Ok(()) 189 | } 190 | Self::VarVar(ptr) => { 191 | // println!("{self:?} => {rhs:?}"); 192 | ptr.replace(rhs.make_var()); 193 | // println!("{self:?}"); 194 | Ok(()) 195 | } 196 | } 197 | } 198 | 199 | /// Convert to an immutable reference to a value. If `self` is value-var, clones the internal value. 200 | /// 201 | /// If you want to use a reference to the value inside of a function, look into `Pointer::with_ref`. 202 | pub fn make_const(&self) -> Rc { 203 | match self { 204 | Self::ConstConst(val) => val.clone(), 205 | Self::VarConst(val) => val.borrow().clone(), 206 | Self::ConstVar(val) => Rc::new(val.borrow().value.clone()), 207 | Self::VarVar(val) => Rc::new(val.borrow().borrow().value.clone()), 208 | } 209 | } 210 | 211 | /// Convert to a mutable reference to a value. If `self` is value-const, clones the internal value 212 | pub fn make_var(&self) -> RcMut { 213 | match self { 214 | Self::ConstConst(val) => rc_mut_new(val.as_ref().clone().into()), 215 | Self::VarConst(val) => rc_mut_new(val.borrow().as_ref().clone().into()), 216 | Self::ConstVar(val) => val.clone(), 217 | Self::VarVar(val) => val.borrow().clone(), 218 | } 219 | } 220 | 221 | /// Convert to a mutable reference to a value. If `self` is value-const, returns None 222 | pub fn as_var(&self) -> Option> { 223 | match self { 224 | Self::ConstConst(_) | Self::VarConst(_) => None, 225 | Self::ConstVar(val) => Some(val.clone()), 226 | Self::VarVar(val) => Some(val.borrow().clone()), 227 | } 228 | } 229 | 230 | /// Run a function on a reference to the internal value. This does not clone the internal value. 231 | pub fn with_ref T>(&self, func: F) -> T { 232 | match self { 233 | Self::ConstConst(val) => func(val.as_ref()), 234 | Self::VarConst(val) => func(val.borrow().as_ref()), 235 | Self::ConstVar(val) => func(&val.borrow().value), 236 | Self::VarVar(val) => func(&val.borrow().borrow().value), 237 | } 238 | } 239 | 240 | /// Run a function on a pair of references to two different Pointers. 241 | pub fn with_refs T>(&self, other: &Self, func: F) -> T { 242 | self.with_ref(|val| other.with_ref(|other| func(val, other))) 243 | } 244 | } 245 | 246 | impl PartialEq for Pointer { 247 | fn eq(&self, other: &Value) -> bool { 248 | self.with_ref(|r| r == other) 249 | } 250 | } 251 | 252 | impl PartialOrd for Pointer { 253 | fn partial_cmp(&self, other: &Self) -> Option { 254 | self.with_refs(other, std::cmp::PartialOrd::partial_cmp) 255 | } 256 | } 257 | 258 | impl Add for Pointer { 259 | type Output = Self; 260 | 261 | fn add(self, rhs: Self) -> Self::Output { 262 | Self::from(self.clone_inner() + rhs.clone_inner()) 263 | } 264 | } 265 | 266 | impl AddAssign for Pointer { 267 | fn add_assign(&mut self, rhs: Self) { 268 | let output = self.clone_inner() + rhs.clone_inner(); 269 | match self { 270 | Self::ConstVar(val) => { 271 | val.borrow_mut().assign(output); 272 | } 273 | Self::VarVar(val) => { 274 | val.borrow().borrow_mut().assign(output); 275 | } 276 | _ => {} 277 | }; 278 | } 279 | } 280 | 281 | impl Sub for Pointer { 282 | type Output = Self; 283 | 284 | fn sub(self, rhs: Self) -> Self::Output { 285 | Self::from(self.clone_inner() - rhs.clone_inner()) 286 | } 287 | } 288 | 289 | impl SubAssign for Pointer { 290 | fn sub_assign(&mut self, rhs: Self) { 291 | let output = self.clone_inner() - rhs.clone_inner(); 292 | match self { 293 | Self::ConstVar(val) => { 294 | val.borrow_mut().assign(output); 295 | } 296 | Self::VarVar(val) => { 297 | val.borrow().borrow_mut().assign(output); 298 | } 299 | _ => {} 300 | }; 301 | } 302 | } 303 | 304 | impl Mul for Pointer { 305 | type Output = Self; 306 | 307 | fn mul(self, rhs: Self) -> Self::Output { 308 | Self::from(self.clone_inner() * rhs.clone_inner()) 309 | } 310 | } 311 | 312 | impl MulAssign for Pointer { 313 | fn mul_assign(&mut self, rhs: Self) { 314 | let output = self.clone_inner() * rhs.clone_inner(); 315 | match self { 316 | Self::ConstVar(val) => { 317 | val.borrow_mut().assign(output); 318 | } 319 | Self::VarVar(val) => { 320 | val.borrow().borrow_mut().assign(output); 321 | } 322 | _ => {} 323 | }; 324 | } 325 | } 326 | 327 | impl Div for Pointer { 328 | type Output = Self; 329 | 330 | fn div(self, rhs: Self) -> Self::Output { 331 | Self::from(self.clone_inner() / rhs.clone_inner()) 332 | } 333 | } 334 | 335 | impl DivAssign for Pointer { 336 | fn div_assign(&mut self, rhs: Self) { 337 | let output = self.clone_inner() / rhs.clone_inner(); 338 | match self { 339 | Self::ConstVar(val) => { 340 | val.borrow_mut().assign(output); 341 | } 342 | Self::VarVar(val) => { 343 | val.borrow().borrow_mut().assign(output); 344 | } 345 | _ => {} 346 | }; 347 | } 348 | } 349 | 350 | impl Neg for Pointer { 351 | type Output = Self; 352 | fn neg(self) -> Self::Output { 353 | Self::from(-self.clone_inner()) 354 | } 355 | } 356 | 357 | impl Rem for Pointer { 358 | type Output = Self; 359 | 360 | fn rem(self, rhs: Self) -> Self::Output { 361 | Self::from(self.clone_inner() % rhs.clone_inner()) 362 | } 363 | } 364 | 365 | impl RemAssign for Pointer { 366 | fn rem_assign(&mut self, rhs: Self) { 367 | let _ = self.assign(&(self.clone() % rhs)); 368 | } 369 | } 370 | 371 | impl BitAnd for Pointer { 372 | type Output = Self; 373 | 374 | fn bitand(self, rhs: Self) -> Self::Output { 375 | Self::from(self.clone_inner() & rhs.clone_inner()) 376 | } 377 | } 378 | 379 | impl BitOr for Pointer { 380 | type Output = Self; 381 | 382 | fn bitor(self, rhs: Self) -> Self::Output { 383 | Self::from(self.clone_inner() | rhs.clone_inner()) 384 | } 385 | } 386 | 387 | impl From for Pointer { 388 | fn from(value: Value) -> Self { 389 | Self::ConstConst(Rc::new(value)) 390 | } 391 | } 392 | 393 | impl From for Pointer { 394 | fn from(value: bool) -> Self { 395 | Self::ConstConst(Rc::new(Value::from(value))) 396 | } 397 | } 398 | 399 | impl From<&str> for Pointer { 400 | fn from(value: &str) -> Self { 401 | Self::ConstConst(Rc::new(Value::String(value.into()))) 402 | } 403 | } 404 | 405 | impl From> for Pointer { 406 | fn from(value: Rc) -> Self { 407 | Self::ConstConst(Rc::new(Value::String(value))) 408 | } 409 | } 410 | 411 | impl From for Pointer { 412 | fn from(value: f64) -> Self { 413 | Self::ConstConst(Rc::new(Value::Number(value))) 414 | } 415 | } 416 | -------------------------------------------------------------------------------- /src/types/state.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::RefCell, 3 | collections::{BTreeMap, HashMap}, 4 | rc::Rc, 5 | }; 6 | 7 | use lazy_regex::regex; 8 | 9 | use crate::types::prelude::*; 10 | 11 | use core::f64::consts as f64; 12 | 13 | #[derive(Debug, PartialEq, Eq)] 14 | pub struct State { 15 | current: HashMap, (Pointer, Lifetime)>, 16 | parent: Option>, 17 | pub undefined: Pointer, 18 | } 19 | 20 | macro_rules! kw { 21 | ($current:ident $str:expr => $kw:expr) => { 22 | $current.insert( 23 | $str.into(), 24 | (Pointer::from(Value::from($kw)), Lifetime::Default), 25 | ) 26 | }; 27 | } 28 | 29 | impl State { 30 | pub fn new() -> Self { 31 | let mut current = HashMap::new(); 32 | 33 | kw!(current "🥧" => f64::PI); 34 | kw!(current "class" => Keyword::Class); 35 | kw!(current "className" => Keyword::Class); 36 | kw!(current "const" => Keyword::Const); 37 | kw!(current "current" => Keyword::Current); 38 | kw!(current "delete" => Keyword::Delete); 39 | kw!(current "eval" => Keyword::Eval); 40 | kw!(current "false" => false); 41 | kw!(current "forget" => Keyword::Forget); 42 | kw!(current "if" => Keyword::If); 43 | kw!(current "infinity" => Value::Number(f64::INFINITY)); 44 | kw!(current "maybe" => Boolean::Maybe); 45 | kw!(current "next" => Keyword::Next); 46 | kw!(current "new" => Keyword::New); 47 | kw!(current "previous" => Keyword::Previous); 48 | kw!(current "true" => true); 49 | kw!(current "var" => Keyword::Var); 50 | kw!(current "when" => Keyword::When); 51 | kw!(current "∞" => Value::Number(f64::INFINITY)); 52 | 53 | let undefined = Pointer::ConstConst(Rc::new(Value::empty_object())); 54 | current.insert("undefined".into(), (undefined.clone(), Lifetime::Default)); 55 | Self { 56 | current, 57 | parent: None, 58 | undefined, 59 | } 60 | } 61 | 62 | pub fn from_parent(parent: Rc>) -> Self { 63 | let undefined = parent.borrow().undefined.clone(); 64 | Self { 65 | current: HashMap::new(), 66 | undefined, 67 | parent: Some(parent), 68 | } 69 | } 70 | 71 | pub fn get(&mut self, key: Rc) -> Pointer { 72 | // println!("{:?}: {key}", self.current); 73 | // if there's a value here, get it 74 | if let Some(val) = self.current.get(&key) { 75 | return val.0.clone(); 76 | } 77 | // if there's a value in the parent, get it 78 | if let Some(parent) = &self.parent { 79 | return (**parent).borrow_mut().get(key); 80 | } 81 | // otherwise, parse it in global context 82 | if let Ok(val) = key.parse() { 83 | let new_val = Pointer::ConstConst(Rc::new(Value::Number(val))); 84 | self.current 85 | .insert(key, (new_val.clone(), Lifetime::Default)); 86 | new_val 87 | } else if regex!("^f?u?n?c?t?i?o?n?$").is_match(&key) { 88 | let v = Pointer::ConstConst(Rc::new(Value::Keyword(Keyword::Function))); 89 | self.current.insert(key, (v.clone(), Lifetime::Default)); 90 | v 91 | } else { 92 | let v = Pointer::ConstConst(Rc::new(Value::String(key.clone()))); 93 | self.current.insert(key, (v.clone(), Lifetime::Default)); 94 | v 95 | } 96 | } 97 | 98 | pub fn insert(&mut self, key: Rc, value: Pointer, lifetime: Lifetime) { 99 | self.current.insert(key, (value, lifetime)); 100 | } 101 | 102 | pub fn delete(&mut self, k: Rc) { 103 | match self.current.entry(k.clone()) { 104 | std::collections::hash_map::Entry::Occupied(mut e) => { 105 | e.insert((self.undefined.clone(), Lifetime::Default)); 106 | } 107 | std::collections::hash_map::Entry::Vacant(e) => { 108 | if let Some(parent) = &self.parent { 109 | parent.borrow_mut().delete(k); 110 | } else { 111 | e.insert((self.undefined.clone(), Lifetime::Default)); 112 | } 113 | } 114 | } 115 | } 116 | 117 | pub fn locals_to_object(&self) -> BTreeMap { 118 | self.current 119 | .iter() 120 | .map(|(k, v)| (Value::String(k.clone()), v.0.clone())) 121 | .collect() 122 | } 123 | 124 | pub fn tick(&mut self) { 125 | let keys = self.current.keys().cloned().collect::>(); 126 | for k in keys { 127 | match self.current.get_mut(&k) { 128 | Some((_, Lifetime::Ticks(t @ ..=-1))) => { 129 | *t += 1; 130 | } 131 | Some((_, Lifetime::Ticks(t @ 1..))) => { 132 | *t -= 1; 133 | } 134 | Some((_, Lifetime::Ticks(0))) => { 135 | self.delete(k); 136 | } 137 | _ => {} 138 | } 139 | } 140 | if let Some(parent) = &self.parent { 141 | parent.borrow_mut().tick(); 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/types/syntax.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Display, hash::Hash, rc::Rc}; 2 | 3 | use super::{StringSegment, Token}; 4 | 5 | #[derive(PartialEq, Debug, Clone, Eq, Hash)] 6 | pub enum Syntax { 7 | Declare(VarType, Rc, Lifetime, Box), 8 | Function(Vec>, Box), 9 | Operation(Box, Operation, Box), 10 | UnaryOperation(UnaryOperation, Box), 11 | Ident(Rc), 12 | String(Vec), 13 | Block(Vec), 14 | Statement(bool, Box, u8), 15 | } 16 | 17 | impl Display for Syntax { 18 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 19 | match self { 20 | Self::Statement(is_debug, content, count) => { 21 | write!( 22 | f, 23 | "{content}{}", 24 | if *is_debug { "?" } else { "!" }.repeat(*count as usize) 25 | ) 26 | } 27 | Self::Block(statements) => { 28 | write!(f, "{{")?; 29 | for statement in statements { 30 | write!(f, "{statement} ")?; 31 | } 32 | write!(f, "}}") 33 | } 34 | Self::String(segments) => { 35 | write!(f, "\"")?; 36 | for segment in segments { 37 | write!(f, "{segment}")?; 38 | } 39 | write!(f, "\"") 40 | } 41 | Self::Ident(ident) => write!(f, "{ident}"), 42 | Self::Declare(var_type, name, lifetime, value) => { 43 | write!(f, "{var_type} {name}{lifetime} = {value}") 44 | } 45 | Self::Operation(lhs, op, rhs) => { 46 | write!(f, "({lhs}{op}{rhs})") 47 | } 48 | Self::UnaryOperation(UnaryOperation::Call(args), operand) => { 49 | write!(f, "{operand}({args:?})") 50 | } 51 | Self::UnaryOperation(UnaryOperation::Decrement, operand) => { 52 | write!(f, "{operand}--") 53 | } 54 | Self::UnaryOperation(UnaryOperation::Increment, operand) => { 55 | write!(f, "{operand}++") 56 | } 57 | Self::Function(args, body) => { 58 | write!(f, "{args:?} -> {body}") 59 | } 60 | Self::UnaryOperation(UnaryOperation::Negate, inner) => write!(f, ";{inner}"), 61 | // other => write!(f, "{other:?}"), 62 | } 63 | } 64 | } 65 | 66 | #[derive(PartialEq, Eq, Debug, Hash, Clone, Copy)] 67 | pub enum VarType { 68 | ConstConst, 69 | ConstVar, 70 | VarConst, 71 | VarVar, 72 | // ConstConstConst, 73 | } 74 | 75 | impl Display for VarType { 76 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 77 | match self { 78 | Self::ConstConst => write!(f, "const const"), 79 | Self::ConstVar => write!(f, "const var"), 80 | Self::VarConst => write!(f, "var const"), 81 | Self::VarVar => write!(f, "var var"), 82 | } 83 | } 84 | } 85 | 86 | #[derive(Debug, Clone, PartialEq, Hash, Eq)] 87 | pub enum UnaryOperation { 88 | Increment, 89 | Decrement, 90 | Negate, 91 | Call(Vec), 92 | } 93 | 94 | #[derive(PartialEq, Eq, Debug, Hash, Clone, Copy)] 95 | pub enum Operation { 96 | Equal(u8), 97 | Add, 98 | AddEq, 99 | Sub, 100 | SubEq, 101 | Mul, 102 | MulEq, 103 | Div, 104 | DivEq, 105 | Mod, 106 | ModEq, 107 | Dot, 108 | And, 109 | Or, 110 | Arrow, 111 | Lt, 112 | Le, 113 | Gt, 114 | Ge, 115 | } 116 | 117 | impl Display for Operation { 118 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 119 | match self { 120 | Self::Equal(count) => write!(f, "{}", "=".repeat(*count as usize)), 121 | Self::Add => write!(f, "+"), 122 | Self::AddEq => write!(f, "+="), 123 | Self::Sub => write!(f, "-"), 124 | Self::SubEq => write!(f, "-="), 125 | Self::Mul => write!(f, "*"), 126 | Self::MulEq => write!(f, "*="), 127 | Self::Div => write!(f, "/"), 128 | Self::DivEq => write!(f, "/="), 129 | Self::Mod => write!(f, "%"), 130 | Self::ModEq => write!(f, "%="), 131 | Self::Dot => write!(f, "."), 132 | Self::And => write!(f, "&&"), 133 | Self::Or => write!(f, "||"), 134 | Self::Arrow => write!(f, "->"), 135 | Self::Lt => write!(f, "<"), 136 | Self::Le => write!(f, "<="), 137 | Self::Gt => write!(f, ">"), 138 | Self::Ge => write!(f, ">="), 139 | } 140 | } 141 | } 142 | 143 | impl TryFrom for Operation { 144 | type Error = (); 145 | fn try_from(value: Token) -> Result { 146 | match value { 147 | Token::Equal(val) => Ok(Self::Equal(val)), 148 | Token::Plus => Ok(Self::Add), 149 | Token::PlusEq => Ok(Self::AddEq), 150 | Token::Tack => Ok(Self::Sub), 151 | Token::TackEq => Ok(Self::SubEq), 152 | Token::Star => Ok(Self::Mul), 153 | Token::StarEq => Ok(Self::MulEq), 154 | Token::Slash => Ok(Self::Div), 155 | Token::SlashEq => Ok(Self::DivEq), 156 | Token::Percent => Ok(Self::Mod), 157 | Token::PercentEq => Ok(Self::ModEq), 158 | Token::Dot => Ok(Self::Dot), 159 | Token::And => Ok(Self::And), 160 | Token::Or => Ok(Self::Or), 161 | Token::Arrow => Ok(Self::Arrow), 162 | Token::LCaret => Ok(Self::Lt), 163 | Token::LCaretEq => Ok(Self::Le), 164 | Token::RCaret => Ok(Self::Gt), 165 | Token::RCaretEq => Ok(Self::Ge), 166 | _ => Err(()), 167 | } 168 | } 169 | } 170 | 171 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)] 172 | pub enum Lifetime { 173 | #[default] 174 | Default, 175 | Ticks(i32), 176 | } 177 | 178 | impl Display for Lifetime { 179 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 180 | match self { 181 | Self::Default => write!(f, ""), 182 | Self::Ticks(t) => write!(f, "<{t}>"), 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/types/token.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Display, rc::Rc}; 2 | 3 | #[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone)] 4 | pub enum StringSegment { 5 | String(Rc), 6 | Ident(Rc), 7 | /// The symbol for the Cape Verdean escudo is placed in the decimal separator position, as in 2$50. 8 | /// Developers from the Republic of Cape Verde can benefit from this syntax: `{e$code}` 9 | Escudo(Rc, Rc), 10 | } 11 | 12 | impl Display for StringSegment { 13 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 14 | match self { 15 | Self::Ident(ident) => write!(f, "${{{ident}}}"), 16 | Self::String(str) => write!(f, "{str}"), 17 | Self::Escudo(var, field) => write!(f, "{{{var}${field}}}"), 18 | } 19 | } 20 | } 21 | 22 | #[derive(Debug, PartialEq, Eq, Clone)] 23 | pub enum Token { 24 | Ident(Rc), 25 | String(Vec), 26 | Space(u8), 27 | Equal(u8), 28 | Bang(u8), 29 | Question(u8), 30 | Plus, 31 | PlusPlus, 32 | PlusEq, 33 | Tack, 34 | TackTack, 35 | TackEq, 36 | Star, 37 | StarEq, 38 | Slash, 39 | SlashEq, 40 | Percent, 41 | PercentEq, 42 | LCaret, 43 | LCaretEq, 44 | RCaret, 45 | RCaretEq, 46 | LParen, 47 | RParen, 48 | LSquirrely, 49 | RSquirrely, 50 | LSquare, 51 | RSquare, 52 | Arrow, 53 | Semicolon, 54 | Comma, 55 | Colon, 56 | Dot, 57 | And, 58 | Or, 59 | } 60 | -------------------------------------------------------------------------------- /src/types/value.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cmp::Ordering, 3 | collections::BTreeMap, 4 | fmt::{Debug, Display}, 5 | hash::Hash, 6 | ops::{Add, BitAnd, BitOr, Div, Mul, Neg, Rem, Sub}, 7 | rc::Rc, 8 | }; 9 | 10 | use super::{Pointer, Syntax}; 11 | 12 | #[derive(PartialEq, Eq, Debug, Hash, Clone, Copy, PartialOrd, Ord)] 13 | pub enum Boolean { 14 | True, 15 | False, 16 | Maybe, 17 | } 18 | 19 | impl Display for Boolean { 20 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 21 | match self { 22 | Self::True => write!(f, "true"), 23 | Self::False => write!(f, "false"), 24 | Self::Maybe => write!(f, "maybe"), 25 | } 26 | } 27 | } 28 | 29 | impl From for Boolean { 30 | fn from(value: bool) -> Self { 31 | if value { 32 | Self::True 33 | } else { 34 | Self::False 35 | } 36 | } 37 | } 38 | 39 | #[derive(PartialEq, Clone)] 40 | #[repr(C)] 41 | pub enum Value { 42 | Boolean(Boolean), 43 | String(Rc), 44 | Number(f64), 45 | Object(BTreeMap), 46 | Function(Vec>, Syntax), 47 | Class(Vec), 48 | Keyword(Keyword), 49 | } 50 | 51 | impl Eq for Value {} 52 | 53 | impl Default for Value { 54 | fn default() -> Self { 55 | Self::Object(BTreeMap::new()) 56 | } 57 | } 58 | 59 | impl PartialOrd for Value { 60 | fn partial_cmp(&self, other: &Self) -> Option { 61 | match unsafe { 62 | core::mem::transmute::<_, u64>(core::mem::discriminant(self)).cmp( 63 | &core::mem::transmute::<_, u64>(core::mem::discriminant(other)), 64 | ) 65 | } { 66 | Ordering::Equal => {} 67 | other => return Some(other), 68 | } 69 | match (self, other) { 70 | (Self::Number(lhs), Self::Number(rhs)) => lhs.partial_cmp(rhs), 71 | (Self::String(lhs), Self::String(rhs)) => lhs.partial_cmp(rhs), 72 | (Self::Boolean(lhs), Self::Boolean(rhs)) => lhs.partial_cmp(rhs), 73 | (Self::Keyword(lhs), Self::Keyword(rhs)) => lhs.partial_cmp(rhs), 74 | _ => todo!(), 75 | } 76 | } 77 | } 78 | 79 | impl Ord for Value { 80 | fn cmp(&self, other: &Self) -> Ordering { 81 | if let Some(ord) = self.partial_cmp(other) { 82 | return ord; 83 | }; 84 | todo!() 85 | } 86 | } 87 | 88 | impl Debug for Value { 89 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 90 | match self { 91 | Self::String(str) => write!(f, "{str:?}"), 92 | this => write!(f, "{this}"), 93 | } 94 | } 95 | } 96 | 97 | impl Display for Value { 98 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 99 | match self { 100 | Self::Boolean(b) => write!(f, "{b}"), 101 | Self::String(str) => write!(f, "{str}"), 102 | Self::Number(num) => write!(f, "{num}"), 103 | Self::Object(obj) => { 104 | if obj.is_empty() { 105 | write!(f, "undefined") 106 | } else { 107 | let mut map = f.debug_struct("object"); 108 | for (k, v) in obj { 109 | map.field(&format!("{k}"), v); 110 | } 111 | map.finish() 112 | } 113 | } 114 | Self::Function(args, body) => { 115 | write!(f, "{args:?} -> {body}") 116 | } 117 | Self::Class(syn) => { 118 | write!(f, "class {{{syn:?}}}") 119 | } 120 | 121 | Self::Keyword(kw) => write!(f, "{kw}"), 122 | } 123 | } 124 | } 125 | 126 | impl Hash for Value { 127 | fn hash(&self, state: &mut H) { 128 | core::mem::discriminant(self).hash(state); 129 | match self { 130 | Self::Boolean(bool) => bool.hash(state), 131 | Self::String(str) => str.hash(state), 132 | Self::Number(float) => (*float).to_bits().hash(state), 133 | Self::Object(obj) => { 134 | let mut vec: Vec<_> = obj.iter().collect::>(); 135 | vec.sort_by_key(|&(k, _)| k); 136 | for (k, v) in vec { 137 | (k, v).hash(state); 138 | } 139 | } 140 | Self::Function(inputs, content) => (inputs, content).hash(state), 141 | Self::Class(body) => body.hash(state), 142 | Self::Keyword(keyword) => keyword.hash(state), 143 | } 144 | } 145 | } 146 | 147 | impl From for Value { 148 | fn from(value: bool) -> Self { 149 | Self::Boolean(Boolean::from(value)) 150 | } 151 | } 152 | 153 | impl Add for Value { 154 | type Output = Self; 155 | fn add(self, rhs: Self) -> Self::Output { 156 | match (self, rhs) { 157 | (Self::Number(lhs), Self::Number(rhs)) => Self::Number(lhs + rhs), 158 | (Self::Boolean(bool), Self::Number(num)) | (Self::Number(num), Self::Boolean(bool)) => { 159 | Self::Number( 160 | match bool { 161 | Boolean::False => 0.0, 162 | Boolean::Maybe => 0.5, 163 | Boolean::True => 1.0, 164 | } + num, 165 | ) 166 | } 167 | (Self::String(lhs), rhs) => { 168 | Self::String((String::from(&*lhs) + &rhs.to_string()).into()) 169 | } 170 | _ => Self::default(), 171 | } 172 | } 173 | } 174 | 175 | impl Sub for Value { 176 | type Output = Self; 177 | fn sub(self, rhs: Self) -> Self::Output { 178 | match (self, rhs) { 179 | (Self::Number(lhs), Self::Number(rhs)) => Self::Number(lhs - rhs), 180 | _ => Self::default(), 181 | } 182 | } 183 | } 184 | 185 | impl Mul for Value { 186 | type Output = Self; 187 | #[allow( 188 | clippy::cast_possible_truncation, 189 | clippy::cast_sign_loss, 190 | clippy::cast_precision_loss 191 | )] 192 | fn mul(self, rhs: Self) -> Self::Output { 193 | match (self, rhs) { 194 | (Self::Number(lhs), Self::Number(rhs)) => Self::Number(lhs * rhs), 195 | (Self::String(str), Self::Number(num)) => { 196 | let mut str_buf = str.repeat(num.abs().floor() as usize); 197 | let portion = ((num.abs() - num.abs().floor()) * str.len() as f64) as usize; 198 | if portion > 0 { 199 | str_buf.push_str(&str[0..portion]); 200 | } 201 | if num.is_sign_negative() { 202 | str_buf = str_buf.chars().rev().collect(); 203 | } 204 | Self::String(str_buf.into()) 205 | } 206 | (Self::Function(lhs_args, lhs), Self::Function(rhs_args, rhs)) 207 | if lhs_args.len() == 1 => 208 | { 209 | Self::Function( 210 | rhs_args, 211 | Syntax::UnaryOperation( 212 | super::UnaryOperation::Call(vec![rhs]), 213 | Box::new(Syntax::Function(lhs_args, Box::new(lhs))), 214 | ), 215 | ) 216 | } 217 | _ => Self::default(), 218 | } 219 | } 220 | } 221 | 222 | impl Div for Value { 223 | type Output = Self; 224 | fn div(self, rhs: Self) -> Self::Output { 225 | match (self, rhs) { 226 | (Self::Number(lhs), Self::Number(rhs)) => { 227 | if rhs == 0.0 { 228 | Self::default() 229 | } else { 230 | Self::Number(lhs / rhs) 231 | } 232 | } 233 | _ => Self::default(), 234 | } 235 | } 236 | } 237 | 238 | impl Rem for Value { 239 | type Output = Self; 240 | fn rem(self, rhs: Self) -> Self::Output { 241 | match (self, rhs) { 242 | (Self::Number(lhs), Self::Number(rhs)) => { 243 | if rhs == 0.0 { 244 | Self::default() 245 | } else { 246 | Self::Number(lhs % rhs) 247 | } 248 | } 249 | _ => Self::default(), 250 | } 251 | } 252 | } 253 | 254 | impl Neg for Value { 255 | type Output = Self; 256 | fn neg(self) -> Self::Output { 257 | match self { 258 | Self::Boolean(Boolean::False) => Self::Boolean(Boolean::True), 259 | Self::Boolean(Boolean::True) => Self::Boolean(Boolean::False), 260 | Self::Boolean(Boolean::Maybe) => Self::Boolean(Boolean::Maybe), 261 | Self::Number(num) => Self::Number(-num), 262 | Self::String(str) => Self::String(str.chars().rev().collect::().into()), 263 | _ => Self::default(), 264 | } 265 | } 266 | } 267 | 268 | impl BitAnd for Value { 269 | type Output = Self; 270 | fn bitand(self, rhs: Self) -> Self::Output { 271 | match (self.bool(), rhs.bool()) { 272 | (Boolean::False, _) | (_, Boolean::False) => Self::from(false), 273 | (Boolean::True, Boolean::True) => Self::from(true), 274 | _ => Self::Boolean(Boolean::Maybe), 275 | } 276 | } 277 | } 278 | 279 | impl BitOr for Value { 280 | type Output = Self; 281 | fn bitor(self, rhs: Self) -> Self::Output { 282 | match (self.bool(), rhs.bool()) { 283 | (Boolean::True, _) | (_, Boolean::True) => Self::from(true), 284 | (Boolean::False, Boolean::False) => Self::from(false), 285 | _ => Self::Boolean(Boolean::Maybe), 286 | } 287 | } 288 | } 289 | 290 | impl Value { 291 | pub fn eq(&self, rhs: &Self, precision: u8) -> Self { 292 | if precision <= 2 && self.bool() == Boolean::False && rhs.bool() == Boolean::False { 293 | return Self::from(true); 294 | } 295 | // true == `aaa` 296 | if precision == 1 { 297 | if let (&Self::Boolean(bool), rhs) | (rhs, &Self::Boolean(bool)) = (self, rhs) { 298 | if rhs.bool() == bool { 299 | return Self::from(true); 300 | } 301 | } 302 | } 303 | if precision == 2 { 304 | return Self::from(format!("{self}") == format!("{rhs}")); 305 | } else if precision == 1 306 | && format!("{self}").to_lowercase().trim() == format!("{rhs}").to_lowercase().trim() 307 | { 308 | return Self::from(true); 309 | } 310 | match (self, rhs) { 311 | (&Self::Number(lhs), &Self::Number(rhs)) => { 312 | Self::from(lhs == rhs || (precision == 1 && (lhs / rhs).ln().abs() < 0.1)) 313 | } 314 | (Self::String(lhs), Self::String(rhs)) => Self::from(*lhs == *rhs), 315 | (&Self::Keyword(lhs), Self::Keyword(rhs)) => Self::from(lhs == *rhs), 316 | (Self::String(ref str), &Self::Number(num)) 317 | | (&Self::Number(num), Self::String(ref str)) => { 318 | let Ok(str_parse) = str.parse::() else { 319 | return Self::from(false) 320 | }; 321 | Self::from( 322 | num == str_parse || (precision == 1 && (num / str_parse).ln().abs() < 0.1), 323 | ) 324 | } 325 | (Self::Object(lhs), Self::Object(rhs)) => Self::from( 326 | !lhs.iter().any(|(k, v)| { 327 | rhs.get(k) 328 | .map_or(true, |r| r.eq(v, precision) == Self::from(false)) 329 | }) && !rhs.iter().any(|(k, _)| lhs.get(k).is_none()), 330 | ), 331 | _ => Self::from(false), 332 | } 333 | } 334 | 335 | pub fn bool(&self) -> Boolean { 336 | match self { 337 | Self::Boolean(bool) => *bool, 338 | Self::Number(num) => { 339 | if *num >= 1.0 { 340 | Boolean::True 341 | } else if *num <= 0.0 { 342 | Boolean::False 343 | } else { 344 | Boolean::Maybe 345 | } 346 | } 347 | Self::String(str) => { 348 | if str.is_empty() { 349 | Boolean::False 350 | } else { 351 | Boolean::True 352 | } 353 | } 354 | Self::Object(obj) => { 355 | if obj.is_empty() { 356 | Boolean::False 357 | } else { 358 | Boolean::True 359 | } 360 | } 361 | _ => Boolean::Maybe, 362 | } 363 | } 364 | 365 | pub const fn empty_object() -> Self { 366 | Self::Object(BTreeMap::new()) 367 | } 368 | } 369 | 370 | impl From for Value { 371 | fn from(value: f64) -> Self { 372 | Self::Number(value) 373 | } 374 | } 375 | 376 | impl From<&str> for Value { 377 | fn from(value: &str) -> Self { 378 | Self::String(value.into()) 379 | } 380 | } 381 | 382 | impl From> for Value { 383 | fn from(value: Rc) -> Self { 384 | Self::String(value) 385 | } 386 | } 387 | 388 | impl From for Value { 389 | fn from(value: Keyword) -> Self { 390 | Self::Keyword(value) 391 | } 392 | } 393 | 394 | impl From for Value { 395 | fn from(value: Boolean) -> Self { 396 | Self::Boolean(value) 397 | } 398 | } 399 | 400 | #[derive(PartialEq, Eq, Debug, Hash, Clone, Copy, PartialOrd, Ord)] 401 | pub enum Keyword { 402 | Class, 403 | Const, 404 | Current, 405 | Delete, 406 | Eval, 407 | Forget, 408 | Function, 409 | If, 410 | Next, 411 | New, 412 | Previous, 413 | Var, 414 | When, 415 | } 416 | 417 | impl Display for Keyword { 418 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 419 | match self { 420 | Self::Class => write!(f, "class"), 421 | Self::Const => write!(f, "const"), 422 | Self::Current => write!(f, "current"), 423 | Self::Eval => write!(f, "eval"), 424 | Self::Delete => write!(f, "delete"), 425 | Self::Forget => write!(f, "forget"), 426 | Self::Function => write!(f, "function"), 427 | Self::If => write!(f, "if"), 428 | Self::Next => write!(f, "next"), 429 | Self::New => write!(f, "new"), 430 | Self::Previous => write!(f, "previous"), 431 | Self::Var => write!(f, "var"), 432 | Self::When => write!(f, "when"), 433 | } 434 | } 435 | } 436 | --------------------------------------------------------------------------------