1
0

Compare commits

..

205 Commits

Author SHA1 Message Date
bb345a7938 Fix argument retrieval in native functions 2024-11-07 03:05:32 -05:00
04a1c81a2a Add parents to the VM; Improve the disassembler 2024-11-07 02:10:02 -05:00
f54d2d0d22 Clean up 2024-11-07 00:49:36 -05:00
3a6c05a79c Begin rewriting the README 2024-11-06 19:18:38 -05:00
ea88ffc451 Move the disassembler to its own module 2024-11-06 18:39:09 -05:00
a997665d1a Write docs; Improve disassembly output 2024-11-06 17:59:16 -05:00
dc9b451b37 Clean up and add docs 2024-11-06 15:50:05 -05:00
e99a7b5e1e Rename Parser to Compiler 2024-11-06 15:40:37 -05:00
74bb8a429a Clean up 2024-11-06 15:14:12 -05:00
d587d87ed7 Refine chunk poisoning and optimize taking values from registers 2024-11-06 15:08:51 -05:00
87f597624a Begin fixing control flow 2024-11-06 14:46:23 -05:00
78840cf3e7 Bring back chunk errors 2024-11-06 13:50:49 -05:00
c0b998c0d8 Add optimizer 2024-11-06 13:09:29 -05:00
e2df823566 Clean up value methods 2024-11-06 03:54:58 -05:00
f08c7c6f1f Refine values to be either abstract or concrete 2024-11-06 03:36:46 -05:00
a16f7795de Continue addind type evaluation 2024-11-06 00:57:54 -05:00
314913dbf5 Begin adding type evaluation to the parser 2024-11-05 23:20:58 -05:00
9294b8f7ed Use macro to simplify operation code 2024-11-05 22:28:10 -05:00
ea46bfe5da Simplify the parser by removing a value 2024-11-05 22:10:13 -05:00
9650183c73 Replace some "if let" statements with "matches!" macro for better formatting 2024-11-05 22:07:54 -05:00
3330939128 Add docs 2024-11-05 19:38:26 -05:00
dd13d2efee Clean up main.rs 2024-11-05 17:39:04 -05:00
b0af1609f0 Improve chunk disassembly code 2024-11-05 17:35:54 -05:00
80b6380255 Clean up 2024-11-05 16:33:56 -05:00
5ff5568e95 Clean up 2024-11-05 16:25:55 -05:00
cfb4fa66b5 Fix scopes 2024-11-05 16:07:51 -05:00
8c72e921dc Rework how scopes work to support excluding sibling scopes 2024-11-05 12:44:16 -05:00
0dcfcd5375 Clean up 2024-11-04 21:32:00 -05:00
a2fa5afcd4 Clean up 2024-11-04 21:30:23 -05:00
c77e1c89a9 Update dependencies and allow compiling to wasm 2024-11-04 21:29:20 -05:00
34dca01d85 Modicy native functions to have a full type specification 2024-11-04 19:16:19 -05:00
0e7aae79f9 Change identifiers to be constant values 2024-11-04 18:07:58 -05:00
a2e7a4e73e Begin removing chunk errors; Use constants for identifiers 2024-11-04 15:38:58 -05:00
a9e675cb4f Clean up native functions 2024-11-04 09:34:26 -05:00
60535e20d6 Deduplication of constants in the chunk; Clean up 2024-11-04 08:08:56 -05:00
e85297bcbb Extend macro use in native functions 2024-11-03 01:30:41 -05:00
1947d66be5 Overhaul implicit returns and add lots of new native functions 2024-11-02 21:24:41 -04:00
ae6d3d7a82 Add new implicit return for end of file 2024-11-01 09:55:15 -04:00
7e4f6654a4 Add move instruction to fix assignment to conditionals 2024-11-01 02:51:18 -04:00
32ff52d9b3 Change binary argument handling to fix expression chaining 2024-10-31 21:40:42 -04:00
febd7bb054 Fix while loop jumps; Pass tests 2024-10-31 20:33:46 -04:00
604962c535 Begin refactoring jump instructions 2024-10-30 14:48:30 -04:00
1f9adfab2f Fix test 2024-10-30 10:30:56 -04:00
10abd91c85 Add failing control flow test with new panic function 2024-10-30 09:50:45 -04:00
bd23853657 Add native function errors 2024-10-30 09:32:46 -04:00
382d43ef77 Add NativeFunction macro and implement read_line 2024-10-30 09:11:28 -04:00
4d6412006a Add lots of new native functions 2024-10-30 08:02:22 -04:00
d7be203bfc Fix write and write_line 2024-10-30 05:31:46 -04:00
c264aaeb13 Add the to_string, write and write_line natives 2024-10-30 05:16:34 -04:00
af4e43fc9f Add native calls and the panic native 2024-10-30 03:08:25 -04:00
caf1c22af0 Clean up 2024-10-30 00:16:10 -04:00
e304195661 Change return to use last_assigned_value; Add scopes tests 2024-10-29 23:11:55 -04:00
004cf73959 Pass tests 2024-10-24 22:37:18 -04:00
c0f74f1e09 Tweak control flow jumping and compile-time optimizations 2024-10-20 22:18:58 -04:00
648bbdbc4d Refactor parsing for better jumping and returns 2024-10-20 18:51:04 -04:00
1da61f0873 Begin reworking jump instructions 2024-10-20 10:20:09 -04:00
8db37bcdfd Add LoadSelf instruction to enable recursion 2024-10-20 02:30:22 -04:00
6caae6c952 Add chunk names 2024-10-20 00:46:59 -04:00
f15cf84c4d Refactor to fix bugs with loops; Add close instructions while calling functions 2024-10-20 00:06:22 -04:00
13b4cfffcc Add function declarations to the parser 2024-10-19 19:13:19 -04:00
9d5c9d9fd0 Implement functions calls 2024-10-19 17:24:22 -04:00
65e513488f Clean up unused code 2024-10-19 13:52:23 -04:00
1c8e8b35b9 Remove old, unused code from the parser 2024-10-19 13:40:46 -04:00
3f8b554a86 Refactor parser and chunk to have fewer errors and less retrospection 2024-10-19 12:05:20 -04:00
8c79157fa7 Simplify and clean up 2024-10-19 03:06:14 -04:00
6bcc5b1555 Add function test 2024-10-19 01:58:30 -04:00
b7153df9be Pass tests 2024-10-19 01:04:53 -04:00
bfade78a0d Consume VM and chunk when running the VM 2024-10-18 23:34:48 -04:00
8b70a1dcc4 Clean up 2024-10-18 23:09:07 -04:00
589e59b8c4 Clean up 2024-10-18 23:03:50 -04:00
86e055a562 Clean up 2024-10-18 22:30:20 -04:00
19f2d19134 Clean up 2024-10-18 21:34:47 -04:00
95e5b3062d Fix lexing bug 2024-10-18 19:31:46 -04:00
44659ec34a Extend CLI to cover more formatting options; Extend formatting 2024-10-13 16:46:45 -04:00
0c758c9768 Clean up 2024-10-13 13:08:12 -04:00
12d34d6354 Add a new parser method to handle errors 2024-10-13 13:01:58 -04:00
3609fddaea Expand formatter 2024-10-13 12:04:32 -04:00
5d62d897f4 Ads no-run command to the CLI 2024-10-13 08:04:32 -04:00
565d3c54f1 Add format option to the CLI 2024-10-13 07:56:11 -04:00
02ee7d126c Clean up 2024-10-13 07:45:16 -04:00
c7bba88875 Create formatter 2024-10-13 07:14:12 -04:00
5eb901f468 Add a VM method; Refactor and clean up 2024-10-13 04:21:07 -04:00
055f0a4100 Continue working on function calls 2024-10-13 02:47:12 -04:00
d5fc68e466 Refactor chunk disassembly output 2024-10-13 02:33:58 -04:00
743679371d Fix function bug 2024-10-12 20:34:13 -04:00
2864bee057 Add types to functions and improve calls 2024-10-12 20:19:21 -04:00
2527cc2de7 Clean up 2024-10-12 10:57:22 -04:00
ea0be43199 Begin adding function calls 2024-10-12 10:55:34 -04:00
5bbda1a24e Disallow comparison chaining 2024-10-12 08:16:06 -04:00
f1034534ed Avoid emitting duplicate return instructions; Clean up 2024-10-12 07:12:44 -04:00
02877d06d3 Reduce use of TokenKind in parser to only error cases 2024-10-12 06:27:19 -04:00
30b2801a38 Add item type to list value; Clean up 2024-10-12 06:17:16 -04:00
9c612317dc Refactor values to use register pointers for complex values 2024-10-12 04:50:30 -04:00
6e1ef77192 Refactor to use index references to avoid cloning values 2024-10-12 03:06:44 -04:00
c1e372d7cf Improve disassembler output; Fix return positions 2024-10-09 19:59:06 -04:00
61d633392c Improve disassembler output 2024-10-09 17:34:11 -04:00
b6c3b1e5ba Refactor 2024-10-09 12:16:46 -04:00
259721f6cb Continue implemnting functions; Begin adding types 2024-10-09 06:15:06 -04:00
bdc34cb10e Begin adding functions to the language 2024-10-08 22:56:01 -04:00
dddbf77fac Fix bug in the VM 2024-10-05 21:30:48 -04:00
79cc59c952 Make minor changes to fizzbuzz example 2024-10-05 06:42:19 -04:00
35f73d60f0 Add tests for parser errors 2024-10-05 05:57:28 -04:00
437a6bf164 Add math-assignment operators 2024-10-05 04:33:38 -04:00
d857f42434 Pass all tests 2024-10-05 02:07:26 -04:00
9d0aa13e8a Refactor VM, chunk and parser to pass tests 2024-10-05 00:11:03 -04:00
9b1dc6c55c Rewrite tests for consistency 2024-10-04 22:36:33 -04:00
5015cf4cc4 Refactor how return instruction works; Begin passing tests 2024-10-04 22:33:47 -04:00
5411a1db27 Move and replace lots of tests; Refactor parser 2024-10-04 22:21:17 -04:00
ba904fdcd8 Clean up disassembler output 2024-10-01 20:27:28 -04:00
9d1996c9ec Simplify parsing comparison expressions 2024-10-01 20:07:57 -04:00
88684f49b6 Small refactor to jump position 2024-09-27 13:16:41 -04:00
d0d80cf407 Use test for single registers in conditional expressions 2024-09-25 09:55:10 -04:00
47d6ea417d Improve logs; Clean up 2024-09-25 01:27:10 -04:00
daca836db1 Refine LoadList instruction; Improve logs; Refactor 2024-09-24 22:58:14 -04:00
60df8b4d64 Remove identiifer cache and extend some tests 2024-09-24 20:32:52 -04:00
95e22aa97f Pass tests 2024-09-24 17:49:30 -04:00
7afde989f9 Fix bugs in binary parsing and running 2024-09-24 16:49:17 -04:00
85241c04b9 Fix lists 2024-09-24 13:51:04 -04:00
c1eccf049f Add list test 2024-09-24 12:03:12 -04:00
855782c370 Pass tests 2024-09-24 11:40:12 -04:00
a6da34a79c Fix control flow 2024-09-24 10:16:19 -04:00
3df42f6a47 Refine equality implementation 2024-09-24 08:29:33 -04:00
1d03876b89 Write tests 2024-09-24 01:30:21 -04:00
5441938725 Add better parser error 2024-09-24 00:24:09 -04:00
c31991cc24 Fix and pass tests 2024-09-23 23:38:49 -04:00
9fe3e440ac Add and pass tests 2024-09-23 19:16:15 -04:00
182ef66f23 Refactor register incrementing/decrementing 2024-09-23 11:15:49 -04:00
d68c789ea8 Add test for equality chain 2024-09-23 08:57:49 -04:00
1d0bafa4a3 Refine binary parsing 2024-09-23 06:42:41 -04:00
2c485cf046 Implement logical and/or operators 2024-09-23 04:01:36 -04:00
c5c2fe95ae Implement unary operators and divide binary parsing 2024-09-23 01:45:52 -04:00
23f733d8b2 Extend parsing 2024-09-23 00:55:39 -04:00
57edf48e36 Extend parsing 2024-09-22 18:17:21 -04:00
573e5ae470 Change VM's final value to last modified register 2024-09-22 12:57:58 -04:00
31bb7eaffc Pass tests 2024-09-19 18:07:11 -04:00
d4a8a65096 Pass tests 2024-09-19 13:54:28 -04:00
dee09d3583 Add bytcode to dissassembly 2024-09-19 11:53:41 -04:00
be77d64c39 Pass all but one test 2024-09-19 11:41:18 -04:00
03113fdf5e Clean up 2024-09-18 23:02:28 -04:00
bf4f319302 Add and pass tests 2024-09-18 22:00:24 -04:00
a1dd7e3bb9 Add if/else test 2024-09-18 20:08:02 -04:00
5b3232c723 Refactor and clean up; Pass tests 2024-09-18 20:02:12 -04:00
413cb70731 Begin implementing control flow with if/else expressions 2024-09-18 16:43:34 -04:00
915340fbdb Add LoadBoolean; Refactor; Improve disassembly output 2024-09-18 13:42:32 -04:00
85b95a56aa Add mutable variables 2024-09-18 11:27:41 -04:00
2cb03297c5 Add test 2024-09-18 08:33:29 -04:00
56f1222cfc Fix block test 2024-09-18 08:29:00 -04:00
89573e81b9 Fix bugs in binary parsing 2024-09-18 08:24:57 -04:00
fa4c2d48a3 Implement better closing for blocks 2024-09-18 07:58:31 -04:00
0a16c5e0ca Fix binary expressions with variables 2024-09-18 01:21:40 -04:00
2f8c46f0a5 Implement closing for lists 2024-09-17 23:06:58 -04:00
37dc2e05c5 Clean up 2024-09-17 21:10:44 -04:00
f02c3d1fb5 Add lists 2024-09-17 19:35:33 -04:00
6ca96bc1dc Add &&, || and ! to the language; Add tests 2024-09-17 17:51:39 -04:00
00555785e3 Refactor pratt parsing 2024-09-17 17:23:37 -04:00
6c76006ad9 Improve test 2024-09-17 13:39:12 -04:00
0d55bb7244 Fix binary expression parsing 2024-09-17 13:24:45 -04:00
fd4ffeec7c Pass tests 2024-09-17 12:08:40 -04:00
71a4f863e3 Add test 2024-09-15 10:20:43 -04:00
9cb6873618 Add test 2024-09-15 06:33:56 -04:00
b66710e3eb Improve precedence parsing 2024-09-15 06:26:31 -04:00
4653a3f506 Improve on proof of concept 2024-09-15 04:25:24 -04:00
97bde437e8 Pass tests and fix instruction byte encoding bug 2024-09-15 02:03:54 -04:00
d1bdabed56 Continue register proof of concept 2024-09-15 01:24:04 -04:00
ba80774e7b Refactor and debug 2024-09-14 21:05:03 -04:00
aa8b1215a8 Clean up 2024-09-14 15:06:13 -04:00
9418cd5b70 Add "styled" CLI option and debug logging for disassembler 2024-09-14 14:31:40 -04:00
8534f18c9a Tweak the chunk disassembly 2024-09-14 13:59:11 -04:00
2ad3440097 Clean up 2024-09-13 02:30:09 -04:00
3b7987c218 Replace local variables with indexes to the register 2024-09-13 02:28:18 -04:00
a0439675b7 Fix bugs and improve disassembler 2024-09-13 01:10:07 -04:00
82a2b8f6b7 Fix byte lexing bug; Implement Move operation 2024-09-12 21:14:15 -04:00
5c54a5b9bd Make chunk disassembly the prettiest thing ever 2024-09-12 19:25:20 -04:00
9144257524 Replace VM's "clone" method with "take" to avoid cloning values 2024-09-12 14:22:07 -04:00
caf59894b6 Fix and implement variable getting and setting 2024-09-12 14:16:26 -04:00
8b33df3d4a Prettify the hell out of chunk disassembly 2024-09-12 13:03:24 -04:00
78c9ed97e2 Experiment with instruction optimization 2024-09-12 09:11:49 -04:00
6ff25a22ec Fix lexing bug that I just created 2024-09-12 05:12:38 -04:00
d4d58e793b Refactor and improve the VM, Parser, and Lexer 2024-09-12 05:08:55 -04:00
67e5de6664 Reimplement the Parser and VM with a register-based VM 2024-09-12 00:39:31 -04:00
7b055d79b5 Begin rewrite with register-based bytecode 2024-09-11 23:07:20 -04:00
974310ffab Preffify logs 2024-09-11 04:33:53 -04:00
86f8e47b0c Avoid cloning constant and move identifier stack to the chunk 2024-09-11 04:22:54 -04:00
e4204c1b0d Get variable scopes working 2024-09-11 03:10:12 -04:00
8f58bf30a4 Make everything messy 2024-09-10 18:19:59 -04:00
c3790e90bf Implement blocks with scopes 2024-09-10 10:44:15 -04:00
4ba3a47ae5 Add implicit returns and fix variable declaration and resolution 2024-09-10 09:26:05 -04:00
f936c30b4f Go to great lengths to avoid cloning Values; Extend error reports 2024-09-10 03:42:25 -04:00
0ed2733991 Add quotes to string value display 2024-09-10 01:06:38 -04:00
8798efc0af Add error reports and byte operations 2024-09-10 01:04:30 -04:00
8f20e53880 Add support for characters and bytes 2024-09-09 23:45:35 -04:00
fcfcb4a429 Restructure, clean up and add tests 2024-09-09 23:24:22 -04:00
85f5f44946 Pass all tests 2024-09-09 22:57:14 -04:00
5b8ec74d05 Prettify disassembly output 2024-09-09 20:55:00 -04:00
c406039c99 Replace global variables with locals 2024-09-09 19:23:49 -04:00
32347ec512 Make chunk debug print infallible 2024-09-07 18:48:01 -04:00
e9ec838b25 Refactor and clean up 2024-09-07 17:16:14 -04:00
b8957190e0 Add and pass tests 2024-09-07 13:51:05 -04:00
3ac15fe70b Implement let assignment 2024-09-07 12:15:47 -04:00
03d44434e2 Refactor parsing 2024-09-07 06:38:12 -04:00
616f890028 Pass parser test 2024-09-07 04:37:38 -04:00
812d930488 Continue writing bytecode implementation 2024-09-07 04:34:03 -04:00
406edda573 Begin parser 2024-09-06 23:30:43 -04:00
3c2e3699ab Add binary operations 2024-09-06 19:37:12 -04:00
1ecaac0819 Start new rewrite of Dust 2024-09-06 19:27:16 -04:00
48 changed files with 8512 additions and 10842 deletions

427
Cargo.lock generated
View File

@ -4,9 +4,9 @@ version = 3
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.1.2" version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -23,52 +23,65 @@ dependencies = [
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.13" version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"anstyle-parse", "anstyle-parse",
"anstyle-query", "anstyle-query",
"anstyle-wincon", "anstyle-wincon",
"colorchoice", "colorchoice",
"is_terminal_polyfill",
"utf8parse", "utf8parse",
] ]
[[package]] [[package]]
name = "anstyle" name = "anstyle"
version = "1.0.8" version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]] [[package]]
name = "anstyle-parse" name = "anstyle-parse"
version = "0.2.3" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [ dependencies = [
"utf8parse", "utf8parse",
] ]
[[package]] [[package]]
name = "anstyle-query" name = "anstyle-query"
version = "1.0.2" version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [ dependencies = [
"windows-sys", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
name = "anstyle-wincon" name = "anstyle-wincon"
version = "3.0.2" version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"windows-sys", "windows-sys 0.59.0",
] ]
[[package]]
name = "bumpalo"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
@ -77,9 +90,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.14" version = "4.5.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c937d4061031a6d0c8da4b9a4f98a172fc2976dfb1c19213a9cf7d0d3c837e36" checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -87,9 +100,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.14" version = "4.5.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85379ba512b21a328adf887e85f7742d12e96eb31f3ef077df4ffc26b506ffed" checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -99,9 +112,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.13" version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -117,44 +130,30 @@ checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
version = "1.0.0" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]] [[package]]
name = "crossbeam-deque" name = "colored"
version = "0.8.5" version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
dependencies = [ dependencies = [
"crossbeam-epoch", "lazy_static",
"crossbeam-utils", "windows-sys 0.48.0",
] ]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]] [[package]]
name = "dust-lang" name = "dust-lang"
version = "0.5.0" version = "0.5.0"
dependencies = [ dependencies = [
"annotate-snippets", "annotate-snippets",
"colored",
"env_logger", "env_logger",
"getrandom",
"log", "log",
"rand", "rand",
"rayon",
"serde", "serde",
"serde_json", "serde_json",
] ]
@ -164,21 +163,17 @@ name = "dust-shell"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"colored",
"dust-lang", "dust-lang",
"env_logger", "env_logger",
"log",
] ]
[[package]]
name = "either"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]] [[package]]
name = "env_filter" name = "env_filter"
version = "0.1.0" version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab"
dependencies = [ dependencies = [
"log", "log",
"regex", "regex",
@ -199,13 +194,15 @@ dependencies = [
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.12" version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys",
"libc", "libc",
"wasi", "wasi",
"wasm-bindgen",
] ]
[[package]] [[package]]
@ -221,16 +218,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]] [[package]]
name = "itoa" name = "is_terminal_polyfill"
version = "1.0.10" version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "js-sys"
version = "0.3.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.153" version = "0.2.161"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
[[package]] [[package]]
name = "log" name = "log"
@ -240,30 +258,39 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.1" version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "once_cell"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.17" version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.79" version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.35" version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -298,31 +325,11 @@ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "rayon"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.10.3" version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -332,9 +339,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.4.6" version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -343,30 +350,30 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.8.2" version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.17" version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.203" version = "1.0.214"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.203" version = "1.0.214"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -375,11 +382,12 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.117" version = "1.0.132"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr",
"ryu", "ryu",
"serde", "serde",
] ]
@ -392,9 +400,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.53" version = "2.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -403,21 +411,21 @@ dependencies = [
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.12" version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.13" version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.2.1" version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "wasi" name = "wasi"
@ -426,67 +434,216 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]] [[package]]
name = "windows-sys" name = "wasm-bindgen"
version = "0.52.0" version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
dependencies = [ dependencies = [
"windows-targets", "cfg-if",
"once_cell",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
] ]
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.52.4" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm", "windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc", "windows_aarch64_msvc 0.48.5",
"windows_i686_gnu", "windows_i686_gnu 0.48.5",
"windows_i686_msvc", "windows_i686_msvc 0.48.5",
"windows_x86_64_gnu", "windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm", "windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc", "windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
] ]
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.52.4" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.52.4" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.52.4" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.52.4" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.52.4" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.52.4" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.52.4" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

125
README.md
View File

@ -1,109 +1,50 @@
# Dust # Dust
High-level programming language with effortless concurrency, automatic memory management and type Dust is a high-level interpreted programming language with static types that focuses on ease of use,
safety. performance and correctness.
Dust is a work in progress. Because it aims to deliver a high level of safety, extensive testing ## Implementation
is required. The language is still in the design phase, and the syntax is subject to change.
## Usage Dust is implemented in Rust and is divided into several parts, primarily the lexer, compiler, and
virtual machine. All of Dust's components are designed with performance in mind and the codebase
uses as few dependencies as possible.
The Dust command line tool can be used to run Dust programs. It is not yet available outside of ### Lexer
this repository.
```sh The lexer emits tokens from the source code. Dust makes extensive use of Rust's zero-copy
cargo run --package dust-shell -- examples/hello_world.ds capabilities to avoid unnecessary allocations when creating tokens. A token, depending on its type,
``` may contain a reference to some data from the source code. The data is only copied in the case of an
error, because it improves the usability of the codebase for errors to own their data when possible.
In a successfully executed program, no part of the source code is copied unless it is a string
literal or identifier.
```sh ### Compiler
cargo run --package dust-shell -- -c '"Hello my name is " + read_line() + "!"'
```
Dust is easily embedded in another program. You can run a dust program of any size or complexity The compiler creates a chunk, which contains all of the data needed by the virtual machine to run a
with a single function. Dust program. It does so by emitting bytecode instructions, constants and locals while parsing the
tokens, which are generated one at a time by the lexer.
```rust #### Parsing
use dust_lang::{run, Value};
fn main() { Dust's compiler uses a custom Pratt parser, a kind of recursive descent parser, to translate a
let code = " sequence of tokens into a chunk.
let x = 'Dust'
let y = ' is awesome!'
write_line(x + y) #### Optimizing
42 When generating instructions for a register-based virtual machine, there are opportunities to
"; optimize the generated code, usually by consolidating register use or reusing registers within an
expression. While it is best to output optimal code in the first place, it is not always possible.
Dust's compiler has a simple peephole optimizer that can be used to modify isolated sections of the
instruction list through a mutable reference.
let result = run(code); ### Instructions
assert_eq!(result, Ok(Some(Value::integer(42)))); ### Virtual Machine
}
```
## Concepts ## Previous Implementations
### Effortless Concurrency ## Inspiration
Dust makes concurrency as effortless as possible. Dust is organized into **statements**, and any - [The Implementation of Lua 5.0](https://www.lua.org/doc/jucs05.pdf)
sequence of statements can be run concurrently by simply adding the `async` keyword before the block - [A No-Frills Introduction to Lua 5.1 VM Instructions](https://www.mcours.net/cours/pdf/hasclic3/hasssclic818.pdf)
of statements. - [Crafting Interpreters](https://craftinginterpreters.com/)
```rust
// Counts from 0 to 9, sleeping for an increasing amount of time between each.
let count_slowly = fn (multiplier: int) {
i = 0
while i < 10 {
sleep(i * multiplier)
write_line(i.to_string())
i += 1
}
}
async {
count_slowly(200) // Finishes last
count_slowly(100) // Finishes second
count_slowly(50) // Finishes first
}
```
### Automatic Memory Management
Dust uses a garbage collector to automatically manage memory.
```rust
let x = 0 // x is assigned but never used
// x is removed from memory
let y = 41 // y is assigned
let z = y + 1 // y is kept alive for this statement
// y is removed from memory
write_line(z) // z is kept alive for this statement
// z is removed from memory
```
### Type Safety
Dust is statically typed and null-free, but the type of a value can usually be inferred from its
usage. Dust will refuse to run programs with type errors, but will usually not require type
annotations.
```rust
// These two statements are identical to Dust
let x = 1
let x: int = 1
// Numbers with decimals are floats
let y = 10.0
let y: float = 10.0
// Strings are enclosed in double quotes and are guaranteed to be valid UTF-8
let z = "Hello, world!"
let z: str = "Hello, world!"
```
Aside from the ubiqutous `bool`, `int`, `float`, and `str` types, Dust also has lists, maps,
ranges, structures, enums and functions.

View File

@ -10,12 +10,14 @@ repository.workspace = true
[dependencies] [dependencies]
annotate-snippets = "0.11.4" annotate-snippets = "0.11.4"
env_logger = "0.11.3" colored = "2.1.0"
log = "0.4.22" log = "0.4.22"
rand = "0.8.5" rand = "0.8.5"
rayon = "1.9.0"
serde = { version = "1.0.203", features = ["derive"] } serde = { version = "1.0.203", features = ["derive"] }
serde_json = "1.0.117" serde_json = "1.0.117"
getrandom = { version = "0.2", features = [
"js",
] } # Indirect dependency, for wasm builds
[dev-dependencies] [dev-dependencies]
env_logger = "0.11.5" env_logger = "0.11.5"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,192 +0,0 @@
//! In-memory representation of a Dust program.
mod expression;
mod statement;
pub use expression::*;
pub use statement::*;
use std::{
collections::VecDeque,
fmt::{self, Debug, Display, Formatter},
num::TryFromIntError,
};
use serde::{Deserialize, Serialize};
use crate::{core_library, Context, ContextError};
pub type Span = (usize, usize);
/// In-memory representation of a Dust program.
#[derive(Clone, Serialize, Deserialize)]
pub struct AbstractSyntaxTree {
pub statements: VecDeque<Statement>,
#[serde(skip)]
pub context: Context,
}
impl Debug for AbstractSyntaxTree {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("AbstractSyntaxTree")
.field("statements", &self.statements)
.finish()
}
}
impl Eq for AbstractSyntaxTree {}
impl PartialEq for AbstractSyntaxTree {
fn eq(&self, other: &Self) -> bool {
self.statements == other.statements
}
}
impl PartialOrd for AbstractSyntaxTree {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for AbstractSyntaxTree {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.statements.cmp(&other.statements)
}
}
impl AbstractSyntaxTree {
pub fn new() -> Self {
Self {
statements: VecDeque::new(),
context: Context::new(),
}
}
pub fn with_statements<T: Into<VecDeque<Statement>>>(statements: T) -> Self {
Self {
statements: statements.into(),
context: Context::new(),
}
}
pub fn with_core_library() -> Self {
Self {
statements: VecDeque::new(),
context: core_library().create_child(),
}
}
}
impl Default for AbstractSyntaxTree {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Node<T> {
pub inner: T,
pub position: Span,
}
impl<T> Node<T> {
pub fn new(inner: T, position: Span) -> Self {
Self { inner, position }
}
}
impl<T: Display> Display for Node<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.inner)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum AstError {
ContextError {
error: ContextError,
position: Span,
},
ExpectedFunctionOrConstructor {
position: Span,
},
ExpectedInteger {
position: Span,
},
ExpectedListType {
position: Span,
},
ExpectedNonEmptyEvaluation {
position: Span,
},
ExpectedNonEmptyList {
position: Span,
},
ExpectedRangeableType {
position: Span,
},
ExpectedStructFieldsType {
position: Span,
},
ExpectedTupleType {
position: Span,
},
FromIntError {
error: TryFromIntError,
position: Span,
},
}
impl AstError {
pub fn position(&self) -> Span {
match self {
AstError::ContextError { position, .. } => *position,
AstError::ExpectedFunctionOrConstructor { position } => *position,
AstError::ExpectedInteger { position } => *position,
AstError::ExpectedListType { position } => *position,
AstError::ExpectedNonEmptyEvaluation { position } => *position,
AstError::ExpectedNonEmptyList { position } => *position,
AstError::ExpectedRangeableType { position } => *position,
AstError::ExpectedStructFieldsType { position } => *position,
AstError::ExpectedTupleType { position } => *position,
AstError::FromIntError { position, .. } => *position,
}
}
}
impl Display for AstError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
AstError::ContextError { error, position } => {
write!(f, "Context error at {:?}: {}", position, error)
}
AstError::ExpectedFunctionOrConstructor { position } => {
write!(f, "Expected a function or constructor at {:?}", position)
}
AstError::ExpectedInteger { position } => {
write!(f, "Expected an integer at {:?}", position)
}
AstError::ExpectedListType { position } => {
write!(f, "Expected a type at {:?}", position)
}
AstError::ExpectedTupleType { position } => {
write!(f, "Expected a tuple type at {:?}", position)
}
AstError::ExpectedNonEmptyEvaluation { position } => {
write!(f, "Expected a type at {:?}", position)
}
AstError::ExpectedNonEmptyList { position } => {
write!(f, "Expected a non-empty list at {:?}", position)
}
AstError::ExpectedRangeableType { position } => {
write!(f, "Expected a rangeable type at {:?}", position)
}
AstError::ExpectedStructFieldsType { position } => {
write!(f, "Expected a struct type with fields at {:?}", position)
}
AstError::FromIntError { error, position } => {
write!(f, "Integer conversion error at {:?}: {}", position, error)
}
}
}
}

View File

@ -1,187 +0,0 @@
use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
use crate::{Context, Identifier, Type, TypeEvaluation};
use super::{AstError, Expression, Node, Span};
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Statement {
Expression(Expression),
ExpressionNullified(Node<Expression>),
Let(Node<LetStatement>),
StructDefinition(Node<StructDefinition>),
}
impl Statement {
pub fn struct_definition(struct_definition: StructDefinition, position: Span) -> Self {
Statement::StructDefinition(Node::new(struct_definition, position))
}
pub fn position(&self) -> Span {
match self {
Statement::Expression(expression) => expression.position(),
Statement::ExpressionNullified(expression_node) => expression_node.position,
Statement::Let(r#let) => r#let.position,
Statement::StructDefinition(definition) => definition.position,
}
}
pub fn type_evaluation(&self, context: &Context) -> Result<TypeEvaluation, AstError> {
match self {
Statement::Expression(expression) => expression.type_evaluation(context),
Statement::ExpressionNullified(expression_node) => {
let type_evaluation = expression_node.inner.type_evaluation(context)?;
if let TypeEvaluation::Break(_) = type_evaluation {
Ok(type_evaluation)
} else {
Ok(TypeEvaluation::Return(None))
}
}
Statement::Let(_) => Ok(TypeEvaluation::Return(None)),
Statement::StructDefinition(_) => Ok(TypeEvaluation::Return(None)),
}
}
}
impl Display for Statement {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Statement::Expression(expression) => write!(f, "{}", expression),
Statement::ExpressionNullified(expression) => write!(f, "{};", expression),
Statement::Let(r#let) => write!(f, "{};", r#let),
Statement::StructDefinition(struct_definition) => match &struct_definition.inner {
StructDefinition::Unit { name } => write!(f, "struct {};", name),
StructDefinition::Tuple { name, items } => {
write!(f, "struct {name} {{ ")?;
for (index, item) in items.iter().enumerate() {
write!(f, "{}: {}", item, index)?;
if index < items.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, " }}")
}
StructDefinition::Fields { name, fields } => {
write!(f, "struct {name} {{ ")?;
for (index, (field, r#type)) in fields.iter().enumerate() {
write!(f, "{}: {}", field, r#type)?;
if index < fields.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, " }}")
}
},
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum LetStatement {
Let {
identifier: Node<Identifier>,
value: Expression,
},
LetMut {
identifier: Node<Identifier>,
value: Expression,
},
LetType {
identifier: Node<Identifier>,
r#type: Node<Type>,
value: Expression,
},
LetMutType {
identifier: Node<Identifier>,
r#type: Node<Type>,
value: Expression,
},
}
impl Display for LetStatement {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
LetStatement::Let { identifier, value } => {
write!(f, "let {identifier} = {value}")
}
LetStatement::LetMut { identifier, value } => {
write!(f, "let mut {identifier} = {value}")
}
LetStatement::LetType {
identifier,
r#type,
value,
} => {
write!(f, "let {identifier}: {type} = {value}")
}
LetStatement::LetMutType {
identifier,
r#type,
value,
} => {
write!(f, "let mut {identifier}: {type} = {value}")
}
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum StructDefinition {
Unit {
name: Node<Identifier>,
},
Tuple {
name: Node<Identifier>,
items: Vec<Node<Type>>,
},
Fields {
name: Node<Identifier>,
fields: Vec<(Node<Identifier>, Node<Type>)>,
},
}
impl Display for StructDefinition {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
StructDefinition::Unit { name } => write!(f, "struct {name}"),
StructDefinition::Tuple {
name,
items: fields,
} => {
write!(f, "struct {name} {{")?;
for (i, field) in fields.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{field}")?;
}
write!(f, "}}")
}
StructDefinition::Fields { name, fields } => {
write!(f, "struct {name} {{")?;
for (i, (field_name, field_type)) in fields.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{field_name}: {field_type}")?;
}
write!(f, "}}")
}
}
}
}

View File

@ -1,208 +0,0 @@
//! Integrated functions that can be called from Dust code.
use std::{
error::Error,
fmt::{self, Display, Formatter},
io::{self, stdin, stdout, Write},
};
use serde::{Deserialize, Serialize};
use crate::{FunctionType, Identifier, Type, Value, ValueData, ValueError};
/// Integrated function that can be called from Dust code.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum BuiltInFunction {
// String tools
ToString,
// Integer and float tools
IsEven,
IsOdd,
// I/O
ReadLine,
WriteLine,
}
impl BuiltInFunction {
pub fn name(&self) -> &'static str {
match self {
BuiltInFunction::IsEven => "is_even",
BuiltInFunction::IsOdd => "is_odd",
BuiltInFunction::ReadLine => "read_line",
BuiltInFunction::ToString { .. } => "to_string",
BuiltInFunction::WriteLine => "write_line",
}
}
pub fn type_parameters(&self) -> Option<Vec<Identifier>> {
match self {
BuiltInFunction::ToString { .. } => None,
BuiltInFunction::IsEven => None,
BuiltInFunction::IsOdd => None,
BuiltInFunction::ReadLine => None,
BuiltInFunction::WriteLine => None,
}
}
pub fn value_parameters(&self) -> Option<Vec<(Identifier, Type)>> {
match self {
BuiltInFunction::ToString { .. } => Some(vec![("value".into(), Type::Any)]),
BuiltInFunction::IsEven => Some(vec![("value".into(), Type::Number)]),
BuiltInFunction::IsOdd => Some(vec![("value".into(), Type::Number)]),
BuiltInFunction::ReadLine => None,
BuiltInFunction::WriteLine => Some(vec![("value".into(), Type::Any)]),
}
}
pub fn return_type(&self) -> Option<Type> {
match self {
BuiltInFunction::ToString { .. } => Some(Type::String { length: None }),
BuiltInFunction::IsEven => Some(Type::Boolean),
BuiltInFunction::IsOdd => Some(Type::Boolean),
BuiltInFunction::ReadLine => Some(Type::String { length: None }),
BuiltInFunction::WriteLine => None,
}
}
pub fn r#type(&self) -> Type {
Type::Function(FunctionType {
name: Identifier::new(self.name()),
type_parameters: self.type_parameters(),
value_parameters: self.value_parameters(),
return_type: self.return_type().map(Box::new),
})
}
pub fn call(
&self,
_type_arguments: Option<Vec<Type>>,
value_arguments: Option<Vec<Value>>,
) -> Result<Option<Value>, BuiltInFunctionError> {
match (self.value_parameters(), &value_arguments) {
(Some(value_parameters), Some(value_arguments)) => {
if value_parameters.len() != value_arguments.len() {
return Err(BuiltInFunctionError::WrongNumberOfValueArguments);
}
}
(Some(_), None) | (None, Some(_)) => {
return Err(BuiltInFunctionError::WrongNumberOfValueArguments);
}
(None, None) => {}
}
match self {
BuiltInFunction::ToString => {
Ok(Some(Value::string(value_arguments.unwrap()[0].to_string())))
}
BuiltInFunction::IsEven => {
let is_even = value_arguments.unwrap()[0].is_even()?;
Ok(Some(is_even))
}
BuiltInFunction::IsOdd => {
let is_odd = value_arguments.unwrap()[0].is_odd()?;
Ok(Some(is_odd))
}
BuiltInFunction::ReadLine => {
let mut input = String::new();
stdin().read_line(&mut input)?;
Ok(Some(Value::string(input.trim_end_matches('\n'))))
}
BuiltInFunction::WriteLine => {
let first_argument = &value_arguments.unwrap()[0];
match first_argument {
Value::Raw(ValueData::String(string)) => {
let mut stdout = stdout();
stdout.write_all(string.as_bytes())?;
stdout.write_all(b"\n")?;
Ok(None)
}
Value::Reference(reference) => match reference.as_ref() {
ValueData::String(string) => {
let mut stdout = stdout();
stdout.write_all(string.as_bytes())?;
stdout.write_all(b"\n")?;
Ok(None)
}
_ => Err(BuiltInFunctionError::ExpectedString),
},
Value::Mutable(locked) => {
let value_data = &*locked.read().unwrap();
let string = match value_data {
ValueData::String(string) => string,
_ => return Err(BuiltInFunctionError::ExpectedString),
};
let mut stdout = stdout();
stdout.write_all(string.as_bytes())?;
stdout.write_all(b"\n")?;
Ok(None)
}
_ => Err(BuiltInFunctionError::ExpectedString),
}
}
}
}
}
impl Display for BuiltInFunction {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum BuiltInFunctionError {
Io(io::ErrorKind),
ValueError(ValueError),
ExpectedString,
ExpectedList,
ExpectedInteger,
WrongNumberOfValueArguments,
}
impl From<ValueError> for BuiltInFunctionError {
fn from(v: ValueError) -> Self {
Self::ValueError(v)
}
}
impl From<io::Error> for BuiltInFunctionError {
fn from(error: io::Error) -> Self {
Self::Io(error.kind())
}
}
impl Error for BuiltInFunctionError {}
impl Display for BuiltInFunctionError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
BuiltInFunctionError::Io(error_kind) => write!(f, "I/O error: {}", error_kind),
BuiltInFunctionError::ValueError(value_error) => {
write!(f, "Value error: {}", value_error)
}
BuiltInFunctionError::ExpectedInteger => write!(f, "Expected an integer"),
BuiltInFunctionError::ExpectedString => write!(f, "Expected a string"),
BuiltInFunctionError::ExpectedList => write!(f, "Expected a list"),
BuiltInFunctionError::WrongNumberOfValueArguments => {
write!(f, "Wrong number of value arguments")
}
}
}
}

402
dust-lang/src/chunk.rs Normal file
View File

@ -0,0 +1,402 @@
//! In-memory representation of a Dust program or function.
//!
//! A chunk consists of a sequence of instructions and their positions, a list of constants, and a
//! list of locals that can be executed by the Dust virtual machine. Chunks have a name when they
//! belong to a named function.
use std::{
cmp::Ordering,
fmt::{self, Debug, Display, Formatter},
};
use serde::{Deserialize, Serialize};
use crate::{Disassembler, Instruction, Operation, Span, Type, Value};
/// In-memory representation of a Dust program or function.
///
/// See the [module-level documentation](index.html) for more information.
#[derive(Clone, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Chunk {
name: Option<String>,
pub is_poisoned: bool,
instructions: Vec<(Instruction, Span)>,
constants: Vec<Value>,
locals: Vec<Local>,
current_scope: Scope,
block_index: u8,
}
impl Chunk {
pub fn new(name: Option<String>) -> Self {
Self {
name,
is_poisoned: false,
instructions: Vec::new(),
constants: Vec::new(),
locals: Vec::new(),
current_scope: Scope::default(),
block_index: 0,
}
}
pub fn with_data(
name: Option<String>,
instructions: Vec<(Instruction, Span)>,
constants: Vec<Value>,
locals: Vec<Local>,
) -> Self {
Self {
name,
is_poisoned: false,
instructions,
constants,
locals,
current_scope: Scope::default(),
block_index: 0,
}
}
pub fn name(&self) -> Option<&String> {
self.name.as_ref()
}
pub fn set_name(&mut self, name: String) {
self.name = Some(name);
}
pub fn len(&self) -> usize {
self.instructions.len()
}
pub fn is_empty(&self) -> bool {
self.instructions.is_empty()
}
pub fn constants(&self) -> &Vec<Value> {
&self.constants
}
pub fn constants_mut(&mut self) -> &mut Vec<Value> {
&mut self.constants
}
pub fn take_constants(self) -> Vec<Value> {
self.constants
}
pub fn instructions(&self) -> &Vec<(Instruction, Span)> {
&self.instructions
}
pub fn instructions_mut(&mut self) -> &mut Vec<(Instruction, Span)> {
&mut self.instructions
}
pub fn get_instruction(&self, index: usize) -> Result<&(Instruction, Span), ChunkError> {
self.instructions
.get(index)
.ok_or(ChunkError::InstructionIndexOutOfBounds { index })
}
pub fn locals(&self) -> &Vec<Local> {
&self.locals
}
pub fn locals_mut(&mut self) -> &mut Vec<Local> {
&mut self.locals
}
pub fn get_local(&self, index: u8) -> Result<&Local, ChunkError> {
self.locals
.get(index as usize)
.ok_or(ChunkError::LocalIndexOutOfBounds {
index: index as usize,
})
}
pub fn get_local_mut(&mut self, index: u8) -> Result<&mut Local, ChunkError> {
self.locals
.get_mut(index as usize)
.ok_or(ChunkError::LocalIndexOutOfBounds {
index: index as usize,
})
}
pub fn current_scope(&self) -> Scope {
self.current_scope
}
pub fn get_constant(&self, index: u8) -> Result<&Value, ChunkError> {
self.constants
.get(index as usize)
.ok_or(ChunkError::ConstantIndexOutOfBounds {
index: index as usize,
})
}
pub fn push_or_get_constant(&mut self, value: Value) -> u8 {
if let Some(index) = self
.constants
.iter()
.position(|constant| constant == &value)
{
return index as u8;
}
self.constants.push(value);
(self.constants.len() - 1) as u8
}
pub fn get_identifier(&self, local_index: u8) -> Option<String> {
self.locals.get(local_index as usize).and_then(|local| {
self.constants
.get(local.identifier_index as usize)
.map(|value| value.to_string())
})
}
pub fn begin_scope(&mut self) {
self.block_index += 1;
self.current_scope.block_index = self.block_index;
self.current_scope.depth += 1;
}
pub fn end_scope(&mut self) {
self.current_scope.depth -= 1;
if self.current_scope.depth == 0 {
self.current_scope.block_index = 0;
} else {
self.current_scope.block_index -= 1;
}
}
pub fn expect_not_poisoned(&self) -> Result<(), ChunkError> {
if self.is_poisoned {
Err(ChunkError::PoisonedChunk)
} else {
Ok(())
}
}
pub fn get_constant_type(&self, constant_index: u8) -> Option<Type> {
self.constants
.get(constant_index as usize)
.map(|value| value.r#type())
}
pub fn get_local_type(&self, local_index: u8) -> Option<Type> {
self.locals.get(local_index as usize)?.r#type.clone()
}
pub fn get_register_type(&self, register_index: u8) -> Option<Type> {
let local_type_option = self
.locals
.iter()
.find(|local| local.register_index == register_index)
.map(|local| local.r#type.clone());
if let Some(local_type) = local_type_option {
return local_type;
}
self.instructions
.iter()
.enumerate()
.find_map(|(index, (instruction, _))| {
if let Operation::LoadList = instruction.operation() {
if instruction.a() == register_index {
let mut length = (instruction.c() - instruction.b() + 1) as usize;
let mut item_type = Type::Any;
let distance_to_end = self.len() - index;
for (instruction, _) in self
.instructions()
.iter()
.rev()
.skip(distance_to_end)
.take(length)
{
if let Operation::Close = instruction.operation() {
length -= (instruction.c() - instruction.b()) as usize;
} else if let Type::Any = item_type {
item_type = instruction.yielded_type(self).unwrap_or(Type::Any);
}
}
return Some(Type::List {
item_type: Box::new(item_type),
length,
});
}
}
if instruction.yields_value() && instruction.a() == register_index {
instruction.yielded_type(self)
} else {
None
}
})
}
pub fn return_type(&self) -> Option<Type> {
let returns_value = self
.instructions()
.last()
.map(|(instruction, _)| {
debug_assert!(matches!(instruction.operation(), Operation::Return));
instruction.b_as_boolean()
})
.unwrap_or(false);
if returns_value {
self.instructions.iter().rev().find_map(|(instruction, _)| {
if instruction.yields_value() {
instruction.yielded_type(self)
} else {
None
}
})
} else {
None
}
}
pub fn disassembler(&self) -> Disassembler {
Disassembler::new(self)
}
}
impl Display for Chunk {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let disassembler = self.disassembler().styled(false);
write!(f, "{}", disassembler.disassemble())
}
}
impl Debug for Chunk {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let disassembly = self.disassembler().styled(false).disassemble();
if cfg!(debug_assertions) {
write!(f, "\n{}", disassembly)
} else {
write!(f, "{}", disassembly)
}
}
}
impl Eq for Chunk {}
impl PartialEq for Chunk {
fn eq(&self, other: &Self) -> bool {
self.instructions == other.instructions
&& self.constants == other.constants
&& self.locals == other.locals
}
}
/// A scoped variable.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Local {
/// The index of the identifier in the constants table.
pub identifier_index: u8,
/// The expected type of the local's value.
pub r#type: Option<Type>,
/// Whether the local is mutable.
pub is_mutable: bool,
/// Scope where the variable was declared.
pub scope: Scope,
/// Expected location of a local's value.
pub register_index: u8,
}
impl Local {
/// Creates a new Local instance.
pub fn new(
identifier_index: u8,
r#type: Option<Type>,
mutable: bool,
scope: Scope,
register_index: u8,
) -> Self {
Self {
identifier_index,
r#type,
is_mutable: mutable,
scope,
register_index,
}
}
}
/// Variable locality, as defined by its depth and block index.
///
/// The `block index` is a unique identifier for a block within a chunk. It is used to differentiate
/// between blocks that are not nested together but have the same depth, i.e. sibling scopes. If the
/// `block_index` is 0, then the scope is the root scope of the chunk. The `block_index` is always 0
/// when the `depth` is 0. See [Chunk::begin_scope][] and [Chunk::end_scope][] to see how scopes are
/// incremented and decremented.
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Scope {
/// Level of block nesting.
pub depth: u8,
/// Index of the block in the chunk.
pub block_index: u8,
}
impl Scope {
pub fn new(depth: u8, block_index: u8) -> Self {
Self { depth, block_index }
}
pub fn contains(&self, other: &Self) -> bool {
match self.depth.cmp(&other.depth) {
Ordering::Less => false,
Ordering::Greater => self.block_index >= other.block_index,
Ordering::Equal => self.block_index == other.block_index,
}
}
}
impl Display for Scope {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.depth, self.block_index)
}
}
/// Errors that can occur when using a [`Chunk`].
#[derive(Clone, Debug, PartialEq)]
pub enum ChunkError {
ConstantIndexOutOfBounds { index: usize },
InstructionIndexOutOfBounds { index: usize },
LocalIndexOutOfBounds { index: usize },
PoisonedChunk,
}
impl Display for ChunkError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
ChunkError::ConstantIndexOutOfBounds { index } => {
write!(f, "Constant index {} out of bounds", index)
}
ChunkError::InstructionIndexOutOfBounds { index } => {
write!(f, "Instruction index {} out of bounds", index)
}
ChunkError::LocalIndexOutOfBounds { index } => {
write!(f, "Local index {} out of bounds", index)
}
ChunkError::PoisonedChunk => write!(f, "Chunk is poisoned"),
}
}
}

1977
dust-lang/src/compiler.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,107 +0,0 @@
use std::{
collections::HashMap,
fmt::{self, Display, Formatter},
};
use serde::{Deserialize, Serialize};
use crate::{Identifier, Struct, StructType, TypeConflict, Value};
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Constructor {
pub struct_type: StructType,
}
impl Constructor {
pub fn construct_unit(&self) -> Result<Value, ConstructError> {
if let StructType::Unit { name } = &self.struct_type {
Ok(Value::r#struct(Struct::Unit { name: name.clone() }))
} else {
Err(ConstructError::ExpectedUnit)
}
}
pub fn construct_tuple(&self, fields: Vec<Value>) -> Result<Value, ConstructError> {
if let StructType::Tuple {
name: expected_name,
fields: expected_fields,
} = &self.struct_type
{
if fields.len() != expected_fields.len() {
return Err(ConstructError::FieldCountMismatch);
}
for (i, value) in fields.iter().enumerate() {
let expected_type = expected_fields.get(i).unwrap();
let actual_type = value.r#type();
expected_type.check(&actual_type)?;
}
Ok(Value::r#struct(Struct::Tuple {
name: expected_name.clone(),
fields,
}))
} else {
Err(ConstructError::ExpectedTuple)
}
}
pub fn construct_fields(
&self,
fields: HashMap<Identifier, Value>,
) -> Result<Value, ConstructError> {
if let StructType::Fields {
name: expected_name,
fields: expected_fields,
} = &self.struct_type
{
if fields.len() != expected_fields.len() {
return Err(ConstructError::FieldCountMismatch);
}
for (field_name, field_value) in fields.iter() {
let expected_type = expected_fields.get(field_name).unwrap();
let actual_type = field_value.r#type();
expected_type.check(&actual_type)?;
}
Ok(Value::r#struct(Struct::Fields {
name: expected_name.clone(),
fields,
}))
} else {
Err(ConstructError::ExpectedFields)
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum ConstructError {
FieldCountMismatch,
ExpectedUnit,
ExpectedTuple,
ExpectedFields,
TypeConflict(TypeConflict),
}
impl From<TypeConflict> for ConstructError {
fn from(conflict: TypeConflict) -> Self {
Self::TypeConflict(conflict)
}
}
impl Display for ConstructError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
ConstructError::FieldCountMismatch => write!(f, "Field count mismatch"),
ConstructError::ExpectedUnit => write!(f, "Expected unit struct"),
ConstructError::ExpectedTuple => write!(f, "Expected tuple struct"),
ConstructError::ExpectedFields => write!(f, "Expected fields struct"),
ConstructError::TypeConflict(TypeConflict { expected, actual }) => {
write!(f, "Type conflict: expected {}, got {}", expected, actual)
}
}
}
}

View File

@ -1,651 +0,0 @@
//! Garbage-collecting context for variables.
use std::{
collections::HashMap,
fmt::{self, Display, Formatter},
sync::{
atomic::{AtomicUsize, Ordering},
Arc, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak,
},
};
use crate::{Constructor, Identifier, StructType, Type, Value};
pub type Associations = HashMap<Identifier, (ContextData, usize)>;
static ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
fn next_id() -> usize {
ID_COUNTER.fetch_add(1, Ordering::SeqCst)
}
/// Garbage-collecting context for variables.
#[derive(Debug, Clone)]
pub struct Context {
inner: Arc<ContextInner>,
}
impl Context {
pub fn new() -> Self {
Self::with_data(HashMap::new())
}
pub fn with_data(data: Associations) -> Self {
Self {
inner: Arc::new(ContextInner {
associations: RwLock::new(data),
parent: None,
is_immutable: false,
id: next_id(),
}),
}
}
pub fn with_data_immutable(data: Associations) -> Self {
Self {
inner: Arc::new(ContextInner {
associations: RwLock::new(data),
parent: None,
is_immutable: true,
id: next_id(),
}),
}
}
/// Creates a deep copy of another context.
pub fn with_data_from(other: &Self) -> Result<Self, ContextError> {
let mut associations = HashMap::new();
for (identifier, (context_data, position)) in other.inner.associations.read()?.iter() {
associations.insert(identifier.clone(), (context_data.clone(), *position));
}
Ok(Self::with_data(associations))
}
pub fn create_child(&self) -> Self {
Self {
inner: Arc::new(ContextInner {
associations: RwLock::new(HashMap::new()),
parent: Some(Arc::downgrade(&self.inner)),
is_immutable: false,
id: next_id(),
}),
}
}
pub fn id(&self) -> usize {
self.inner.id
}
/// Returns the number of associated identifiers in the context.
pub fn association_count(&self) -> Result<usize, ContextError> {
self.inner.association_count()
}
/// Returns a boolean indicating whether the identifier is in the context.
pub fn contains(&self, identifier: &Identifier) -> Result<bool, ContextError> {
self.inner.contains(identifier)
}
/// Returns the full ContextData and Span if the context contains the given identifier.
pub fn get(
&self,
identifier: &Identifier,
) -> Result<Option<(ContextData, usize)>, ContextError> {
self.inner.get(identifier)
}
/// Returns the type associated with the given identifier.
pub fn get_type(&self, identifier: &Identifier) -> Result<Option<Type>, ContextError> {
self.inner.get_type(identifier)
}
/// Returns the ContextData associated with the identifier.
pub fn get_data(&self, identifier: &Identifier) -> Result<Option<ContextData>, ContextError> {
self.inner.get_data(identifier)
}
/// Returns the value associated with the identifier.
pub fn get_variable_value(
&self,
identifier: &Identifier,
) -> Result<Option<Value>, ContextError> {
self.inner.get_variable_value(identifier)
}
/// Returns the constructor associated with the identifier.
pub fn get_constructor(
&self,
identifier: &Identifier,
) -> Result<Option<Constructor>, ContextError> {
self.inner.get_constructor(identifier)
}
/// Returns the constructor type associated with the identifier.
pub fn get_constructor_type(
&self,
identifier: &Identifier,
) -> Result<Option<StructType>, ContextError> {
self.inner.get_constructor_type(identifier)
}
/// Associates an identifier with a variable type, with a position given for garbage collection.
pub fn set_variable_type(
&self,
identifier: Identifier,
r#type: Type,
) -> Result<(), ContextError> {
self.inner.set_variable_type(identifier, r#type)
}
/// Associates an identifier with a variable value.
pub fn set_variable_value(
&self,
identifier: Identifier,
value: Value,
) -> Result<(), ContextError> {
self.inner.set_variable_value(identifier, value)
}
/// Associates an identifier with a constructor.
pub fn set_constructor(
&self,
identifier: Identifier,
constructor: Constructor,
) -> Result<(), ContextError> {
self.inner.set_constructor(identifier, constructor)
}
/// Associates an identifier with a constructor type, with a position given for garbage
/// collection.
pub fn set_constructor_type(
&self,
identifier: Identifier,
struct_type: StructType,
) -> Result<(), ContextError> {
self.inner.set_constructor_type(identifier, struct_type)
}
/// Collects garbage up to the given position, removing all variables with lesser positions.
pub fn collect_garbage(&self, position: usize) -> Result<(), ContextError> {
self.inner.collect_garbage(position)
}
/// Updates an associated identifier's last known position, allowing it to live longer in the
/// program. Returns a boolean indicating whether the identifier was found. If the identifier is
/// not found in the current context, the parent context is searched but parent context's
/// position is not updated.
pub fn set_position(
&self,
identifier: &Identifier,
position: usize,
) -> Result<bool, ContextError> {
self.inner.set_position(identifier, position)
}
/// Recovers the context from a poisoned state by recovering data from an error.
///
/// This method is not used.
pub fn _recover_from_poison(&mut self, recovered: &RwLockReadGuard<Associations>) {
log::debug!("Context is recovering from poison error");
let mut new_associations = HashMap::new();
for (identifier, (context_data, position)) in recovered.iter() {
new_associations.insert(identifier.clone(), (context_data.clone(), *position));
}
self.inner = Arc::new(ContextInner {
associations: RwLock::new(new_associations),
parent: None,
is_immutable: false,
id: next_id(),
});
}
}
impl Default for Context {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct ContextInner {
id: usize,
associations: RwLock<Associations>,
parent: Option<Weak<ContextInner>>,
is_immutable: bool,
}
impl ContextInner {
fn parent(&self) -> Option<Arc<ContextInner>> {
self.parent.as_ref().and_then(|parent| parent.upgrade())
}
/// Returns the number of associated identifiers in the context.
pub fn association_count(&self) -> Result<usize, ContextError> {
Ok(self.associations.read()?.len())
}
/// Returns a boolean indicating whether the identifier is in the context.
pub fn contains(&self, identifier: &Identifier) -> Result<bool, ContextError> {
if self.associations.read()?.contains_key(identifier) {
Ok(true)
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
parent.contains(identifier)
} else {
Ok(false)
}
} else {
Ok(false)
}
}
/// Returns the full ContextData and Span if the context contains the given identifier.
pub fn get(
&self,
identifier: &Identifier,
) -> Result<Option<(ContextData, usize)>, ContextError> {
if let Some((variable_data, position)) = self.associations.read()?.get(identifier) {
return Ok(Some((variable_data.clone(), *position)));
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get(identifier);
}
}
Ok(None)
}
/// Returns the type associated with the given identifier.
pub fn get_type(&self, identifier: &Identifier) -> Result<Option<Type>, ContextError> {
match self.associations.read()?.get(identifier) {
Some((ContextData::VariableType(r#type), _)) => return Ok(Some(r#type.clone())),
Some((ContextData::VariableValue(value), _)) => return Ok(Some(value.r#type())),
Some((ContextData::ConstructorType(struct_type), _)) => {
return Ok(Some(Type::Struct(struct_type.clone())))
}
_ => {}
}
if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get_type(identifier);
}
}
Ok(None)
}
/// Returns the ContextData associated with the identifier.
pub fn get_data(&self, identifier: &Identifier) -> Result<Option<ContextData>, ContextError> {
if let Some((variable_data, _)) = self.associations.read()?.get(identifier) {
return Ok(Some(variable_data.clone()));
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get_data(identifier);
}
}
Ok(None)
}
/// Returns the value associated with the identifier.
pub fn get_variable_value(
&self,
identifier: &Identifier,
) -> Result<Option<Value>, ContextError> {
if let Some((ContextData::VariableValue(value), _)) =
self.associations.read()?.get(identifier)
{
return Ok(Some(value.clone()));
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get_variable_value(identifier);
}
}
Ok(None)
}
/// Returns the constructor associated with the identifier.
pub fn get_constructor(
&self,
identifier: &Identifier,
) -> Result<Option<Constructor>, ContextError> {
if let Some((ContextData::Constructor(constructor), _)) =
self.associations.read()?.get(identifier)
{
return Ok(Some(constructor.clone()));
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get_constructor(identifier);
}
}
Ok(None)
}
/// Returns the constructor type associated with the identifier.
pub fn get_constructor_type(
&self,
identifier: &Identifier,
) -> Result<Option<StructType>, ContextError> {
let read_associations = self.associations.read()?;
if let Some((context_data, _)) = read_associations.get(identifier) {
return match context_data {
ContextData::Constructor(constructor) => Ok(Some(constructor.struct_type.clone())),
ContextData::ConstructorType(struct_type) => Ok(Some(struct_type.clone())),
_ => Ok(None),
};
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get_constructor_type(identifier);
}
}
Ok(None)
}
/// Associates an identifier with a variable type.
pub fn set_variable_type(
&self,
identifier: Identifier,
r#type: Type,
) -> Result<(), ContextError> {
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
log::trace!("Setting {identifier} to type {type} in context {}", self.id);
let mut associations = self.associations.write()?;
let last_position = associations
.get(&identifier)
.map(|(_, last_position)| *last_position)
.unwrap_or_default();
associations.insert(
identifier,
(ContextData::VariableType(r#type), last_position),
);
Ok(())
}
/// Associates an identifier with a variable value.
pub fn set_variable_value(
&self,
identifier: Identifier,
value: Value,
) -> Result<(), ContextError> {
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
log::trace!(
"Setting {identifier} to value {value} in context {}",
self.id
);
let mut associations = self.associations.write()?;
let last_position = associations
.get(&identifier)
.map(|(_, last_position)| *last_position)
.unwrap_or_default();
associations.insert(
identifier,
(ContextData::VariableValue(value), last_position),
);
Ok(())
}
/// Associates an identifier with a constructor.
pub fn set_constructor(
&self,
identifier: Identifier,
constructor: Constructor,
) -> Result<(), ContextError> {
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
log::trace!(
"Setting {identifier} to constructor {constructor:?} in context {}",
self.id
);
let mut associations = self.associations.write()?;
let last_position = associations
.get(&identifier)
.map(|(_, last_position)| *last_position)
.unwrap_or_default();
associations.insert(
identifier,
(ContextData::Constructor(constructor), last_position),
);
Ok(())
}
/// Associates an identifier with a constructor type, with a position given for garbage
/// collection.
pub fn set_constructor_type(
&self,
identifier: Identifier,
struct_type: StructType,
) -> Result<(), ContextError> {
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
log::trace!(
"Setting {identifier} to constructor of type {struct_type} in context {}",
self.id
);
let mut variables = self.associations.write()?;
let last_position = variables
.get(&identifier)
.map(|(_, last_position)| *last_position)
.unwrap_or_default();
variables.insert(
identifier,
(ContextData::ConstructorType(struct_type), last_position),
);
Ok(())
}
/// Collects garbage up to the given position, removing all variables with lesser positions.
pub fn collect_garbage(&self, position: usize) -> Result<(), ContextError> {
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
log::trace!("Collecting garbage up to {position} in context {}", self.id);
let mut variables = self.associations.write()?;
variables.retain(|identifier, (_, last_used)| {
let should_drop = position >= *last_used;
if should_drop {
log::trace!("Removing {identifier} from context {}", self.id);
}
!should_drop
});
variables.shrink_to_fit();
Ok(())
}
/// Updates an associated identifier's last known position, allowing it to live longer in the
/// program. Returns a boolean indicating whether the identifier was found. If the identifier is
/// not found in the current context, the parent context is searched but parent context's
/// position is not updated.
pub fn set_position(
&self,
identifier: &Identifier,
position: usize,
) -> Result<bool, ContextError> {
let found = self.update_position_if_found(identifier, position)?;
if found {
return Ok(true);
}
let found_in_ancestor = if let Some(parent) = &self.parent() {
if parent.is_immutable {
false
} else {
parent.update_position_if_found(identifier, position)?
}
} else {
false
};
if found_in_ancestor {
return Ok(true);
}
let mut associations = self.associations.write()?;
log::trace!(
"Reserving {identifier} at position {position:?} in context {}",
self.id
);
associations.insert(identifier.clone(), (ContextData::Reserved, position));
Ok(false)
}
fn update_position_if_found(
&self,
identifier: &Identifier,
position: usize,
) -> Result<bool, ContextError> {
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
let mut associations = self.associations.write()?;
if let Some((_, last_position)) = associations.get_mut(identifier) {
log::trace!(
"Updating {identifier}'s last position to {position:?} in context {}",
self.id
);
*last_position = position;
Ok(true)
} else {
Ok(false)
}
}
}
#[derive(Debug, Clone)]
pub enum ContextData {
Constructor(Constructor),
ConstructorType(StructType),
VariableValue(Value),
VariableType(Type),
Reserved,
}
#[derive(Debug, Clone)]
pub enum ContextError {
CannotMutateImmutableContext,
PoisonErrorRecovered(Arc<Associations>),
}
impl From<PoisonError<RwLockWriteGuard<'_, Associations>>> for ContextError {
fn from(error: PoisonError<RwLockWriteGuard<'_, Associations>>) -> Self {
let associations = error.into_inner().clone();
Self::PoisonErrorRecovered(Arc::new(associations))
}
}
impl From<PoisonError<RwLockReadGuard<'_, Associations>>> for ContextError {
fn from(error: PoisonError<RwLockReadGuard<'_, Associations>>) -> Self {
let associations = error.into_inner().clone();
Self::PoisonErrorRecovered(Arc::new(associations))
}
}
impl PartialEq for ContextError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::CannotMutateImmutableContext, Self::CannotMutateImmutableContext) => true,
(Self::PoisonErrorRecovered(left), Self::PoisonErrorRecovered(right)) => {
Arc::ptr_eq(left, right)
}
_ => false,
}
}
}
impl Display for ContextError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::CannotMutateImmutableContext => write!(f, "Cannot mutate immutable context"),
Self::PoisonErrorRecovered(associations) => {
write!(
f,
"Context poisoned with {} associations recovered",
associations.len()
)
}
}
}
}
#[cfg(test)]
mod tests {
use crate::{parse, Vm};
use super::*;
#[test]
fn context_removes_variables() {
let source = "
let x = 5;
let y = 10;
let z = x + y;
z
";
let ast = parse(source).unwrap();
let context = ast.context.clone();
assert_eq!(Vm.run(ast), Ok(Some(Value::integer(15))));
assert_eq!(context.association_count().unwrap(), 0);
}
#[test]
fn garbage_collector_does_not_break_loops() {
let source = "
let mut z = 0;
while z < 10 {
z += 1;
}
";
let ast = parse(source).unwrap();
let context = ast.context.clone();
assert_eq!(Vm.run(ast), Ok(None));
assert_eq!(context.association_count().unwrap(), 0);
}
}

View File

@ -1,39 +0,0 @@
use std::{collections::HashMap, sync::OnceLock};
use crate::{BuiltInFunction, Context, ContextData, Function, Identifier, Value};
static CORE_LIBRARY: OnceLock<Context> = OnceLock::new();
pub fn core_library<'a>() -> &'a Context {
CORE_LIBRARY.get_or_init(|| {
Context::with_data_immutable(HashMap::from([
(
Identifier::new("to_string"),
(
ContextData::VariableValue(Value::function(Function::BuiltIn(
BuiltInFunction::ToString,
))),
0,
),
),
(
Identifier::new("read_line"),
(
ContextData::VariableValue(Value::function(Function::BuiltIn(
BuiltInFunction::ReadLine,
))),
0,
),
),
(
Identifier::new("write_line"),
(
ContextData::VariableValue(Value::function(Function::BuiltIn(
BuiltInFunction::WriteLine,
))),
0,
),
),
]))
})
}

View File

@ -0,0 +1,366 @@
//! Tool for disassembling chunks into a human-readable format.
//!
//! A disassembler can be created by calling [Chunk::disassembler][] or by instantiating one with
//! [Disassembler::new][].
//!
//! # Options
//!
//! The disassembler can be customized with the 'styled' option, which will apply ANSI color codes
//! to the output.
//!
//! # Output
//!
//! The output of [Disassembler::disassemble] is a string that can be printed to the console or
//! written to a file. Below is an example of the disassembly for a simple "Hello, world!" program.
//!
//! ```text
//! ┌──────────────────────────────────────────────────────────────────────────────┐
//! │ <file name omitted> │
//! │ │
//! │ write_line("Hello, world!") │
//! │ │
//! │ 3 instructions, 1 constants, 0 locals, returns none │
//! │ │
//! │ Instructions │
//! │ ------------ │
//! │ i BYTECODE OPERATION INFO TYPE POSITION │
//! │--- -------- ------------- -------------------- ---------------- ------------ │
//! │ 0 03 LOAD_CONSTANT R0 = C0 str (11, 26) │
//! │ 1 1390117 CALL_NATIVE write_line(R0) (0, 27) │
//! │ 2 18 RETURN (27, 27) │
//! │┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈│
//! │ Locals │
//! │ ------ │
//! │ i IDENTIFIER TYPE MUTABLE SCOPE REGISTER │
//! │ --- ---------- ---------------- ------- ------- -------- │
//! │┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈│
//! │ Constants │
//! │ --------- │
//! │ i VALUE │
//! │ --- --------------- │
//! │ 0 Hello, world! │
//! └──────────────────────────────────────────────────────────────────────────────┘
//! ```
use std::env::current_exe;
use colored::Colorize;
use crate::{Chunk, ConcreteValue, Local, Value};
const INSTRUCTION_HEADER: [&str; 4] = [
"Instructions",
"------------",
" i BYTECODE OPERATION INFO TYPE POSITION ",
"--- -------- ------------- -------------------- ---------------- ----------",
];
const CONSTANT_HEADER: [&str; 4] = [
"Constants",
"---------",
" i VALUE ",
"--- ---------------",
];
const LOCAL_HEADER: [&str; 4] = [
"Locals",
"------",
" i IDENTIFIER TYPE MUTABLE SCOPE REGISTER",
"--- ---------- ---------------- ------- ------- --------",
];
/// Builder that constructs a human-readable representation of a chunk.
///
/// See the [module-level documentation](index.html) for more information.
pub struct Disassembler<'a> {
output: String,
chunk: &'a Chunk,
source: Option<&'a str>,
// Options
styled: bool,
indent: usize,
}
impl<'a> Disassembler<'a> {
pub fn new(chunk: &'a Chunk) -> Self {
Self {
output: String::new(),
chunk,
source: None,
styled: false,
indent: 0,
}
}
/// The default width of the disassembly output. To correctly align the output, this should
/// return the width of the longest line that the disassembler is guaranteed to produce.
pub fn default_width() -> usize {
let longest_line = INSTRUCTION_HEADER[3];
longest_line.chars().count().max(80)
}
pub fn source(mut self, source: &'a str) -> Self {
self.source = Some(source);
self
}
pub fn styled(mut self, styled: bool) -> Self {
self.styled = styled;
self
}
pub fn indent(mut self, indent: usize) -> Self {
self.indent = indent;
self
}
fn push(
&mut self,
text: &str,
center: bool,
style_bold: bool,
style_dim: bool,
add_border: bool,
) {
let width = Disassembler::default_width();
let characters = text.chars().collect::<Vec<char>>();
let content_width = if add_border { width - 2 } else { width };
let (line_characters, remainder) = characters
.split_at_checked(content_width)
.unwrap_or((characters.as_slice(), &[]));
let (left_pad_length, right_pad_length) = {
let extra_space = content_width.saturating_sub(characters.len());
if center {
(extra_space / 2, extra_space / 2 + extra_space % 2)
} else {
(0, extra_space)
}
};
let content = if style_bold {
line_characters
.iter()
.collect::<String>()
.bold()
.to_string()
} else if style_dim {
line_characters
.iter()
.collect::<String>()
.dimmed()
.to_string()
} else {
line_characters.iter().collect::<String>()
};
let length_before_content = self.output.chars().count();
for _ in 0..self.indent {
self.output.push_str("");
}
if add_border {
self.output.push('│');
}
self.output.push_str(&" ".repeat(left_pad_length));
self.output.push_str(&content);
self.output.push_str(&" ".repeat(right_pad_length));
let length_after_content = self.output.chars().count();
let line_length = length_after_content - length_before_content;
if line_length < content_width - 1 {
self.output
.push_str(&" ".repeat(content_width - line_length));
}
if add_border {
self.output.push('│');
}
self.output.push('\n');
if !remainder.is_empty() {
self.push(
remainder.iter().collect::<String>().as_str(),
center,
style_bold,
style_dim,
add_border,
);
}
}
fn push_header(&mut self, header: &str) {
self.push(header, true, self.styled, false, true);
}
fn push_details(&mut self, details: &str) {
self.push(details, true, false, false, true);
}
fn push_border(&mut self, border: &str) {
self.push(border, false, false, false, false);
}
fn push_empty(&mut self) {
self.push("", false, false, false, true);
}
pub fn disassemble(mut self) -> String {
let width = Disassembler::default_width();
let top_border = "".to_string() + &"".repeat(width - 2) + "";
let section_border = "".to_string() + &"".repeat(width - 2) + "";
let bottom_border = "".to_string() + &"".repeat(width - 2) + "";
let name_display = self
.chunk
.name()
.map(|identifier| identifier.to_string())
.unwrap_or_else(|| {
current_exe()
.map(|path| path.to_string_lossy().to_string())
.unwrap_or("Chunk Disassembly".to_string())
});
self.push_border(&top_border);
self.push_header(&name_display);
if let Some(source) = self.source {
self.push_empty();
self.push_details(
&source
.replace(" ", "")
.replace("\n\n", " ")
.replace('\n', " "),
);
self.push_empty();
}
let info_line = format!(
"{} instructions, {} constants, {} locals, returns {}",
self.chunk.len(),
self.chunk.constants().len(),
self.chunk.locals().len(),
self.chunk
.return_type()
.map(|r#type| r#type.to_string())
.unwrap_or("none".to_string())
);
self.push(&info_line, true, false, true, true);
self.push_empty();
for line in INSTRUCTION_HEADER {
self.push_header(line);
}
for (index, (instruction, position)) in self.chunk.instructions().iter().enumerate() {
let bytecode = format!("{:02X}", u32::from(instruction));
let operation = instruction.operation().to_string();
let info = instruction.disassembly_info(self.chunk);
let type_display = instruction
.yielded_type(self.chunk)
.map(|r#type| {
let type_string = r#type.to_string();
if type_string.len() > 16 {
format!("{type_string:.13}...")
} else {
type_string
}
})
.unwrap_or(String::with_capacity(0));
let position = position.to_string();
let instruction_display = format!(
"{index:^3} {bytecode:>8} {operation:13} {info:^20} {type_display:^16} {position:10}"
);
self.push_details(&instruction_display);
}
self.push_border(&section_border);
for line in LOCAL_HEADER {
self.push_header(line);
}
for (
index,
Local {
identifier_index,
r#type,
scope,
register_index,
is_mutable: mutable,
},
) in self.chunk.locals().iter().enumerate()
{
let identifier_display = self
.chunk
.constants()
.get(*identifier_index as usize)
.map(|value| value.to_string())
.unwrap_or_else(|| "unknown".to_string());
let type_display = r#type
.as_ref()
.map(|r#type| {
let type_string = r#type.to_string();
if type_string.len() > 16 {
format!("{type_string:.13}...")
} else {
type_string
}
})
.unwrap_or("unknown".to_string());
let local_display = format!(
"{index:^3} {identifier_display:10} {type_display:16} {mutable:7} {scope:7} {register_index:8}"
);
self.push_details(&local_display);
}
self.push_border(&section_border);
for line in CONSTANT_HEADER {
self.push_header(line);
}
for (index, value) in self.chunk.constants().iter().enumerate() {
let value_display = {
let value_string = value.to_string();
if value_string.len() > 15 {
format!("{value_string:.12}...")
} else {
value_string
}
};
let constant_display = format!("{index:^3} {value_display:^15}");
self.push_details(&constant_display);
if let Value::Concrete(ConcreteValue::Function(function)) = value {
let function_disassembly = function
.chunk()
.disassembler()
.styled(self.styled)
.indent(self.indent + 1)
.disassemble();
self.output.push_str(&function_disassembly);
}
}
self.push_border(&bottom_border);
let _ = self.output.trim_end_matches('\n');
self.output
}
}

View File

@ -1,167 +1,53 @@
//! Top-level error handling for the Dust language. //! Top-level Dust errors with source code annotations.
use annotate_snippets::{Level, Renderer, Snippet}; use annotate_snippets::{Level, Renderer, Snippet};
use std::fmt::Display;
use crate::{AnalysisError, ContextError, LexError, ParseError, RuntimeError}; use crate::{vm::VmError, CompileError, Span};
/// An error that occurred during the execution of the Dust language and its /// A top-level error that can occur during the execution of Dust code.
/// corresponding source code. ///
#[derive(Debug, Clone, PartialEq)] /// This error can display nicely formatted messages with source code annotations.
#[derive(Debug, PartialEq)]
pub enum DustError<'src> { pub enum DustError<'src> {
ContextError(ContextError), Compile {
error: CompileError,
source: &'src str,
},
Runtime { Runtime {
runtime_error: RuntimeError, error: VmError,
source: &'src str, source: &'src str,
}, },
Analysis {
analysis_errors: Vec<AnalysisError>,
source: &'src str,
},
Parse {
parse_error: ParseError,
source: &'src str,
},
Lex {
lex_error: LexError,
source: &'src str,
},
}
impl<'src> From<ContextError> for DustError<'src> {
fn from(error: ContextError) -> Self {
Self::ContextError(error)
}
} }
impl<'src> DustError<'src> { impl<'src> DustError<'src> {
pub fn runtime(runtime_error: RuntimeError, source: &'src str) -> Self {
DustError::Runtime {
runtime_error,
source,
}
}
pub fn analysis<T: Into<Vec<AnalysisError>>>(analysis_errors: T, source: &'src str) -> Self {
DustError::Analysis {
analysis_errors: analysis_errors.into(),
source,
}
}
pub fn parse(parse_error: ParseError, source: &'src str) -> Self {
DustError::Parse {
parse_error,
source,
}
}
pub fn lex(lex_error: LexError, source: &'src str) -> Self {
DustError::Lex { lex_error, source }
}
pub fn title(&self) -> &'static str {
match self {
DustError::ContextError(_) => "Context error",
DustError::Runtime { .. } => "Runtime error",
DustError::Analysis { .. } => "Analysis error",
DustError::Parse { .. } => "Parse error",
DustError::Lex { .. } => "Lex error",
}
}
pub fn source(&self) -> &'src str {
match self {
DustError::ContextError(_) => "",
DustError::Runtime { source, .. } => source,
DustError::Analysis { source, .. } => source,
DustError::Parse { source, .. } => source,
DustError::Lex { source, .. } => source,
}
}
pub fn report(&self) -> String { pub fn report(&self) -> String {
let mut report = String::new(); let mut report = String::new();
let renderer = Renderer::styled(); let renderer = Renderer::styled();
match self { match self {
DustError::ContextError(_) => { DustError::Runtime { error, source } => {
let message = Level::Error.title("Context error");
report.push_str(&renderer.render(message).to_string());
}
DustError::Runtime {
runtime_error,
source,
} => {
let error = runtime_error.root_error();
let position = error.position(); let position = error.position();
let label = error.to_string(); let label = format!("{}: {}", VmError::title(), error.description());
let message = Level::Error let details = error
.title("Runtime error") .details()
.snippet( .unwrap_or_else(|| "While running this code".to_string());
Snippet::source(source) let message = Level::Error.title(&label).snippet(
.fold(true)
.annotation(Level::Error.span(position.0..position.1).label(&label)),
)
.footer(
Level::Error
.title("This error occured during the execution of the Dust program."),
);
report.push_str(&renderer.render(message).to_string());
report.push_str("\n\n");
}
DustError::Analysis {
analysis_errors,
source,
} => {
for error in analysis_errors {
let position = error.position();
let label = error.to_string();
let message =
Level::Warning
.title("Analysis error")
.snippet(Snippet::source(source).fold(true).annotation(
Level::Warning.span(position.0..position.1).label(&label),
))
.footer(
Level::Warning
.title("This error was found without running the program."),
);
report.push_str(&renderer.render(message).to_string());
report.push_str("\n\n");
}
}
DustError::Parse {
parse_error,
source,
} => {
if let ParseError::Lex(lex_error) = parse_error {
let lex_error_report = DustError::lex(lex_error.clone(), source).report();
report.push_str(&lex_error_report);
return report;
}
let position = parse_error.position();
let label = parse_error.to_string();
let message = Level::Error.title("Parse error").snippet(
Snippet::source(source) Snippet::source(source)
.fold(true) .fold(false)
.annotation(Level::Error.span(position.0..position.1).label(&label)), .annotation(Level::Error.span(position.0..position.1).label(&details)),
); );
report.push_str(&renderer.render(message).to_string()); report.push_str(&renderer.render(message).to_string());
} }
DustError::Lex { lex_error, source } => { DustError::Compile { error, source } => {
let position = lex_error.position(); let position = error.position();
let label = lex_error.to_string(); let label = format!("{}: {}", CompileError::title(), error.description());
let message = Level::Error.title("Lex error").snippet( let details = error
.details()
.unwrap_or_else(|| "While parsing this code".to_string());
let message = Level::Error.title(&label).snippet(
Snippet::source(source) Snippet::source(source)
.fold(true) .fold(false)
.annotation(Level::Error.span(position.0..position.1).label(&label)), .annotation(Level::Error.span(position.0..position.1).label(&details)),
); );
report.push_str(&renderer.render(message).to_string()); report.push_str(&renderer.render(message).to_string());
@ -172,22 +58,9 @@ impl<'src> DustError<'src> {
} }
} }
impl Display for DustError<'_> { pub trait AnnotatedError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn title() -> &'static str;
match self { fn description(&self) -> &'static str;
DustError::ContextError(context_error) => write!(f, "{context_error}"), fn details(&self) -> Option<String>;
DustError::Runtime { runtime_error, .. } => write!(f, "{runtime_error}"), fn position(&self) -> Span;
DustError::Analysis {
analysis_errors, ..
} => {
for error in analysis_errors {
write!(f, "{error} ")?;
}
Ok(())
}
DustError::Parse { parse_error, .. } => write!(f, "{parse_error}"),
DustError::Lex { lex_error, .. } => write!(f, "{lex_error}"),
}
}
} }

View File

@ -1,41 +0,0 @@
use crate::{Constructor, RuntimeError, Span, StructType, Type, Value};
#[derive(Debug, Clone, PartialEq)]
pub enum Evaluation {
Break(Option<Value>),
Constructor(Constructor),
Return(Option<Value>),
}
impl Evaluation {
pub fn value(self) -> Option<Value> {
match self {
Evaluation::Return(value_option) => value_option,
_ => None,
}
}
pub fn expect_value(self, position: Span) -> Result<Value, RuntimeError> {
if let Evaluation::Return(Some(value)) = self {
Ok(value)
} else {
Err(RuntimeError::ExpectedValue { position })
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum TypeEvaluation {
Break(Option<Type>),
Constructor(StructType),
Return(Option<Type>),
}
impl TypeEvaluation {
pub fn r#type(self) -> Option<Type> {
match self {
TypeEvaluation::Return(type_option) => type_option,
_ => None,
}
}
}

224
dust-lang/src/formatter.rs Normal file
View File

@ -0,0 +1,224 @@
//! Formatting tools
use std::mem::replace;
use colored::{ColoredString, Colorize, CustomColor};
use crate::{CompileError, DustError, LexError, Lexer, Token};
pub fn format(source: &str, line_numbers: bool, colored: bool) -> Result<String, DustError> {
let lexer = Lexer::new(source);
let formatted = Formatter::new(lexer)
.line_numbers(line_numbers)
.colored(colored)
.format()
.map_err(|error| DustError::Compile {
error: CompileError::Lex(error),
source,
})?;
Ok(formatted)
}
#[derive(Debug)]
pub struct Formatter<'src> {
lexer: Lexer<'src>,
output_lines: Vec<(String, LineKind, usize)>,
next_line: String,
indent: usize,
current_token: Token<'src>,
previous_token: Token<'src>,
// Options
line_numbers: bool,
colored: bool,
}
impl<'src> Formatter<'src> {
pub fn new(mut lexer: Lexer<'src>) -> Self {
let (current_token, _) = lexer.next_token().unwrap();
Self {
lexer,
output_lines: Vec::new(),
next_line: String::new(),
indent: 0,
current_token,
previous_token: Token::Eof,
line_numbers: false,
colored: false,
}
}
pub fn line_numbers(mut self, line_numbers: bool) -> Self {
self.line_numbers = line_numbers;
self
}
pub fn colored(mut self, colored: bool) -> Self {
self.colored = colored;
self
}
pub fn format(&mut self) -> Result<String, LexError> {
let mut line_kind = LineKind::Empty;
self.advance()?;
while self.current_token != Token::Eof {
use Token::*;
if self.current_token.is_expression() && line_kind != LineKind::Assignment {
line_kind = LineKind::Expression;
}
match self.current_token {
Boolean(boolean) => {
self.push_colored(boolean.red());
}
Byte(byte) => {
self.push_colored(byte.green());
}
Character(character) => {
self.push_colored(
character
.to_string()
.custom_color(CustomColor::new(225, 150, 150)),
);
}
Float(float) => {
self.push_colored(float.yellow());
}
Identifier(identifier) => {
self.push_colored(identifier.blue());
self.next_line.push(' ');
}
Integer(integer) => {
self.push_colored(integer.cyan());
}
String(string) => {
self.push_colored(string.magenta());
}
LeftCurlyBrace => {
self.next_line.push_str(self.current_token.as_str());
self.commit_line(LineKind::OpenBlock);
self.indent += 1;
}
RightCurlyBrace => {
self.commit_line(LineKind::CloseBlock);
self.next_line.push_str(self.current_token.as_str());
self.indent -= 1;
}
Semicolon => {
if line_kind != LineKind::Assignment {
line_kind = LineKind::Statement;
}
self.next_line.push_str(self.current_token.as_str());
self.commit_line(line_kind);
}
Let => {
line_kind = LineKind::Assignment;
self.push_colored(self.current_token.as_str().bold());
self.next_line.push(' ');
}
Break | Loop | Return | While => {
line_kind = LineKind::Statement;
self.push_colored(self.current_token.as_str().bold());
self.next_line.push(' ');
}
token => {
self.next_line.push_str(token.as_str());
self.next_line.push(' ');
}
}
}
let mut previous_index = 0;
let mut current_index = 1;
while current_index < self.output_lines.len() {
let (_, previous, _) = &self.output_lines[previous_index];
let (_, current, _) = &self.output_lines[current_index];
match (previous, current) {
(LineKind::Empty, _)
| (_, LineKind::Empty)
| (LineKind::OpenBlock, _)
| (_, LineKind::CloseBlock) => {}
(left, right) if left == right => {}
_ => {
self.output_lines
.insert(current_index, ("".to_string(), LineKind::Empty, 0));
}
}
previous_index += 1;
current_index += 1;
}
let formatted = String::with_capacity(
self.output_lines
.iter()
.fold(0, |total, (line, _, _)| total + line.len()),
);
Ok(self.output_lines.iter().enumerate().fold(
formatted,
|acc, (index, (line, _, indent))| {
let index = if index == 0 {
format!("{:<3}| ", index + 1).dimmed()
} else {
format!("\n{:<3}| ", index + 1).dimmed()
};
let left_pad = " ".repeat(*indent);
format!("{}{}{}{}", acc, index, left_pad, line)
},
))
}
fn advance(&mut self) -> Result<(), LexError> {
if self.lexer.is_eof() {
return Ok(());
}
let (new_token, position) = self.lexer.next_token()?;
log::info!(
"Parsing {} at {}",
new_token.to_string().bold(),
position.to_string()
);
self.previous_token = replace(&mut self.current_token, new_token);
Ok(())
}
fn push_colored(&mut self, colored: ColoredString) {
self.next_line.push_str(&format!("{}", colored));
}
fn commit_line(&mut self, line_kind: LineKind) {
self.output_lines
.push((self.next_line.clone(), line_kind, self.indent));
self.next_line.clear();
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LineKind {
Empty,
Assignment,
Expression,
Statement,
OpenBlock,
CloseBlock,
}

View File

@ -1,137 +0,0 @@
//! Key used to identify a value or type.
//!
//! Identifiers are used to uniquely identify values and types in Dust programs. They are
//! cached to avoid duplication. This means that two identifiers with the same text are the same
//! object in memory.
//!
//! # Examples
//! ```
//! # use dust_lang::Identifier;
//! let foo = Identifier::new("foo");
//! let also_foo = Identifier::new("foo");
//! let another_foo = Identifier::new("foo");
//!
//! assert_eq!(foo.strong_count(), 4); // One for each of the above and one for the cache.
//! ```
use std::{
collections::HashMap,
fmt::{self, Display, Formatter},
hash::Hash,
sync::{Arc, OnceLock, RwLock},
};
use serde::{de::Visitor, Deserialize, Serialize};
/// In-use identifiers.
static IDENTIFIER_CACHE: OnceLock<RwLock<HashMap<String, Identifier>>> = OnceLock::new();
/// Returns the identifier cache.
fn identifier_cache<'a>() -> &'a RwLock<HashMap<String, Identifier>> {
IDENTIFIER_CACHE.get_or_init(|| RwLock::new(HashMap::new()))
}
/// Key used to identify a value or type.
///
/// See the [module-level documentation](index.html) for more information.
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct Identifier(Arc<String>);
impl Identifier {
/// Creates a new identifier or returns a clone of an existing one from a cache.
pub fn new<T: ToString>(text: T) -> Self {
let string = text.to_string();
let mut cache = identifier_cache().write().unwrap();
if let Some(old) = cache.get(&string) {
old.clone()
} else {
let new = Identifier(Arc::new(string.clone()));
cache.insert(string, new.clone());
new
}
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn strong_count(&self) -> usize {
Arc::strong_count(&self.0)
}
}
impl From<String> for Identifier {
fn from(string: String) -> Self {
Identifier::new(string)
}
}
impl From<&str> for Identifier {
fn from(slice: &str) -> Self {
Identifier::new(slice)
}
}
impl Display for Identifier {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl Serialize for Identifier {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.0.as_str())
}
}
impl<'de> Deserialize<'de> for Identifier {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_identifier(IdentifierVisitor)
}
}
struct IdentifierVisitor;
impl<'de> Visitor<'de> for IdentifierVisitor {
type Value = Identifier;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a UTF-8 string")
}
fn visit_char<E>(self, v: char) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(v.encode_utf8(&mut [0u8; 4]))
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Identifier::new(v))
}
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(v)
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(&v)
}
}

View File

@ -0,0 +1,902 @@
//! An operation and its arguments for the Dust virtual machine.
//!
//! Each instruction is a 32-bit unsigned integer that is divided into five fields:
//! - Bits 0-6: The operation code.
//! - Bit 7: A flag indicating whether the B argument is a constant.
//! - Bit 8: A flag indicating whether the C argument is a constant.
//! - Bits 9-16: The A argument,
//! - Bits 17-24: The B argument.
//! - Bits 25-32: The C argument.
//!
//! Be careful when working with instructions directly. When modifying an instruction, be sure to
//! account for the fact that setting the A, B, or C arguments to 0 will have no effect. It is
//! usually best to remove instructions and insert new ones in their place instead of mutating them.
use serde::{Deserialize, Serialize};
use crate::{Chunk, NativeFunction, Operation, Type};
/// An operation and its arguments for the Dust virtual machine.
///
/// See the [module-level documentation](index.html) for more information.
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Instruction(u32);
impl Instruction {
pub fn with_operation(operation: Operation) -> Instruction {
Instruction(operation as u32)
}
pub fn r#move(to_register: u8, from_register: u8) -> Instruction {
let mut instruction = Instruction(Operation::Move as u32);
instruction.set_a(to_register);
instruction.set_b(from_register);
instruction
}
pub fn close(from_register: u8, to_register: u8) -> Instruction {
let mut instruction = Instruction(Operation::Close as u32);
instruction.set_b(from_register);
instruction.set_c(to_register);
instruction
}
pub fn load_boolean(to_register: u8, value: bool, skip: bool) -> Instruction {
let mut instruction = Instruction(Operation::LoadBoolean as u32);
instruction.set_a(to_register);
instruction.set_b_to_boolean(value);
instruction.set_c_to_boolean(skip);
instruction
}
pub fn load_constant(to_register: u8, constant_index: u8, skip: bool) -> Instruction {
let mut instruction = Instruction(Operation::LoadConstant as u32);
instruction.set_a(to_register);
instruction.set_b(constant_index);
instruction.set_c_to_boolean(skip);
instruction
}
pub fn load_list(to_register: u8, start_register: u8) -> Instruction {
let mut instruction = Instruction(Operation::LoadList as u32);
instruction.set_a(to_register);
instruction.set_b(start_register);
instruction
}
pub fn load_self(to_register: u8) -> Instruction {
let mut instruction = Instruction(Operation::LoadSelf as u32);
instruction.set_a(to_register);
instruction
}
pub fn define_local(to_register: u8, local_index: u8, is_mutable: bool) -> Instruction {
let mut instruction = Instruction(Operation::DefineLocal as u32);
instruction.set_a(to_register);
instruction.set_b(local_index);
instruction.set_c(if is_mutable { 1 } else { 0 });
instruction
}
pub fn get_local(to_register: u8, local_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::GetLocal as u32);
instruction.set_a(to_register);
instruction.set_b(local_index);
instruction
}
pub fn set_local(from_register: u8, local_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::SetLocal as u32);
instruction.set_a(from_register);
instruction.set_b(local_index);
instruction
}
pub fn add(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Add as u32);
instruction.set_a(to_register);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn subtract(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Subtract as u32);
instruction.set_a(to_register);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn multiply(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Multiply as u32);
instruction.set_a(to_register);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn divide(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Divide as u32);
instruction.set_a(to_register);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn modulo(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Modulo as u32);
instruction.set_a(to_register);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn test(to_register: u8, test_value: bool) -> Instruction {
let mut instruction = Instruction(Operation::Test as u32);
instruction.set_a(to_register);
instruction.set_c_to_boolean(test_value);
instruction
}
pub fn test_set(to_register: u8, argument_index: u8, test_value: bool) -> Instruction {
let mut instruction = Instruction(Operation::TestSet as u32);
instruction.set_a(to_register);
instruction.set_b(argument_index);
instruction.set_c_to_boolean(test_value);
instruction
}
pub fn equal(comparison_boolean: bool, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Equal as u32);
instruction.set_a_to_boolean(comparison_boolean);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn less(comparison_boolean: bool, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Less as u32);
instruction.set_a_to_boolean(comparison_boolean);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn less_equal(comparison_boolean: bool, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::LessEqual as u32);
instruction.set_a_to_boolean(comparison_boolean);
instruction.set_b(left_index);
instruction.set_c(right_index);
instruction
}
pub fn negate(to_register: u8, from_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Negate as u32);
instruction.set_a(to_register);
instruction.set_b(from_index);
instruction
}
pub fn not(to_register: u8, from_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Not as u32);
instruction.set_a(to_register);
instruction.set_b(from_index);
instruction
}
pub fn jump(jump_offset: u8, is_positive: bool) -> Instruction {
let mut instruction = Instruction(Operation::Jump as u32);
instruction.set_b(jump_offset);
instruction.set_c_to_boolean(is_positive);
instruction
}
pub fn call(to_register: u8, function_register: u8, argument_count: u8) -> Instruction {
let mut instruction = Instruction(Operation::Call as u32);
instruction.set_a(to_register);
instruction.set_b(function_register);
instruction.set_c(argument_count);
instruction
}
pub fn call_native(
to_register: u8,
native_fn: NativeFunction,
argument_count: u8,
) -> Instruction {
let mut instruction = Instruction(Operation::CallNative as u32);
let native_fn_byte = native_fn as u8;
instruction.set_a(to_register);
instruction.set_b(native_fn_byte);
instruction.set_c(argument_count);
instruction
}
pub fn r#return(should_return_value: bool) -> Instruction {
let mut instruction = Instruction(Operation::Return as u32);
instruction.set_b_to_boolean(should_return_value);
instruction
}
pub fn operation(&self) -> Operation {
Operation::from((self.0 & 0b0000_0000_0011_1111) as u8)
}
pub fn set_operation(&mut self, operation: Operation) {
self.0 |= u8::from(operation) as u32;
}
pub fn data(&self) -> (Operation, u8, u8, u8, bool, bool) {
(
self.operation(),
self.a(),
self.b(),
self.c(),
self.b_is_constant(),
self.c_is_constant(),
)
}
pub fn a(&self) -> u8 {
(self.0 >> 24) as u8
}
pub fn b(&self) -> u8 {
(self.0 >> 16) as u8
}
pub fn c(&self) -> u8 {
(self.0 >> 8) as u8
}
pub fn a_as_boolean(&self) -> bool {
self.a() != 0
}
pub fn b_as_boolean(&self) -> bool {
self.b() != 0
}
pub fn c_as_boolean(&self) -> bool {
self.c() != 0
}
pub fn set_a_to_boolean(&mut self, boolean: bool) -> &mut Self {
self.set_a(if boolean { 1 } else { 0 });
self
}
pub fn set_b_to_boolean(&mut self, boolean: bool) -> &mut Self {
self.set_b(if boolean { 1 } else { 0 });
self
}
pub fn set_c_to_boolean(&mut self, boolean: bool) -> &mut Self {
self.set_c(if boolean { 1 } else { 0 });
self
}
pub fn set_a(&mut self, to_register: u8) {
self.0 |= (to_register as u32) << 24;
}
pub fn set_b(&mut self, argument: u8) {
self.0 |= (argument as u32) << 16;
}
pub fn set_c(&mut self, argument: u8) {
self.0 |= (argument as u32) << 8;
}
pub fn b_is_constant(&self) -> bool {
self.0 & 0b1000_0000 != 0
}
pub fn c_is_constant(&self) -> bool {
self.0 & 0b0100_0000 != 0
}
pub fn set_b_is_constant(&mut self) -> &mut Self {
self.0 |= 0b1000_0000;
self
}
pub fn set_c_is_constant(&mut self) -> &mut Self {
self.0 |= 0b0100_0000;
self
}
pub fn yields_value(&self) -> bool {
match self.operation() {
Operation::Add
| Operation::Call
| Operation::Divide
| Operation::GetLocal
| Operation::LoadBoolean
| Operation::LoadConstant
| Operation::LoadList
| Operation::LoadSelf
| Operation::Modulo
| Operation::Multiply
| Operation::Negate
| Operation::Not
| Operation::Subtract => true,
Operation::CallNative => {
let native_function = NativeFunction::from(self.b());
native_function.r#type().return_type.is_some()
}
_ => false,
}
}
pub fn yielded_type(&self, chunk: &Chunk) -> Option<Type> {
use Operation::*;
match self.operation() {
Add | Divide | Modulo | Multiply | Subtract => {
if self.b_is_constant() {
chunk.get_constant_type(self.b())
} else {
chunk.get_register_type(self.b())
}
}
LoadBoolean | Not => Some(Type::Boolean),
Negate => {
if self.b_is_constant() {
chunk.get_constant_type(self.b())
} else {
chunk.get_register_type(self.b())
}
}
LoadConstant => chunk.get_constant_type(self.b()),
LoadList => chunk.get_register_type(self.a()),
GetLocal => chunk.get_local_type(self.b()),
CallNative => {
let native_function = NativeFunction::from(self.b());
native_function.r#type().return_type.map(|boxed| *boxed)
}
_ => None,
}
}
pub fn disassembly_info(&self, chunk: &Chunk) -> String {
let format_arguments = || {
let first_argument = if self.b_is_constant() {
format!("C{}", self.b())
} else {
format!("R{}", self.b())
};
let second_argument = if self.c_is_constant() {
format!("C{}", self.c())
} else {
format!("R{}", self.c())
};
(first_argument, second_argument)
};
match self.operation() {
Operation::Move => format!("R{} = R{}", self.a(), self.b()),
Operation::Close => {
let from_register = self.b();
let to_register = self.c().saturating_sub(1);
format!("R{from_register}..=R{to_register}")
}
Operation::LoadBoolean => {
let to_register = self.a();
let boolean = self.b_as_boolean();
let jump = self.c_as_boolean();
if jump {
format!("R{to_register} = {boolean} && SKIP")
} else {
format!("R{to_register} = {boolean}")
}
}
Operation::LoadConstant => {
let register_index = self.a();
let constant_index = self.b();
let jump = self.c_as_boolean();
if jump {
format!("R{register_index} = C{constant_index} && SKIP")
} else {
format!("R{register_index} = C{constant_index}")
}
}
Operation::LoadList => {
let to_register = self.a();
let first_index = self.b();
let last_index = self.c();
format!("R{to_register} = [R{first_index}..=R{last_index}]",)
}
Operation::LoadSelf => {
let to_register = self.a();
let name = chunk
.name()
.map(|idenifier| idenifier.as_str())
.unwrap_or("self");
format!("R{to_register} = {name}")
}
Operation::DefineLocal => {
let to_register = self.a();
let local_index = self.b();
let identifier_display = match chunk.get_identifier(local_index) {
Some(identifier) => identifier.to_string(),
None => "???".to_string(),
};
let mutable_display = if self.c_as_boolean() { "mut" } else { "" };
format!("R{to_register} = L{local_index} {mutable_display} {identifier_display}")
}
Operation::GetLocal => {
let local_index = self.b();
format!("R{} = L{}", self.a(), local_index)
}
Operation::SetLocal => {
let local_index = self.b();
let identifier_display = match chunk.get_identifier(local_index) {
Some(identifier) => identifier.to_string(),
None => "???".to_string(),
};
format!("L{} = R{} {}", local_index, self.a(), identifier_display)
}
Operation::Add => {
let to_register = self.a();
let (first_argument, second_argument) = format_arguments();
format!("R{to_register} = {first_argument} + {second_argument}",)
}
Operation::Subtract => {
let to_register = self.a();
let (first_argument, second_argument) = format_arguments();
format!("R{to_register} = {first_argument} - {second_argument}",)
}
Operation::Multiply => {
let to_register = self.a();
let (first_argument, second_argument) = format_arguments();
format!("R{to_register} = {first_argument} * {second_argument}",)
}
Operation::Divide => {
let to_register = self.a();
let (first_argument, second_argument) = format_arguments();
format!("R{to_register} = {first_argument} / {second_argument}",)
}
Operation::Modulo => {
let to_register = self.a();
let (first_argument, second_argument) = format_arguments();
format!("R{to_register} = {first_argument} % {second_argument}",)
}
Operation::Test => {
let to_register = self.a();
let test_value = self.c_as_boolean();
format!("if R{to_register} != {test_value} {{ SKIP }}")
}
Operation::TestSet => {
let to_register = self.a();
let argument = format!("R{}", self.b());
let test_value = self.c_as_boolean();
let bang = if test_value { "" } else { "!" };
format!("if {bang}R{to_register} {{ R{to_register} = R{argument} }}",)
}
Operation::Equal => {
let comparison_symbol = if self.a_as_boolean() { "==" } else { "!=" };
let (first_argument, second_argument) = format_arguments();
format!("if {first_argument} {comparison_symbol} {second_argument} {{ SKIP }}")
}
Operation::Less => {
let comparison_symbol = if self.a_as_boolean() { "<" } else { ">=" };
let (first_argument, second_argument) = format_arguments();
format!("if {first_argument} {comparison_symbol} {second_argument} {{ SKIP }}")
}
Operation::LessEqual => {
let comparison_symbol = if self.a_as_boolean() { "<=" } else { ">" };
let (first_argument, second_argument) = format_arguments();
format!("if {first_argument} {comparison_symbol} {second_argument} {{ SKIP }}")
}
Operation::Negate => {
let to_register = self.a();
let argument = if self.b_is_constant() {
format!("C{}", self.b())
} else {
format!("R{}", self.b())
};
format!("R{to_register} = -{argument}")
}
Operation::Not => {
let to_register = self.a();
let argument = if self.b_is_constant() {
format!("C{}", self.b())
} else {
format!("R{}", self.b())
};
format!("R{to_register} = !{argument}")
}
Operation::Jump => {
let jump_distance = self.b();
let is_positive = self.c_as_boolean();
if is_positive {
format!("JUMP +{jump_distance}")
} else {
format!("JUMP -{jump_distance}")
}
}
Operation::Call => {
let to_register = self.a();
let function_register = self.b();
let argument_count = self.c();
let mut output = format!("R{to_register} = R{function_register}(");
if argument_count != 0 {
let first_argument = function_register + 1;
for (index, register) in
(first_argument..first_argument + argument_count).enumerate()
{
if index > 0 {
output.push_str(", ");
}
output.push_str(&format!("R{}", register));
}
}
output.push(')');
output
}
Operation::CallNative => {
let to_register = self.a();
let native_function = NativeFunction::from(self.b());
let argument_count = self.c();
let mut output = String::new();
let native_function_name = native_function.as_str();
if native_function.r#type().return_type.is_some() {
output.push_str(&format!("R{} = {}(", to_register, native_function_name));
} else {
output.push_str(&format!("{}(", native_function_name));
}
if argument_count != 0 {
let first_argument = to_register.saturating_sub(argument_count);
for (index, register) in (first_argument..to_register).enumerate() {
if index > 0 {
output.push_str(", ");
}
output.push_str(&format!("R{}", register));
}
}
output.push(')');
output
}
Operation::Return => {
let should_return_value = self.b_as_boolean();
if should_return_value {
"->".to_string()
} else {
"".to_string()
}
}
}
}
}
impl From<&Instruction> for u32 {
fn from(instruction: &Instruction) -> Self {
instruction.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn r#move() {
let mut instruction = Instruction::r#move(0, 1);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::Move);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
}
#[test]
fn close() {
let instruction = Instruction::close(1, 2);
assert_eq!(instruction.operation(), Operation::Close);
assert_eq!(instruction.b(), 1);
assert_eq!(instruction.c(), 2);
}
#[test]
fn load_boolean() {
let instruction = Instruction::load_boolean(4, true, true);
assert_eq!(instruction.operation(), Operation::LoadBoolean);
assert_eq!(instruction.a(), 4);
assert!(instruction.a_as_boolean());
assert!(instruction.c_as_boolean());
}
#[test]
fn load_constant() {
let mut instruction = Instruction::load_constant(0, 1, true);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::LoadConstant);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
assert!(instruction.c_as_boolean());
}
#[test]
fn load_list() {
let instruction = Instruction::load_list(0, 1);
assert_eq!(instruction.operation(), Operation::LoadList);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
}
#[test]
fn load_self() {
let instruction = Instruction::load_self(10);
assert_eq!(instruction.operation(), Operation::LoadSelf);
assert_eq!(instruction.a(), 10);
}
#[test]
fn declare_local() {
let mut instruction = Instruction::define_local(0, 1, true);
instruction.set_b_is_constant();
assert_eq!(instruction.operation(), Operation::DefineLocal);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert_eq!(instruction.c(), true as u8);
assert!(instruction.b_is_constant());
}
#[test]
fn add() {
let mut instruction = Instruction::add(1, 1, 0);
instruction.set_b_is_constant();
assert_eq!(instruction.operation(), Operation::Add);
assert_eq!(instruction.a(), 1);
assert_eq!(instruction.b(), 1);
assert_eq!(instruction.c(), 0);
assert!(instruction.b_is_constant());
}
#[test]
fn subtract() {
let mut instruction = Instruction::subtract(0, 1, 2);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::Subtract);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert_eq!(instruction.c(), 2);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
}
#[test]
fn multiply() {
let mut instruction = Instruction::multiply(0, 1, 2);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::Multiply);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert_eq!(instruction.c(), 2);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
}
#[test]
fn divide() {
let mut instruction = Instruction::divide(0, 1, 2);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::Divide);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert_eq!(instruction.c(), 2);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
}
#[test]
fn and() {
let instruction = Instruction::test(4, true);
assert_eq!(instruction.operation(), Operation::Test);
assert_eq!(instruction.a(), 4);
assert!(instruction.c_as_boolean());
}
#[test]
fn or() {
let instruction = Instruction::test_set(4, 1, true);
assert_eq!(instruction.operation(), Operation::TestSet);
assert_eq!(instruction.a(), 4);
assert_eq!(instruction.b(), 1);
assert!(instruction.c_as_boolean());
}
#[test]
fn equal() {
let mut instruction = Instruction::equal(true, 1, 2);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::Equal);
assert!(instruction.a_as_boolean());
assert_eq!(instruction.b(), 1);
assert_eq!(instruction.c(), 2);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
}
#[test]
fn negate() {
let mut instruction = Instruction::negate(0, 1);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::Negate);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
}
#[test]
fn not() {
let mut instruction = Instruction::not(0, 1);
instruction.set_b_is_constant();
instruction.set_c_is_constant();
assert_eq!(instruction.operation(), Operation::Not);
assert_eq!(instruction.a(), 0);
assert_eq!(instruction.b(), 1);
assert!(instruction.b_is_constant());
assert!(instruction.b_is_constant());
}
#[test]
fn jump() {
let instruction = Instruction::jump(4, true);
assert_eq!(instruction.operation(), Operation::Jump);
assert_eq!(instruction.b(), 4);
assert!(instruction.c_as_boolean());
}
#[test]
fn call() {
let instruction = Instruction::call(1, 3, 4);
assert_eq!(instruction.operation(), Operation::Call);
assert_eq!(instruction.a(), 1);
assert_eq!(instruction.b(), 3);
assert_eq!(instruction.c(), 4);
}
#[test]
fn r#return() {
let instruction = Instruction::r#return(true);
assert_eq!(instruction.operation(), Operation::Return);
assert!(instruction.b_as_boolean());
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,48 +1,44 @@
//! The Dust programming language. //! The Dust programming language library.
//!
//! To get started, you can use the `run` function to run a Dust program. pub mod chunk;
//! pub mod compiler;
//! ```rust pub mod disassembler;
//! use dust_lang::{run, Value};
//!
//! let program = "
//! let foo = 21
//! let bar = 2
//! foo * bar
//! ";
//!
//! let the_answer = run(program).unwrap();
//!
//! assert_eq!(the_answer, Some(Value::integer(42)));
//! ```
pub mod analyzer;
pub mod ast;
pub mod built_in_function;
pub mod constructor;
pub mod context;
pub mod core_library;
pub mod dust_error; pub mod dust_error;
pub mod evaluation; pub mod formatter;
pub mod identifier; pub mod instruction;
pub mod lexer; pub mod lexer;
pub mod parser; pub mod native_function;
pub mod operation;
pub mod optimizer;
pub mod token; pub mod token;
pub mod r#type; pub mod r#type;
pub mod value; pub mod value;
pub mod vm; pub mod vm;
pub use analyzer::{analyze, AnalysisError, Analyzer}; pub use crate::chunk::{Chunk, ChunkError, Local, Scope};
pub use ast::{AbstractSyntaxTree, AstError, Expression, Node, Span, Statement}; pub use crate::compiler::{compile, CompileError, Compiler};
pub use built_in_function::{BuiltInFunction, BuiltInFunctionError}; pub use crate::disassembler::Disassembler;
pub use constructor::{ConstructError, Constructor}; pub use crate::dust_error::{AnnotatedError, DustError};
pub use context::{Context, ContextData, ContextError}; pub use crate::formatter::{format, Formatter};
pub use core_library::core_library; pub use crate::instruction::Instruction;
pub use dust_error::DustError; pub use crate::lexer::{lex, LexError, Lexer};
pub use evaluation::{Evaluation, TypeEvaluation}; pub use crate::native_function::{NativeFunction, NativeFunctionError};
pub use identifier::Identifier; pub use crate::operation::Operation;
pub use lexer::{lex, LexError, Lexer}; pub use crate::optimizer::{optimize, Optimizer};
pub use parser::{parse, ParseError, Parser}; pub use crate::r#type::{EnumType, FunctionType, RangeableType, StructType, Type, TypeConflict};
pub use r#type::*; pub use crate::token::{Token, TokenKind, TokenOwned};
pub use token::{Token, TokenKind, TokenOwned}; pub use crate::value::{ConcreteValue, Function, Value, ValueError};
pub use value::*; pub use crate::vm::{run, Vm, VmError};
pub use vm::{run, RuntimeError, Vm};
use std::fmt::Display;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Span(pub usize, pub usize);
impl Display for Span {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({}, {})", self.0, self.1)
}
}

View File

@ -0,0 +1,418 @@
//! Built-in functions that implement extended functionality.
//!
//! Native functions are used either to implement features that are not possible to implement in
//! Dust itself or that are more efficient to implement in Rust.
use std::{
fmt::{self, Display, Formatter},
io::{self, stdin, stdout, Write},
string::{self},
};
use serde::{Deserialize, Serialize};
use crate::{
AnnotatedError, ConcreteValue, FunctionType, Instruction, Span, Type, Value, Vm, VmError,
};
macro_rules! define_native_function {
($(($name:ident, $byte:literal, $str:expr, $type:expr)),*) => {
/// A dust-native function.
///
/// See the [module-level documentation](index.html) for more information.
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum NativeFunction {
$(
$name = $byte as isize,
)*
}
impl NativeFunction {
pub fn as_str(&self) -> &'static str {
match self {
$(
NativeFunction::$name => $str,
)*
}
}
#[allow(clippy::should_implement_trait)]
pub fn from_str(string: &str) -> Option<Self> {
match string {
$(
$str => Some(NativeFunction::$name),
)*
_ => None,
}
}
pub fn r#type(&self) -> FunctionType {
match self {
$(
NativeFunction::$name => $type,
)*
}
}
pub fn returns_value(&self) -> bool {
match self {
$(
NativeFunction::$name => $type.return_type.is_some(),
)*
}
}
}
impl From<u8> for NativeFunction {
fn from(byte: u8) -> Self {
match byte {
$(
$byte => NativeFunction::$name,
)*
_ => {
if cfg!(test) {
panic!("Invalid native function byte: {}", byte)
} else {
NativeFunction::Panic
}
}
}
}
}
impl From<NativeFunction> for u8 {
fn from(native_function: NativeFunction) -> Self {
match native_function {
$(
NativeFunction::$name => $byte,
)*
}
}
}
};
}
define_native_function! {
// Assertion
(
Assert,
0_u8,
"assert",
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: None
}
),
// (AssertEqual, 1_u8, "assert_equal", false),
// (AssertNotEqual, 2_u8, "assert_not_equal", false),
(
Panic,
3_u8,
"panic",
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Some(Box::new(Type::Any))
}
),
// // Type conversion
// (Parse, 4_u8, "parse", true),
// (ToByte, 5_u8, "to_byte", true),
// (ToFloat, 6_u8, "to_float", true),
// (ToInteger, 7_u8, "to_integer", true),
(
ToString,
8_u8,
"to_string",
FunctionType {
type_parameters: None,
value_parameters: Some(vec![(0, Type::Any)]),
return_type: Some(Box::new(Type::String { length: None }))
}
),
// // List and string
// (All, 9_u8, "all", true),
// (Any, 10_u8, "any", true),
// (Append, 11_u8, "append", false),
// (Contains, 12_u8, "contains", true),
// (Dedup, 13_u8, "dedup", false),
// (EndsWith, 14_u8, "ends_with", true),
// (Find, 15_u8, "find", true),
// (Get, 16_u8, "get", true),
// (IndexOf, 17_u8, "index_of", true),
// (Length, 18_u8, "length", true),
// (Prepend, 19_u8, "prepend", false),
// (Replace, 20_u8, "replace", false),
// (Set, 21_u8, "set", false),
// (StartsWith, 22_u8, "starts_with", true),
// (Slice, 23_u8, "slice", true),
// (Sort, 24_u8, "sort", false),
// (Split, 25_u8, "split", true),
// // List
// (Flatten, 26_u8, "flatten", false),
// (Join, 27_u8, "join", true),
// (Map, 28_u8, "map", true),
// (Reduce, 29_u8, "reduce", true),
// (Remove, 30_u8, "remove", false),
// (Reverse, 31_u8, "reverse", false),
// (Unzip, 32_u8, "unzip", true),
// (Zip, 33_u8, "zip", true),
// // String
// (Bytes, 34_u8, "bytes", true),
// (CharAt, 35_u8, "char_at", true),
// (CharCodeAt, 36_u8, "char_code_at", true),
// (Chars, 37_u8, "chars", true),
// (Format, 38_u8, "format", true),
// (Repeat, 39_u8, "repeat", true),
// (SplitAt, 40_u8, "split_at", true),
// (SplitLines, 41_u8, "split_lines", true),
// (SplitWhitespace, 42_u8, "split_whitespace", true),
// (ToLowerCase, 43_u8, "to_lower_case", true),
// (ToUpperCase, 44_u8, "to_upper_case", true),
// (Trim, 45_u8, "trim", true),
// (TrimEnd, 46_u8, "trim_end", true),
// (TrimStart, 47_u8, "trim_start", true),
// // I/O
// // Read
// (Read, 48_u8, "read", true),
// (ReadFile, 49_u8, "read_file", true),
(
ReadLine,
50_u8,
"read_line",
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Some(Box::new(Type::String { length: None }))
}
),
// (ReadTo, 51_u8, "read_to", false),
// (ReadUntil, 52_u8, "read_until", true),
// // Write
// (AppendFile, 53_u8, "append_file", false),
// (PrependFile, 54_u8, "prepend_file", false),
(
Write,
55_u8,
"write",
FunctionType {
type_parameters: None,
value_parameters: Some(vec![(0, Type::String { length: None })]),
return_type: None
}
),
// (WriteFile, 56_u8, "write_file", false),
(
WriteLine,
57_u8,
"write_line",
FunctionType {
type_parameters: None,
value_parameters: Some(vec![(0, Type::String { length: None })]),
return_type: None
}
)
// // Random
// (Random, 58_u8, "random", true),
// (RandomInRange, 59_u8, "random_in_range", true)
}
impl NativeFunction {
pub fn call(
&self,
instruction: Instruction,
vm: &Vm,
position: Span,
) -> Result<Option<Value>, VmError> {
let to_register = instruction.a();
let argument_count = instruction.c();
let return_value = match self {
NativeFunction::Panic => {
let message = if argument_count == 0 {
None
} else {
let mut message = String::new();
for argument_index in 0..argument_count {
if argument_index != 0 {
message.push(' ');
}
let argument = vm.open_register(argument_index, position)?;
message.push_str(&argument.to_string());
}
Some(message)
};
return Err(VmError::NativeFunction(NativeFunctionError::Panic {
message,
position,
}));
}
// Type conversion
NativeFunction::ToString => {
let mut string = String::new();
for argument_index in 0..argument_count {
let argument = vm.open_register(argument_index, position)?;
string.push_str(&argument.to_string());
}
Some(Value::Concrete(ConcreteValue::String(string)))
}
// I/O
NativeFunction::ReadLine => {
let mut buffer = String::new();
stdin().read_line(&mut buffer).map_err(|io_error| {
VmError::NativeFunction(NativeFunctionError::Io {
error: io_error.kind(),
position,
})
})?;
buffer = buffer.trim_end_matches('\n').to_string();
Some(Value::Concrete(ConcreteValue::String(buffer)))
}
NativeFunction::Write => {
let to_register = instruction.a();
let mut stdout = stdout();
let map_err = |io_error: io::Error| {
VmError::NativeFunction(NativeFunctionError::Io {
error: io_error.kind(),
position,
})
};
let first_argument = to_register.saturating_sub(argument_count);
let last_argument = to_register.saturating_sub(1);
for argument_index in first_argument..=last_argument {
if argument_index != first_argument {
stdout.write(b" ").map_err(map_err)?;
}
let argument_string = vm.open_register(argument_index, position)?.to_string();
stdout
.write_all(argument_string.as_bytes())
.map_err(map_err)?;
}
None
}
NativeFunction::WriteLine => {
let mut stdout = stdout();
let map_err = |io_error: io::Error| {
VmError::NativeFunction(NativeFunctionError::Io {
error: io_error.kind(),
position,
})
};
let first_index = to_register.saturating_sub(argument_count);
let arguments = vm.open_nonempty_registers(first_index..to_register, position)?;
for (index, argument) in arguments.into_iter().enumerate() {
if index != 0 {
stdout.write(b" ").map_err(map_err)?;
}
if let Value::Concrete(ConcreteValue::String(string)) = argument {
let bytes = string.as_bytes();
stdout.write_all(bytes).map_err(map_err)?;
} else {
let bytes = argument.to_string().into_bytes();
stdout.write_all(&bytes).map_err(map_err)?;
}
}
stdout.write(b"\n").map_err(map_err)?;
None
}
_ => todo!(),
};
Ok(return_value)
}
}
impl Display for NativeFunction {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum NativeFunctionError {
ExpectedArgumentCount {
expected: usize,
found: usize,
position: Span,
},
Panic {
message: Option<String>,
position: Span,
},
Parse {
error: string::ParseError,
position: Span,
},
Io {
error: io::ErrorKind,
position: Span,
},
}
impl AnnotatedError for NativeFunctionError {
fn title() -> &'static str {
"Native Function Error"
}
fn description(&self) -> &'static str {
match self {
NativeFunctionError::ExpectedArgumentCount { .. } => {
"Expected a different number of arguments"
}
NativeFunctionError::Panic { .. } => "Explicit panic",
NativeFunctionError::Parse { .. } => "Failed to parse value",
NativeFunctionError::Io { .. } => "I/O error",
}
}
fn details(&self) -> Option<String> {
match self {
NativeFunctionError::ExpectedArgumentCount {
expected, found, ..
} => Some(format!("Expected {} arguments, found {}", expected, found)),
NativeFunctionError::Panic { message, .. } => message.clone(),
NativeFunctionError::Parse { error, .. } => Some(format!("{}", error)),
NativeFunctionError::Io { error, .. } => Some(format!("{}", error)),
}
}
fn position(&self) -> Span {
match self {
NativeFunctionError::ExpectedArgumentCount { position, .. } => *position,
NativeFunctionError::Panic { position, .. } => *position,
NativeFunctionError::Parse { position, .. } => *position,
NativeFunctionError::Io { position, .. } => *position,
}
}
}

110
dust-lang/src/operation.rs Normal file
View File

@ -0,0 +1,110 @@
//! Part of an [Instruction][crate::Instruction], which can be executed by the Dust virtual machine.
//!
//! !!! Warning !!!
//! The byte values of the operations matter. The seventh and eighth bits must be zero so that the
//! [Instruction][crate::Instruction] type can use them as flags.
use std::fmt::{self, Display, Formatter};
macro_rules! define_operation {
($(($name:ident, $byte:literal, $str:expr, $type:expr)),*) => {
/// Part of an [Instruction][crate::Instruction], which can be executed by the Dust virtual machine.)
///
/// See the [module-level documentation](index.html) for more information.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Operation {
$(
$name = $byte as isize,
)*
}
impl From<u8> for Operation {
fn from(byte: u8) -> Self {
match byte {
$(
$byte => Operation::$name,
)*
_ => {
if cfg!(test) {
panic!("Invalid operation byte: {}", byte)
} else {
Operation::Return
}
}
}
}
}
impl From<Operation> for u8 {
fn from(operation: Operation) -> Self {
match operation {
$(
Operation::$name => $byte,
)*
}
}
}
impl Display for Operation {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
$(
Operation::$name => write!(f, "{}", $str),
)*
}
}
}
}
}
define_operation! {
(Move, 0b0000_0000, "MOVE", None),
(Close, 0b000_0001, "CLOSE", None),
(LoadBoolean, 0b0000_0010, "LOAD_BOOLEAN", None),
(LoadConstant, 0b0000_0011, "LOAD_CONSTANT", None),
(LoadList, 0b0000_0100, "LOAD_LIST", None),
(LoadSelf, 0b0000_0101, "LOAD_SELF", None),
(DefineLocal, 0b0000_0110, "DEFINE_LOCAL", None),
(GetLocal, 0b0000_0111, "GET_LOCAL", None),
(SetLocal, 0b0000_1000, "SET_LOCAL", None),
(Add, 0b0000_1001, "ADD", None),
(Subtract, 0b0000_1010, "SUBTRACT", None),
(Multiply, 0b0000_1011, "MULTIPLY", None),
(Divide, 0b0000_1100, "DIVIDE", None),
(Modulo, 0b0000_1101, "MODULO", None),
(Test, 0b0000_1110, "TEST", None),
(TestSet, 0b0000_1111, "TEST_SET", None),
(Equal, 0b0001_0000, "EQUAL", None),
(Less, 0b0001_0001, "LESS", None),
(LessEqual, 0b0001_0010, "LESS_EQUAL", None),
(Negate, 0b0001_0011, "NEGATE", None),
(Not, 0b0001_0100, "NOT", None),
(Jump, 0b0001_0101, "JUMP", None),
(Call, 0b0001_0110, "CALL", None),
(CallNative, 0b0001_0111, "CALL_NATIVE", None),
(Return, 0b0001_1000, "RETURN", None)
}
impl Operation {
pub fn is_math(&self) -> bool {
matches!(
self,
Operation::Add
| Operation::Subtract
| Operation::Multiply
| Operation::Divide
| Operation::Modulo
)
}
pub fn is_comparison(&self) -> bool {
matches!(
self,
Operation::Equal | Operation::Less | Operation::LessEqual
)
}
pub fn is_test(&self) -> bool {
matches!(self, Operation::Test | Operation::TestSet)
}
}

View File

@ -0,0 +1,96 @@
//! Tools used by the compiler to optimize a chunk's bytecode.
use std::{iter::Map, slice::Iter};
use crate::{Instruction, Operation, Span};
type MapToOperation = fn(&(Instruction, Span)) -> Operation;
type OperationIter<'iter> = Map<Iter<'iter, (Instruction, Span)>, MapToOperation>;
/// Performs optimizations on a subset of instructions.
pub fn optimize(instructions: &mut [(Instruction, Span)]) -> usize {
Optimizer::new(instructions).optimize()
}
/// An instruction optimizer that mutably borrows instructions from a chunk.
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct Optimizer<'chunk> {
instructions: &'chunk mut [(Instruction, Span)],
}
impl<'chunk> Optimizer<'chunk> {
/// Creates a new optimizer with a mutable reference to some of a chunk's instructions.
pub fn new(instructions: &'chunk mut [(Instruction, Span)]) -> Self {
Self { instructions }
}
/// Potentially mutates the instructions to optimize them.
pub fn optimize(&mut self) -> usize {
let mut optimizations = 0;
if matches!(
self.get_operations(),
Some([
Operation::Equal | Operation::Less | Operation::LessEqual,
Operation::Jump,
Operation::LoadBoolean | Operation::LoadConstant,
Operation::LoadBoolean | Operation::LoadConstant,
])
) {
self.optimize_comparison();
optimizations += 1;
}
optimizations
}
/// Optimizes a comparison operation.
///
/// The instructions must be in the following order:
/// - `Operation::Equal | Operation::Less | Operation::LessEqual`
/// - `Operation::Jump`
/// - `Operation::LoadBoolean | Operation::LoadConstant`
/// - `Operation::LoadBoolean | Operation::LoadConstant`
fn optimize_comparison(&mut self) {
log::debug!("Optimizing comparison");
let first_loader_register = {
let first_loader = &mut self.instructions[2].0;
first_loader.set_c_to_boolean(true);
first_loader.a()
};
let second_loader = &mut self.instructions[3].0;
let mut second_loader_new = Instruction::with_operation(second_loader.operation());
second_loader_new.set_a(first_loader_register);
second_loader_new.set_b(second_loader.b());
second_loader_new.set_c(second_loader.c());
second_loader_new.set_b_to_boolean(second_loader.b_is_constant());
second_loader_new.set_c_to_boolean(second_loader.c_is_constant());
*second_loader = second_loader_new;
}
fn operations_iter(&self) -> OperationIter {
self.instructions
.iter()
.map(|(instruction, _)| instruction.operation())
}
fn get_operations<const COUNT: usize>(&self) -> Option<[Operation; COUNT]> {
if self.instructions.len() < COUNT {
return None;
}
let mut n_operations = [Operation::Return; COUNT];
for (nth, operation) in n_operations.iter_mut().zip(self.operations_iter()) {
*nth = operation;
}
Some(n_operations)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,39 @@
//! Token and TokenOwned types. //! Token, TokenOwned and TokenKind types.
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Source code token. macro_rules! define_tokens {
#[derive(Debug, Serialize, Deserialize, PartialEq)] ($($variant:ident $(($data_type:ty))?),+ $(,)?) => {
pub enum Token<'src> { /// Source token.
// End of file ///
Eof, /// This is a borrowed type, i.e. some variants contain references to the source text.
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Default, Serialize, Deserialize)]
pub enum Token<'src> {
#[default]
Eof,
$(
$variant $(($data_type))?,
)*
}
#[derive(Debug, PartialEq, Clone)]
/// Data-less representation of a source token.
///
/// If a [Token] borrows from the source text, its TokenKind omits the data.
pub enum TokenKind {
Eof,
$(
$variant,
)*
}
};
}
define_tokens! {
// Hard-coded values // Hard-coded values
Boolean(&'src str), Boolean(&'src str),
Byte(&'src str),
Character(char), Character(char),
Float(&'src str), Float(&'src str),
Identifier(&'src str), Identifier(&'src str),
@ -23,17 +46,20 @@ pub enum Token<'src> {
Break, Break,
Else, Else,
FloatKeyword, FloatKeyword,
Fn,
If, If,
Int, Int,
Let, Let,
Loop, Loop,
Map, Map,
Mut, Mut,
Return,
Str, Str,
Struct, Struct,
While, While,
// Symbols // Symbols
ArrowThin,
BangEqual, BangEqual,
Bang, Bang,
Colon, Colon,
@ -54,6 +80,7 @@ pub enum Token<'src> {
Minus, Minus,
MinusEqual, MinusEqual,
Percent, Percent,
PercentEqual,
Plus, Plus,
PlusEqual, PlusEqual,
RightCurlyBrace, RightCurlyBrace,
@ -61,7 +88,9 @@ pub enum Token<'src> {
RightSquareBrace, RightSquareBrace,
Semicolon, Semicolon,
Slash, Slash,
SlashEqual,
Star, Star,
StarEqual,
} }
impl<'src> Token<'src> { impl<'src> Token<'src> {
@ -70,16 +99,19 @@ impl<'src> Token<'src> {
match self { match self {
Token::Eof => 0, Token::Eof => 0,
Token::Boolean(text) => text.len(), Token::Boolean(text) => text.len(),
Token::Byte(_) => 3,
Token::Character(_) => 3, Token::Character(_) => 3,
Token::Float(text) => text.len(), Token::Float(text) => text.len(),
Token::Identifier(text) => text.len(), Token::Identifier(text) => text.len(),
Token::Integer(text) => text.len(), Token::Integer(text) => text.len(),
Token::String(text) => text.len() + 2, Token::String(text) => text.len() + 2,
Token::Async => 5, Token::Async => 5,
Token::ArrowThin => 2,
Token::Bool => 4, Token::Bool => 4,
Token::Break => 5, Token::Break => 5,
Token::Else => 4, Token::Else => 4,
Token::FloatKeyword => 5, Token::FloatKeyword => 5,
Token::Fn => 2,
Token::If => 2, Token::If => 2,
Token::Int => 3, Token::Int => 3,
Token::Let => 3, Token::Let => 3,
@ -109,25 +141,92 @@ impl<'src> Token<'src> {
Token::Minus => 1, Token::Minus => 1,
Token::MinusEqual => 2, Token::MinusEqual => 2,
Token::Percent => 1, Token::Percent => 1,
Token::PercentEqual => 2,
Token::Plus => 1, Token::Plus => 1,
Token::PlusEqual => 2, Token::PlusEqual => 2,
Token::Return => 6,
Token::RightCurlyBrace => 1, Token::RightCurlyBrace => 1,
Token::RightParenthesis => 1, Token::RightParenthesis => 1,
Token::RightSquareBrace => 1, Token::RightSquareBrace => 1,
Token::Semicolon => 1, Token::Semicolon => 1,
Token::Slash => 1, Token::Slash => 1,
Token::SlashEqual => 2,
Token::Star => 1, Token::Star => 1,
Token::StarEqual => 2,
}
}
pub fn as_str(&self) -> &str {
match self {
Token::Eof => "",
Token::Boolean(text) => text,
Token::Byte(text) => text,
Token::Character(_) => "character token",
Token::Float(text) => text,
Token::Identifier(text) => text,
Token::Integer(text) => text,
Token::String(text) => text,
Token::Async => "async",
Token::ArrowThin => "->",
Token::Bool => "bool",
Token::Break => "break",
Token::Else => "else",
Token::FloatKeyword => "float",
Token::Fn => "fn",
Token::If => "if",
Token::Int => "int",
Token::Let => "let",
Token::Loop => "loop",
Token::Map => "map",
Token::Mut => "mut",
Token::Str => "str",
Token::Struct => "struct",
Token::While => "while",
Token::BangEqual => "!=",
Token::Bang => "!",
Token::Colon => ":",
Token::Comma => ",",
Token::Dot => ".",
Token::DoubleAmpersand => "&&",
Token::DoubleDot => "..",
Token::DoubleEqual => "==",
Token::DoublePipe => "||",
Token::Equal => "=",
Token::Greater => ">",
Token::GreaterEqual => ">=",
Token::LeftCurlyBrace => "{",
Token::LeftParenthesis => "(",
Token::LeftSquareBrace => "[",
Token::Less => "<",
Token::LessEqual => "<=",
Token::Minus => "-",
Token::MinusEqual => "-=",
Token::Percent => "%",
Token::PercentEqual => "%=",
Token::Plus => "+",
Token::PlusEqual => "+=",
Token::Return => "return",
Token::RightCurlyBrace => "}",
Token::RightParenthesis => ")",
Token::RightSquareBrace => "]",
Token::Semicolon => ";",
Token::Slash => "/",
Token::SlashEqual => "/=",
Token::Star => "*",
Token::StarEqual => "*=",
} }
} }
pub fn to_owned(&self) -> TokenOwned { pub fn to_owned(&self) -> TokenOwned {
match self { match self {
Token::ArrowThin => TokenOwned::ArrowThin,
Token::Async => TokenOwned::Async, Token::Async => TokenOwned::Async,
Token::BangEqual => TokenOwned::BangEqual, Token::BangEqual => TokenOwned::BangEqual,
Token::Bang => TokenOwned::Bang, Token::Bang => TokenOwned::Bang,
Token::Bool => TokenOwned::Bool, Token::Bool => TokenOwned::Bool,
Token::Boolean(boolean) => TokenOwned::Boolean(boolean.to_string()), Token::Boolean(boolean) => TokenOwned::Boolean(boolean.to_string()),
Token::Break => TokenOwned::Break, Token::Break => TokenOwned::Break,
Token::Byte(byte) => TokenOwned::Byte(byte.to_string()),
Token::Character(character) => TokenOwned::Character(*character), Token::Character(character) => TokenOwned::Character(*character),
Token::Colon => TokenOwned::Colon, Token::Colon => TokenOwned::Colon,
Token::Comma => TokenOwned::Comma, Token::Comma => TokenOwned::Comma,
@ -141,6 +240,7 @@ impl<'src> Token<'src> {
Token::Equal => TokenOwned::Equal, Token::Equal => TokenOwned::Equal,
Token::Float(float) => TokenOwned::Float(float.to_string()), Token::Float(float) => TokenOwned::Float(float.to_string()),
Token::FloatKeyword => TokenOwned::FloatKeyword, Token::FloatKeyword => TokenOwned::FloatKeyword,
Token::Fn => TokenOwned::Fn,
Token::Greater => TokenOwned::Greater, Token::Greater => TokenOwned::Greater,
Token::GreaterEqual => TokenOwned::GreaterOrEqual, Token::GreaterEqual => TokenOwned::GreaterOrEqual,
Token::Identifier(text) => TokenOwned::Identifier(text.to_string()), Token::Identifier(text) => TokenOwned::Identifier(text.to_string()),
@ -159,14 +259,18 @@ impl<'src> Token<'src> {
Token::MinusEqual => TokenOwned::MinusEqual, Token::MinusEqual => TokenOwned::MinusEqual,
Token::Mut => TokenOwned::Mut, Token::Mut => TokenOwned::Mut,
Token::Percent => TokenOwned::Percent, Token::Percent => TokenOwned::Percent,
Token::PercentEqual => TokenOwned::PercentEqual,
Token::Plus => TokenOwned::Plus, Token::Plus => TokenOwned::Plus,
Token::PlusEqual => TokenOwned::PlusEqual, Token::PlusEqual => TokenOwned::PlusEqual,
Token::Return => TokenOwned::Return,
Token::RightCurlyBrace => TokenOwned::RightCurlyBrace, Token::RightCurlyBrace => TokenOwned::RightCurlyBrace,
Token::RightParenthesis => TokenOwned::RightParenthesis, Token::RightParenthesis => TokenOwned::RightParenthesis,
Token::RightSquareBrace => TokenOwned::RightSquareBrace, Token::RightSquareBrace => TokenOwned::RightSquareBrace,
Token::Semicolon => TokenOwned::Semicolon, Token::Semicolon => TokenOwned::Semicolon,
Token::Star => TokenOwned::Star, Token::Star => TokenOwned::Star,
Token::StarEqual => TokenOwned::StarEqual,
Token::Slash => TokenOwned::Slash, Token::Slash => TokenOwned::Slash,
Token::SlashEqual => TokenOwned::SlashEqual,
Token::String(text) => TokenOwned::String(text.to_string()), Token::String(text) => TokenOwned::String(text.to_string()),
Token::Str => TokenOwned::Str, Token::Str => TokenOwned::Str,
Token::Struct => TokenOwned::Struct, Token::Struct => TokenOwned::Struct,
@ -176,12 +280,14 @@ impl<'src> Token<'src> {
pub fn kind(&self) -> TokenKind { pub fn kind(&self) -> TokenKind {
match self { match self {
Token::ArrowThin => TokenKind::ArrowThin,
Token::Async => TokenKind::Async, Token::Async => TokenKind::Async,
Token::BangEqual => TokenKind::BangEqual, Token::BangEqual => TokenKind::BangEqual,
Token::Bang => TokenKind::Bang, Token::Bang => TokenKind::Bang,
Token::Bool => TokenKind::Bool, Token::Bool => TokenKind::Bool,
Token::Boolean(_) => TokenKind::Boolean, Token::Boolean(_) => TokenKind::Boolean,
Token::Break => TokenKind::Break, Token::Break => TokenKind::Break,
Token::Byte(_) => TokenKind::Byte,
Token::Character(_) => TokenKind::Character, Token::Character(_) => TokenKind::Character,
Token::Colon => TokenKind::Colon, Token::Colon => TokenKind::Colon,
Token::Comma => TokenKind::Comma, Token::Comma => TokenKind::Comma,
@ -195,8 +301,9 @@ impl<'src> Token<'src> {
Token::Equal => TokenKind::Equal, Token::Equal => TokenKind::Equal,
Token::Float(_) => TokenKind::Float, Token::Float(_) => TokenKind::Float,
Token::FloatKeyword => TokenKind::FloatKeyword, Token::FloatKeyword => TokenKind::FloatKeyword,
Token::Fn => TokenKind::Fn,
Token::Greater => TokenKind::Greater, Token::Greater => TokenKind::Greater,
Token::GreaterEqual => TokenKind::GreaterOrEqual, Token::GreaterEqual => TokenKind::GreaterEqual,
Token::Identifier(_) => TokenKind::Identifier, Token::Identifier(_) => TokenKind::Identifier,
Token::If => TokenKind::If, Token::If => TokenKind::If,
Token::Int => TokenKind::Int, Token::Int => TokenKind::Int,
@ -206,21 +313,25 @@ impl<'src> Token<'src> {
Token::LeftSquareBrace => TokenKind::LeftSquareBrace, Token::LeftSquareBrace => TokenKind::LeftSquareBrace,
Token::Let => TokenKind::Let, Token::Let => TokenKind::Let,
Token::Less => TokenKind::Less, Token::Less => TokenKind::Less,
Token::LessEqual => TokenKind::LessOrEqual, Token::LessEqual => TokenKind::LessEqual,
Token::Loop => TokenKind::Loop, Token::Loop => TokenKind::Loop,
Token::Map => TokenKind::Map, Token::Map => TokenKind::Map,
Token::Minus => TokenKind::Minus, Token::Minus => TokenKind::Minus,
Token::MinusEqual => TokenKind::MinusEqual, Token::MinusEqual => TokenKind::MinusEqual,
Token::Mut => TokenKind::Mut, Token::Mut => TokenKind::Mut,
Token::Percent => TokenKind::Percent, Token::Percent => TokenKind::Percent,
Token::PercentEqual => TokenKind::PercentEqual,
Token::Plus => TokenKind::Plus, Token::Plus => TokenKind::Plus,
Token::PlusEqual => TokenKind::PlusEqual, Token::PlusEqual => TokenKind::PlusEqual,
Token::Return => TokenKind::Return,
Token::RightCurlyBrace => TokenKind::RightCurlyBrace, Token::RightCurlyBrace => TokenKind::RightCurlyBrace,
Token::RightParenthesis => TokenKind::RightParenthesis, Token::RightParenthesis => TokenKind::RightParenthesis,
Token::RightSquareBrace => TokenKind::RightSquareBrace, Token::RightSquareBrace => TokenKind::RightSquareBrace,
Token::Semicolon => TokenKind::Semicolon, Token::Semicolon => TokenKind::Semicolon,
Token::Star => TokenKind::Star, Token::Star => TokenKind::Star,
Token::StarEqual => TokenKind::StarEqual,
Token::Slash => TokenKind::Slash, Token::Slash => TokenKind::Slash,
Token::SlashEqual => TokenKind::SlashEqual,
Token::Str => TokenKind::Str, Token::Str => TokenKind::Str,
Token::String(_) => TokenKind::String, Token::String(_) => TokenKind::String,
Token::Struct => TokenKind::Struct, Token::Struct => TokenKind::Struct,
@ -228,55 +339,45 @@ impl<'src> Token<'src> {
} }
} }
pub fn is_eof(&self) -> bool { /// Returns true if the token yields a value, begins an expression or is an expression operator.
matches!(self, Token::Eof) pub fn is_expression(&self) -> bool {
}
pub fn precedence(&self) -> u8 {
match self {
Token::Dot => 9,
Token::LeftParenthesis | Token::LeftSquareBrace => 8,
Token::Star | Token::Slash | Token::Percent => 7,
Token::Minus | Token::Plus => 6,
Token::DoubleEqual
| Token::Less
| Token::LessEqual
| Token::Greater
| Token::GreaterEqual => 5,
Token::DoubleAmpersand => 4,
Token::DoublePipe => 3,
Token::DoubleDot => 2,
Token::Equal | Token::MinusEqual | Token::PlusEqual => 1,
_ => 0,
}
}
pub fn is_left_associative(&self) -> bool {
matches!( matches!(
self, self,
Token::Dot Token::Boolean(_)
| Token::Byte(_)
| Token::Character(_)
| Token::Float(_)
| Token::Identifier(_)
| Token::Integer(_)
| Token::String(_)
| Token::Break
| Token::If
| Token::Return
| Token::Map
| Token::Loop
| Token::Struct
| Token::BangEqual
| Token::DoubleAmpersand | Token::DoubleAmpersand
| Token::DoubleEqual
| Token::DoublePipe | Token::DoublePipe
| Token::Plus | Token::Equal
| Token::Greater
| Token::GreaterEqual
| Token::LeftCurlyBrace
| Token::LeftParenthesis
| Token::LeftSquareBrace
| Token::Less
| Token::LessEqual
| Token::Minus | Token::Minus
| Token::Star | Token::MinusEqual
| Token::Slash
| Token::Percent | Token::Percent
) | Token::PercentEqual
} | Token::Plus
| Token::PlusEqual
pub fn is_right_associative(&self) -> bool { | Token::Slash
matches!(self, Token::Equal | Token::MinusEqual | Token::PlusEqual) | Token::SlashEqual
} | Token::Star
| Token::StarEqual
pub fn is_prefix(&self) -> bool {
matches!(self, Token::Bang | Token::Minus | Token::Star)
}
pub fn is_postfix(&self) -> bool {
matches!(
self,
Token::Dot | Token::LeftCurlyBrace | Token::LeftParenthesis | Token::LeftSquareBrace
) )
} }
} }
@ -284,13 +385,15 @@ impl<'src> Token<'src> {
impl<'src> Display for Token<'src> { impl<'src> Display for Token<'src> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {
Token::ArrowThin => write!(f, "->"),
Token::Async => write!(f, "async"), Token::Async => write!(f, "async"),
Token::BangEqual => write!(f, "!="), Token::BangEqual => write!(f, "!="),
Token::Bang => write!(f, "!"), Token::Bang => write!(f, "!"),
Token::Bool => write!(f, "bool"), Token::Bool => write!(f, "bool"),
Token::Boolean(value) => write!(f, "{}", value), Token::Boolean(value) => write!(f, "{value}"),
Token::Break => write!(f, "break"), Token::Break => write!(f, "break"),
Token::Character(value) => write!(f, "'{}'", value), Token::Byte(value) => write!(f, "{value}"),
Token::Character(value) => write!(f, "{value}"),
Token::Colon => write!(f, ":"), Token::Colon => write!(f, ":"),
Token::Comma => write!(f, ","), Token::Comma => write!(f, ","),
Token::Dot => write!(f, "."), Token::Dot => write!(f, "."),
@ -301,14 +404,15 @@ impl<'src> Display for Token<'src> {
Token::Else => write!(f, "else"), Token::Else => write!(f, "else"),
Token::Eof => write!(f, "EOF"), Token::Eof => write!(f, "EOF"),
Token::Equal => write!(f, "="), Token::Equal => write!(f, "="),
Token::Float(value) => write!(f, "{}", value), Token::Float(value) => write!(f, "{value}"),
Token::FloatKeyword => write!(f, "float"), Token::FloatKeyword => write!(f, "float"),
Token::Fn => write!(f, "fn"),
Token::Greater => write!(f, ">"), Token::Greater => write!(f, ">"),
Token::GreaterEqual => write!(f, ">="), Token::GreaterEqual => write!(f, ">="),
Token::Identifier(value) => write!(f, "{}", value), Token::Identifier(value) => write!(f, "{value}"),
Token::If => write!(f, "if"), Token::If => write!(f, "if"),
Token::Int => write!(f, "int"), Token::Int => write!(f, "int"),
Token::Integer(value) => write!(f, "{}", value), Token::Integer(value) => write!(f, "{value}"),
Token::LeftCurlyBrace => write!(f, "{{"), Token::LeftCurlyBrace => write!(f, "{{"),
Token::LeftParenthesis => write!(f, "("), Token::LeftParenthesis => write!(f, "("),
Token::LeftSquareBrace => write!(f, "["), Token::LeftSquareBrace => write!(f, "["),
@ -321,26 +425,30 @@ impl<'src> Display for Token<'src> {
Token::MinusEqual => write!(f, "-="), Token::MinusEqual => write!(f, "-="),
Token::Mut => write!(f, "mut"), Token::Mut => write!(f, "mut"),
Token::Percent => write!(f, "%"), Token::Percent => write!(f, "%"),
Token::PercentEqual => write!(f, "%="),
Token::Plus => write!(f, "+"), Token::Plus => write!(f, "+"),
Token::PlusEqual => write!(f, "+="), Token::PlusEqual => write!(f, "+="),
Token::Return => write!(f, "return"),
Token::RightCurlyBrace => write!(f, "}}"), Token::RightCurlyBrace => write!(f, "}}"),
Token::RightParenthesis => write!(f, ")"), Token::RightParenthesis => write!(f, ")"),
Token::RightSquareBrace => write!(f, "]"), Token::RightSquareBrace => write!(f, "]"),
Token::Semicolon => write!(f, ";"), Token::Semicolon => write!(f, ";"),
Token::Slash => write!(f, "/"), Token::Slash => write!(f, "/"),
Token::SlashEqual => write!(f, "/="),
Token::Star => write!(f, "*"), Token::Star => write!(f, "*"),
Token::StarEqual => write!(f, "*="),
Token::Str => write!(f, "str"), Token::Str => write!(f, "str"),
Token::String(value) => write!(f, "\"{}\"", value), Token::String(value) => write!(f, "{value}"),
Token::Struct => write!(f, "struct"), Token::Struct => write!(f, "struct"),
Token::While => write!(f, "while"), Token::While => write!(f, "while"),
} }
} }
} }
/// Owned version of `Token`, which owns all the strings. /// Owned representation of a source token.
/// ///
/// This is used for errors. /// If a [Token] borrows from the source text, its TokenOwned omits the data.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[derive(Debug, PartialEq, Clone)]
pub enum TokenOwned { pub enum TokenOwned {
Eof, Eof,
@ -348,27 +456,31 @@ pub enum TokenOwned {
// Hard-coded values // Hard-coded values
Boolean(String), Boolean(String),
Byte(String),
Character(char), Character(char),
Float(String), Float(String),
Integer(String), Integer(String),
String(String), String(String),
// Keywords // Keywords
Async,
Bool, Bool,
Break, Break,
Else, Else,
FloatKeyword, FloatKeyword,
Fn,
If, If,
Int, Int,
Let, Let,
Loop, Loop,
Map, Map,
Mut, Mut,
Return,
Str, Str,
While, While,
// Symbols // Symbols
Async, ArrowThin,
Bang, Bang,
BangEqual, BangEqual,
Colon, Colon,
@ -389,6 +501,7 @@ pub enum TokenOwned {
Minus, Minus,
MinusEqual, MinusEqual,
Percent, Percent,
PercentEqual,
Plus, Plus,
PlusEqual, PlusEqual,
RightCurlyBrace, RightCurlyBrace,
@ -396,19 +509,23 @@ pub enum TokenOwned {
RightSquareBrace, RightSquareBrace,
Semicolon, Semicolon,
Star, Star,
StarEqual,
Struct, Struct,
Slash, Slash,
SlashEqual,
} }
impl Display for TokenOwned { impl Display for TokenOwned {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self { match self {
TokenOwned::ArrowThin => Token::ArrowThin.fmt(f),
TokenOwned::Async => Token::Async.fmt(f), TokenOwned::Async => Token::Async.fmt(f),
TokenOwned::Bang => Token::Bang.fmt(f), TokenOwned::Bang => Token::Bang.fmt(f),
TokenOwned::BangEqual => Token::BangEqual.fmt(f), TokenOwned::BangEqual => Token::BangEqual.fmt(f),
TokenOwned::Bool => Token::Bool.fmt(f), TokenOwned::Bool => Token::Bool.fmt(f),
TokenOwned::Boolean(boolean) => Token::Boolean(boolean).fmt(f), TokenOwned::Boolean(boolean) => Token::Boolean(boolean).fmt(f),
TokenOwned::Break => Token::Break.fmt(f), TokenOwned::Break => Token::Break.fmt(f),
TokenOwned::Byte(byte) => Token::Byte(byte).fmt(f),
TokenOwned::Character(character) => Token::Character(*character).fmt(f), TokenOwned::Character(character) => Token::Character(*character).fmt(f),
TokenOwned::Colon => Token::Colon.fmt(f), TokenOwned::Colon => Token::Colon.fmt(f),
TokenOwned::Comma => Token::Comma.fmt(f), TokenOwned::Comma => Token::Comma.fmt(f),
@ -422,6 +539,7 @@ impl Display for TokenOwned {
TokenOwned::Equal => Token::Equal.fmt(f), TokenOwned::Equal => Token::Equal.fmt(f),
TokenOwned::Float(float) => Token::Float(float).fmt(f), TokenOwned::Float(float) => Token::Float(float).fmt(f),
TokenOwned::FloatKeyword => Token::FloatKeyword.fmt(f), TokenOwned::FloatKeyword => Token::FloatKeyword.fmt(f),
TokenOwned::Fn => Token::Fn.fmt(f),
TokenOwned::Greater => Token::Greater.fmt(f), TokenOwned::Greater => Token::Greater.fmt(f),
TokenOwned::GreaterOrEqual => Token::GreaterEqual.fmt(f), TokenOwned::GreaterOrEqual => Token::GreaterEqual.fmt(f),
TokenOwned::Identifier(text) => Token::Identifier(text).fmt(f), TokenOwned::Identifier(text) => Token::Identifier(text).fmt(f),
@ -440,14 +558,18 @@ impl Display for TokenOwned {
TokenOwned::MinusEqual => Token::MinusEqual.fmt(f), TokenOwned::MinusEqual => Token::MinusEqual.fmt(f),
TokenOwned::Mut => Token::Mut.fmt(f), TokenOwned::Mut => Token::Mut.fmt(f),
TokenOwned::Percent => Token::Percent.fmt(f), TokenOwned::Percent => Token::Percent.fmt(f),
TokenOwned::PercentEqual => Token::PercentEqual.fmt(f),
TokenOwned::Plus => Token::Plus.fmt(f), TokenOwned::Plus => Token::Plus.fmt(f),
TokenOwned::PlusEqual => Token::PlusEqual.fmt(f), TokenOwned::PlusEqual => Token::PlusEqual.fmt(f),
TokenOwned::Return => Token::Return.fmt(f),
TokenOwned::RightCurlyBrace => Token::RightCurlyBrace.fmt(f), TokenOwned::RightCurlyBrace => Token::RightCurlyBrace.fmt(f),
TokenOwned::RightParenthesis => Token::RightParenthesis.fmt(f), TokenOwned::RightParenthesis => Token::RightParenthesis.fmt(f),
TokenOwned::RightSquareBrace => Token::RightSquareBrace.fmt(f), TokenOwned::RightSquareBrace => Token::RightSquareBrace.fmt(f),
TokenOwned::Semicolon => Token::Semicolon.fmt(f), TokenOwned::Semicolon => Token::Semicolon.fmt(f),
TokenOwned::Star => Token::Star.fmt(f), TokenOwned::Star => Token::Star.fmt(f),
TokenOwned::StarEqual => Token::StarEqual.fmt(f),
TokenOwned::Slash => Token::Slash.fmt(f), TokenOwned::Slash => Token::Slash.fmt(f),
TokenOwned::SlashEqual => Token::SlashEqual.fmt(f),
TokenOwned::Str => Token::Str.fmt(f), TokenOwned::Str => Token::Str.fmt(f),
TokenOwned::String(string) => Token::String(string).fmt(f), TokenOwned::String(string) => Token::String(string).fmt(f),
TokenOwned::Struct => Token::Struct.fmt(f), TokenOwned::Struct => Token::Struct.fmt(f),
@ -456,76 +578,17 @@ impl Display for TokenOwned {
} }
} }
/// Token representation that holds no data.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum TokenKind {
Eof,
Identifier,
// Hard-coded values
Boolean,
Character,
Float,
Integer,
String,
// Keywords
Async,
Bool,
Break,
Else,
FloatKeyword,
If,
Int,
Let,
Loop,
Map,
Str,
While,
// Symbols
BangEqual,
Bang,
Colon,
Comma,
Dot,
DoubleAmpersand,
DoubleDot,
DoubleEqual,
DoublePipe,
Equal,
Greater,
GreaterOrEqual,
LeftCurlyBrace,
LeftParenthesis,
LeftSquareBrace,
Less,
LessOrEqual,
Minus,
MinusEqual,
Mut,
Percent,
Plus,
PlusEqual,
RightCurlyBrace,
RightParenthesis,
RightSquareBrace,
Semicolon,
Star,
Struct,
Slash,
}
impl Display for TokenKind { impl Display for TokenKind {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self { match self {
TokenKind::ArrowThin => Token::ArrowThin.fmt(f),
TokenKind::Async => Token::Async.fmt(f), TokenKind::Async => Token::Async.fmt(f),
TokenKind::Bang => Token::Bang.fmt(f), TokenKind::Bang => Token::Bang.fmt(f),
TokenKind::BangEqual => Token::BangEqual.fmt(f), TokenKind::BangEqual => Token::BangEqual.fmt(f),
TokenKind::Bool => Token::Bool.fmt(f), TokenKind::Bool => Token::Bool.fmt(f),
TokenKind::Boolean => write!(f, "boolean value"), TokenKind::Boolean => write!(f, "boolean value"),
TokenKind::Break => Token::Break.fmt(f), TokenKind::Break => Token::Break.fmt(f),
TokenKind::Byte => write!(f, "byte value"),
TokenKind::Character => write!(f, "character value"), TokenKind::Character => write!(f, "character value"),
TokenKind::Colon => Token::Colon.fmt(f), TokenKind::Colon => Token::Colon.fmt(f),
TokenKind::Comma => Token::Comma.fmt(f), TokenKind::Comma => Token::Comma.fmt(f),
@ -539,8 +602,9 @@ impl Display for TokenKind {
TokenKind::Equal => Token::Equal.fmt(f), TokenKind::Equal => Token::Equal.fmt(f),
TokenKind::Float => write!(f, "float value"), TokenKind::Float => write!(f, "float value"),
TokenKind::FloatKeyword => Token::FloatKeyword.fmt(f), TokenKind::FloatKeyword => Token::FloatKeyword.fmt(f),
TokenKind::Fn => Token::Fn.fmt(f),
TokenKind::Greater => Token::Greater.fmt(f), TokenKind::Greater => Token::Greater.fmt(f),
TokenKind::GreaterOrEqual => Token::GreaterEqual.fmt(f), TokenKind::GreaterEqual => Token::GreaterEqual.fmt(f),
TokenKind::Identifier => write!(f, "identifier"), TokenKind::Identifier => write!(f, "identifier"),
TokenKind::If => Token::If.fmt(f), TokenKind::If => Token::If.fmt(f),
TokenKind::Int => Token::Int.fmt(f), TokenKind::Int => Token::Int.fmt(f),
@ -550,102 +614,29 @@ impl Display for TokenKind {
TokenKind::LeftSquareBrace => Token::LeftSquareBrace.fmt(f), TokenKind::LeftSquareBrace => Token::LeftSquareBrace.fmt(f),
TokenKind::Let => Token::Let.fmt(f), TokenKind::Let => Token::Let.fmt(f),
TokenKind::Less => Token::Less.fmt(f), TokenKind::Less => Token::Less.fmt(f),
TokenKind::LessOrEqual => Token::LessEqual.fmt(f), TokenKind::LessEqual => Token::LessEqual.fmt(f),
TokenKind::Loop => Token::Loop.fmt(f), TokenKind::Loop => Token::Loop.fmt(f),
TokenKind::Map => Token::Map.fmt(f), TokenKind::Map => Token::Map.fmt(f),
TokenKind::Minus => Token::Minus.fmt(f), TokenKind::Minus => Token::Minus.fmt(f),
TokenKind::MinusEqual => Token::MinusEqual.fmt(f), TokenKind::MinusEqual => Token::MinusEqual.fmt(f),
TokenKind::Mut => Token::Mut.fmt(f), TokenKind::Mut => Token::Mut.fmt(f),
TokenKind::Percent => Token::Percent.fmt(f), TokenKind::Percent => Token::Percent.fmt(f),
TokenKind::PercentEqual => Token::PercentEqual.fmt(f),
TokenKind::Plus => Token::Plus.fmt(f), TokenKind::Plus => Token::Plus.fmt(f),
TokenKind::PlusEqual => Token::PlusEqual.fmt(f), TokenKind::PlusEqual => Token::PlusEqual.fmt(f),
TokenKind::Return => Token::Return.fmt(f),
TokenKind::RightCurlyBrace => Token::RightCurlyBrace.fmt(f), TokenKind::RightCurlyBrace => Token::RightCurlyBrace.fmt(f),
TokenKind::RightParenthesis => Token::RightParenthesis.fmt(f), TokenKind::RightParenthesis => Token::RightParenthesis.fmt(f),
TokenKind::RightSquareBrace => Token::RightSquareBrace.fmt(f), TokenKind::RightSquareBrace => Token::RightSquareBrace.fmt(f),
TokenKind::Semicolon => Token::Semicolon.fmt(f), TokenKind::Semicolon => Token::Semicolon.fmt(f),
TokenKind::Star => Token::Star.fmt(f), TokenKind::Star => Token::Star.fmt(f),
TokenKind::StarEqual => Token::StarEqual.fmt(f),
TokenKind::Str => Token::Str.fmt(f), TokenKind::Str => Token::Str.fmt(f),
TokenKind::Slash => Token::Slash.fmt(f), TokenKind::Slash => Token::Slash.fmt(f),
TokenKind::SlashEqual => Token::SlashEqual.fmt(f),
TokenKind::String => write!(f, "string value"), TokenKind::String => write!(f, "string value"),
TokenKind::Struct => Token::Struct.fmt(f), TokenKind::Struct => Token::Struct.fmt(f),
TokenKind::While => Token::While.fmt(f), TokenKind::While => Token::While.fmt(f),
} }
} }
} }
#[cfg(test)]
pub(crate) mod tests {
use super::*;
pub fn all_tokens<'src>() -> [Token<'src>; 47] {
[
Token::Async,
Token::Bang,
Token::BangEqual,
Token::Bool,
Token::Break,
Token::Colon,
Token::Comma,
Token::Dot,
Token::DoubleAmpersand,
Token::DoubleDot,
Token::DoubleEqual,
Token::DoublePipe,
Token::Else,
Token::Eof,
Token::Equal,
Token::FloatKeyword,
Token::Greater,
Token::GreaterEqual,
Token::If,
Token::Int,
Token::LeftCurlyBrace,
Token::LeftParenthesis,
Token::LeftSquareBrace,
Token::Let,
Token::Less,
Token::LessEqual,
Token::Map,
Token::Minus,
Token::MinusEqual,
Token::Mut,
Token::Percent,
Token::Plus,
Token::PlusEqual,
Token::RightCurlyBrace,
Token::RightParenthesis,
Token::RightSquareBrace,
Token::Semicolon,
Token::Star,
Token::Str,
Token::Slash,
Token::Boolean("true"),
Token::Float("0.0"),
Token::Integer("0"),
Token::String("string"),
Token::Identifier("foobar"),
Token::Struct,
Token::While,
]
}
#[test]
fn token_displays() {
for token in all_tokens().iter() {
let display = token.to_string();
assert_eq!(display, token.to_owned().to_string());
if let Token::Boolean(_)
| Token::Float(_)
| Token::Identifier(_)
| Token::Integer(_)
| Token::String(_) = token
{
continue;
} else {
assert_eq!(display, token.kind().to_string());
}
}
}
}

View File

@ -1,14 +1,4 @@
//! Description of a kind of value. //! Value types and conflict handling.
//!
//! Most types are concrete and specific, the exceptions are the Generic and Any types.
//!
//! Generic types are temporary placeholders that describe a type that will be defined later. The
//! interpreter should use the analysis phase to enforce that all Generic types have a concrete
//! type assigned to them before the program is run.
//!
//! The Any type is used in cases where a value's type does not matter. For example, the standard
//! library's "length" function does not care about the type of item in the list, only the list
//! itself. So the input is defined as `[any]`, i.e. `Type::ListOf(Box::new(Type::Any))`.
use std::{ use std::{
cmp::Ordering, cmp::Ordering,
collections::HashMap, collections::HashMap,
@ -17,11 +7,7 @@ use std::{
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{Constructor, BuiltInFunction, Identifier};
/// Description of a kind of value. /// Description of a kind of value.
///
/// See the [module documentation](index.html) for more information.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum Type { pub enum Type {
Any, Any,
@ -32,7 +18,7 @@ pub enum Type {
Float, Float,
Function(FunctionType), Function(FunctionType),
Generic { Generic {
identifier: Identifier, identifier_index: u8,
concrete_type: Option<Box<Type>>, concrete_type: Option<Box<Type>>,
}, },
Integer, Integer,
@ -45,7 +31,7 @@ pub enum Type {
item_type: Box<Type>, item_type: Box<Type>,
}, },
Map { Map {
pairs: HashMap<Identifier, Type>, pairs: HashMap<u8, Type>,
}, },
Number, Number,
Range { Range {
@ -191,20 +177,17 @@ impl Type {
} }
( (
Type::Function(FunctionType { Type::Function(FunctionType {
name: left_name,
type_parameters: left_type_parameters, type_parameters: left_type_parameters,
value_parameters: left_value_parameters, value_parameters: left_value_parameters,
return_type: left_return, return_type: left_return,
}), }),
Type::Function(FunctionType { Type::Function(FunctionType {
name: right_name,
type_parameters: right_type_parameters, type_parameters: right_type_parameters,
value_parameters: right_value_parameters, value_parameters: right_value_parameters,
return_type: right_return, return_type: right_return,
}), }),
) => { ) => {
if left_name != right_name if left_return != right_return
|| left_return != right_return
|| left_type_parameters != right_type_parameters || left_type_parameters != right_type_parameters
|| left_value_parameters != right_value_parameters || left_value_parameters != right_value_parameters
{ {
@ -233,48 +216,6 @@ impl Type {
expected: self.clone(), expected: self.clone(),
}) })
} }
pub fn has_field(&self, field: &Identifier) -> bool {
match field.as_str() {
"to_string" => true,
"length" => {
matches!(
self,
Type::List { .. }
| Type::ListOf { .. }
| Type::ListEmpty
| Type::Map { .. }
| Type::String { .. }
)
}
"is_even" | "is_odd" => matches!(self, Type::Integer | Type::Float),
_ => match self {
Type::Struct(StructType::Fields { fields, .. }) => fields.contains_key(field),
Type::Map { pairs } => pairs.contains_key(field),
_ => false,
},
}
}
pub fn get_field_type(&self, field: &Identifier) -> Option<Type> {
match field.as_str() {
"to_string" => Some(BuiltInFunction::ToString.r#type()),
"length" => match self {
Type::List { .. } => Some(Type::Integer),
Type::ListOf { .. } => Some(Type::Integer),
Type::ListEmpty => Some(Type::Integer),
Type::Map { .. } => Some(Type::Integer),
Type::String { .. } => Some(Type::Integer),
_ => None,
},
"is_even" | "is_odd" => Some(Type::Boolean),
_ => match self {
Type::Struct(StructType::Fields { fields, .. }) => fields.get(field).cloned(),
Type::Map { pairs } => pairs.get(field).cloned(),
_ => None,
},
}
}
} }
impl Display for Type { impl Display for Type {
@ -289,7 +230,10 @@ impl Display for Type {
Type::Function(function_type) => write!(f, "{function_type}"), Type::Function(function_type) => write!(f, "{function_type}"),
Type::Generic { concrete_type, .. } => { Type::Generic { concrete_type, .. } => {
match concrete_type.clone().map(|r#box| *r#box) { match concrete_type.clone().map(|r#box| *r#box) {
Some(Type::Generic { identifier, .. }) => write!(f, "{identifier}"), Some(Type::Generic {
identifier_index: identifier,
..
}) => write!(f, "{identifier}"),
Some(concrete_type) => write!(f, "implied to be {concrete_type}"), Some(concrete_type) => write!(f, "implied to be {concrete_type}"),
None => write!(f, "unknown"), None => write!(f, "unknown"),
} }
@ -420,9 +364,8 @@ impl Ord for Type {
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct FunctionType { pub struct FunctionType {
pub name: Identifier, pub type_parameters: Option<Vec<u8>>,
pub type_parameters: Option<Vec<Identifier>>, pub value_parameters: Option<Vec<(u8, Type)>>,
pub value_parameters: Option<Vec<(Identifier, Type)>>,
pub return_type: Option<Box<Type>>, pub return_type: Option<Box<Type>>,
} }
@ -434,11 +377,11 @@ impl Display for FunctionType {
write!(f, "<")?; write!(f, "<")?;
for (index, type_parameter) in type_parameters.iter().enumerate() { for (index, type_parameter) in type_parameters.iter().enumerate() {
write!(f, "{type_parameter}")?; if index > 0 {
if index != type_parameters.len() - 1 {
write!(f, ", ")?; write!(f, ", ")?;
} }
write!(f, "{type_parameter}")?;
} }
write!(f, ">")?; write!(f, ">")?;
@ -448,11 +391,11 @@ impl Display for FunctionType {
if let Some(value_parameters) = &self.value_parameters { if let Some(value_parameters) = &self.value_parameters {
for (index, (identifier, r#type)) in value_parameters.iter().enumerate() { for (index, (identifier, r#type)) in value_parameters.iter().enumerate() {
write!(f, "{identifier}: {type}")?; if index > 0 {
if index != value_parameters.len() - 1 {
write!(f, ", ")?; write!(f, ", ")?;
} }
write!(f, "{identifier}: {type}")?;
} }
} }
@ -468,31 +411,17 @@ impl Display for FunctionType {
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum StructType { pub enum StructType {
Unit { Unit { name: u8 },
name: Identifier, Tuple { name: u8, fields: Vec<Type> },
}, Fields { name: u8, fields: HashMap<u8, Type> },
Tuple {
name: Identifier,
fields: Vec<Type>,
},
Fields {
name: Identifier,
fields: HashMap<Identifier, Type>,
},
} }
impl StructType { impl StructType {
pub fn name(&self) -> &Identifier { pub fn name(&self) -> u8 {
match self { match self {
StructType::Unit { name } => name, StructType::Unit { name } => *name,
StructType::Tuple { name, .. } => name, StructType::Tuple { name, .. } => *name,
StructType::Fields { name, .. } => name, StructType::Fields { name, .. } => *name,
}
}
pub fn constructor(&self) -> Constructor {
Constructor {
struct_type: self.clone(),
} }
} }
} }
@ -594,7 +523,7 @@ impl Ord for StructType {
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct EnumType { pub struct EnumType {
pub name: Identifier, pub name: u8,
pub variants: Vec<StructType>, pub variants: Vec<StructType>,
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

66
dust-lang/tests/basic.rs Normal file
View File

@ -0,0 +1,66 @@
use dust_lang::*;
#[test]
fn constant() {
let source = "42";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(0, 2)),
(Instruction::r#return(true), Span(2, 2))
],
vec![Value::integer(42)],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(42))));
}
#[test]
fn empty() {
let source = "";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![(Instruction::r#return(false), Span(0, 0))],
vec![],
vec![]
))
);
assert_eq!(run(source), Ok(None));
}
#[test]
fn parentheses_precedence() {
let source = "(1 + 2) * 3";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::add(0, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(3, 4)
),
(
*Instruction::multiply(1, 0, 2).set_c_is_constant(),
Span(8, 9)
),
(Instruction::r#return(true), Span(11, 11)),
],
vec![Value::integer(1), Value::integer(2), Value::integer(3)],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(9))));
}

View File

@ -0,0 +1,169 @@
use dust_lang::*;
#[test]
fn equal() {
let source = "1 == 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 4)
),
(Instruction::jump(1, true), Span(2, 4)),
(Instruction::load_boolean(0, true, true), Span(2, 4)),
(Instruction::load_boolean(0, false, false), Span(2, 4)),
(Instruction::r#return(true), Span(6, 6)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
}
#[test]
fn greater() {
let source = "1 > 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::less_equal(false, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 3)
),
(Instruction::jump(1, true), Span(2, 3)),
(Instruction::load_boolean(0, true, true), Span(2, 3)),
(Instruction::load_boolean(0, false, false), Span(2, 3)),
(Instruction::r#return(true), Span(5, 5)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
}
#[test]
fn greater_than_or_equal() {
let source = "1 >= 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::less(false, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 4)
),
(Instruction::jump(1, true), Span(2, 4)),
(Instruction::load_boolean(0, true, true), Span(2, 4)),
(Instruction::load_boolean(0, false, false), Span(2, 4)),
(Instruction::r#return(true), Span(6, 6)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
}
#[test]
fn less_than() {
let source = "1 < 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::less(true, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 3)
),
(Instruction::jump(1, true), Span(2, 3)),
(Instruction::load_boolean(0, true, true), Span(2, 3)),
(Instruction::load_boolean(0, false, false), Span(2, 3)),
(Instruction::r#return(true), Span(5, 5)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
}
#[test]
fn less_than_or_equal() {
let source = "1 <= 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::less_equal(true, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 4)
),
(Instruction::jump(1, true), Span(2, 4)),
(Instruction::load_boolean(0, true, true), Span(2, 4)),
(Instruction::load_boolean(0, false, false), Span(2, 4)),
(Instruction::r#return(true), Span(6, 6)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
}
#[test]
fn not_equal() {
let source = "1 != 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(false, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 4)
),
(Instruction::jump(1, true), Span(2, 4)),
(Instruction::load_boolean(0, true, true), Span(2, 4)),
(Instruction::load_boolean(0, false, false), Span(2, 4)),
(Instruction::r#return(true), Span(6, 6)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
}

View File

@ -0,0 +1,420 @@
use dust_lang::*;
#[test]
fn equality_assignment_long() {
let source = "let a = if 4 == 4 { true } else { false }; a";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 0)
.set_b_is_constant()
.set_c_is_constant(),
Span(13, 15)
),
(Instruction::jump(1, true), Span(18, 19)),
(Instruction::load_boolean(0, true, true), Span(20, 24)),
(Instruction::load_boolean(0, false, false), Span(34, 39)),
(Instruction::define_local(0, 0, false), Span(4, 5)),
(Instruction::get_local(1, 0), Span(43, 44)),
(Instruction::r#return(true), Span(44, 44)),
],
vec![Value::integer(4), Value::string("a")],
vec![Local::new(
1,
None,
false,
Scope {
depth: 0,
block_index: 0
},
0
)]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
}
#[test]
fn equality_assignment_short() {
let source = "let a = 4 == 4 a";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 0)
.set_b_is_constant()
.set_c_is_constant(),
Span(10, 12)
),
(Instruction::jump(1, true), Span(10, 12)),
(Instruction::load_boolean(0, true, true), Span(10, 12)),
(Instruction::load_boolean(0, false, false), Span(10, 12)),
(Instruction::define_local(0, 0, false), Span(4, 5)),
(Instruction::get_local(1, 0), Span(15, 16)),
(Instruction::r#return(true), Span(16, 16)),
],
vec![Value::integer(4), Value::string("a")],
vec![Local::new(1, None, false, Scope::default(), 0)]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
}
#[test]
fn if_else_assigment_false() {
let source = r#"
let a = if 4 == 3 {
1; 2; 3; 4;
panic()
} else {
1; 2; 3; 4;
42
};
a"#;
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(22, 24)
),
(Instruction::jump(6, true), Span(27, 28)),
(Instruction::load_constant(0, 2, false), Span(41, 42)),
(Instruction::load_constant(1, 3, false), Span(44, 45)),
(Instruction::load_constant(2, 1, false), Span(47, 48)),
(Instruction::load_constant(3, 0, false), Span(50, 51)),
(
Instruction::call_native(4, NativeFunction::Panic, 0),
Span(65, 72)
),
(Instruction::jump(5, true), Span(138, 139)),
(Instruction::load_constant(5, 2, false), Span(102, 103)),
(Instruction::load_constant(6, 3, false), Span(105, 106)),
(Instruction::load_constant(7, 1, false), Span(108, 109)),
(Instruction::load_constant(8, 0, false), Span(111, 112)),
(Instruction::load_constant(9, 4, false), Span(126, 128)),
(Instruction::r#move(9, 4), Span(138, 139)),
(Instruction::define_local(9, 0, false), Span(13, 14)),
(Instruction::get_local(10, 0), Span(148, 149)),
(Instruction::r#return(true), Span(149, 149)),
],
vec![
Value::integer(4),
Value::integer(3),
Value::integer(1),
Value::integer(2),
Value::integer(42),
Value::string("a")
],
vec![Local::new(5, None, false, Scope::default(), 0)]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(42))));
}
#[test]
fn if_else_assigment_true() {
let source = r#"
let a = if 4 == 4 {
1; 2; 3; 4;
42
} else {
1; 2; 3; 4;
panic()
};
a"#;
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 0)
.set_b_is_constant()
.set_c_is_constant(),
Span(22, 24)
),
(Instruction::jump(6, true), Span(27, 28)),
(Instruction::load_constant(0, 1, false), Span(41, 42)),
(Instruction::load_constant(1, 2, false), Span(44, 45)),
(Instruction::load_constant(2, 3, false), Span(47, 48)),
(Instruction::load_constant(3, 0, false), Span(50, 51)),
(Instruction::load_constant(4, 4, false), Span(65, 67)),
(Instruction::jump(5, true), Span(138, 139)),
(Instruction::load_constant(5, 1, false), Span(97, 98)),
(Instruction::load_constant(6, 2, false), Span(100, 101)),
(Instruction::load_constant(7, 3, false), Span(103, 104)),
(Instruction::load_constant(8, 0, false), Span(106, 107)),
(
Instruction::call_native(9, NativeFunction::Panic, 0),
Span(121, 128)
),
(Instruction::r#move(9, 4), Span(138, 139)),
(Instruction::define_local(9, 0, false), Span(13, 14)),
(Instruction::get_local(10, 0), Span(148, 149)),
(Instruction::r#return(true), Span(149, 149)),
],
vec![
Value::integer(4),
Value::integer(1),
Value::integer(2),
Value::integer(3),
Value::integer(42),
Value::string("a")
],
vec![Local::new(5, None, false, Scope::default(), 0)]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(42))));
}
#[test]
fn if_else_complex() {
let source = "
if 1 == 1 {
1; 2; 3; 4;
} else {
1; 2; 3; 4;
}";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 0)
.set_b_is_constant()
.set_c_is_constant(),
Span(14, 16)
),
(Instruction::jump(5, true), Span(19, 20)),
(Instruction::load_constant(0, 0, false), Span(33, 34)),
(Instruction::load_constant(1, 1, false), Span(36, 37)),
(Instruction::load_constant(2, 2, false), Span(39, 40)),
(Instruction::load_constant(3, 3, false), Span(42, 43)),
(Instruction::jump(4, true), Span(95, 95)),
(Instruction::load_constant(4, 0, false), Span(74, 75)),
(Instruction::load_constant(5, 1, false), Span(77, 78)),
(Instruction::load_constant(6, 2, false), Span(80, 81)),
(Instruction::load_constant(7, 3, false), Span(83, 84)),
(Instruction::r#move(7, 3), Span(95, 95)),
(Instruction::r#return(false), Span(95, 95)),
],
vec![
Value::integer(1),
Value::integer(2),
Value::integer(3),
Value::integer(4),
],
vec![]
))
);
assert_eq!(run(source), Ok(None));
}
// #[test]
// fn if_else_nested() {
// let source = r#"
// if 0 == 1 {
// if 0 == 2 {
// 1;
// } else {
// 2;
// }
// } else {
// if 0 == 3 {
// 3;
// } else {
// 4;
// }
// }"#;
// assert_eq!(
// parse(source),
// Ok(Chunk::with_data(
// None,
// vec![
// (
// *Instruction::equal(true, 0, 1)
// .set_b_is_constant()
// .set_c_is_constant(),
// Span(14, 16)
// ),
// (Instruction::jump(7, true), Span(14, 16)),
// (
// *Instruction::equal(true, 0, 2)
// .set_b_is_constant()
// .set_c_is_constant(),
// Span(38, 41)
// ),
// (Instruction::jump(3, true), Span(38, 41)),
// (Instruction::load_constant(0, 1, false), Span(61, 62)),
// (Instruction::jump(1, true1), Span(95, 95)),
// (
// *Instruction::equal(true, 0, 3)
// .set_b_is_constant()
// .set_c_is_constant(),
// Span(77, 79)
// ),
// (Instruction::jump(3, true), Span(77, 79)),
// (Instruction::load_constant(0, 2, false), Span(94, 95)),
// (Instruction::jump(1, true1), Span(95, 95)),
// (Instruction::load_constant(0, 3, false), Span(114, 115)),
// (Instruction::jump(1, true1), Span(95, 95)),
// (Instruction::load_constant(0, 4, false), Span(134, 135)),
// (Instruction::r#return(true), Span(146, 146)),
// ],
// vec![
// Value::integer(0),
// Value::integer(1),
// Value::integer(0),
// Value::integer(2),
// Value::integer(1),
// Value::integer(0),
// Value::integer(3),
// Value::integer(3),
// Value::integer(4)
// ],
// vec![]
// ))
// );
// assert_eq!(run(source), Ok(Some(Value::integer(4))));
// }
#[test]
fn if_else_false() {
let source = "if 1 == 2 { panic() } else { 42 }";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(5, 7)
),
(Instruction::jump(1, true), Span(10, 11)),
(
Instruction::call_native(0, NativeFunction::Panic, 0),
Span(12, 19)
),
(Instruction::load_constant(1, 2, true), Span(29, 31)),
(Instruction::r#move(1, 0), Span(33, 33)),
(Instruction::r#return(true), Span(33, 33)),
],
vec![Value::integer(1), Value::integer(2), Value::integer(42)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(42))));
}
#[test]
fn if_else_true() {
let source = "if 1 == 1 { 42 } else { panic() }";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 0)
.set_b_is_constant()
.set_c_is_constant(),
Span(5, 7)
),
(Instruction::jump(1, true), Span(10, 11)),
(Instruction::load_constant(0, 1, true), Span(12, 14)),
(
Instruction::call_native(1, NativeFunction::Panic, 0),
Span(24, 31)
),
(Instruction::r#move(1, 0), Span(33, 33)),
(Instruction::r#return(true), Span(33, 33))
],
vec![Value::integer(1), Value::integer(42)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(42))));
}
#[test]
fn if_false() {
let source = "if 1 == 2 { 2 }";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(5, 7)
),
(Instruction::jump(1, true), Span(10, 11)),
(Instruction::load_constant(0, 1, false), Span(12, 13)),
(Instruction::r#return(false), Span(15, 15))
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(None));
}
#[test]
fn if_true() {
let source = "if 1 == 1 { 2 }";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::equal(true, 0, 0)
.set_b_is_constant()
.set_c_is_constant(),
Span(5, 7)
),
(Instruction::jump(1, true), Span(10, 11)),
(Instruction::load_constant(0, 1, false), Span(12, 13)),
(Instruction::r#return(false), Span(15, 15))
],
vec![Value::integer(1), Value::integer(2)],
vec![]
)),
);
assert_eq!(run(source), Ok(None));
}

View File

@ -0,0 +1,126 @@
use dust_lang::*;
#[test]
fn function() {
let source = "fn(a: int, b: int) -> int { a + b }";
assert_eq!(
run(source),
Ok(Some(Value::function(
Chunk::with_data(
None,
vec![
(Instruction::add(2, 0, 1), Span(30, 31)),
(Instruction::r#return(true), Span(35, 35)),
],
vec![Value::string("a"), Value::string("b"),],
vec![
Local::new(0, Some(Type::Integer), false, Scope::default(), 0),
Local::new(1, Some(Type::Integer), false, Scope::default(), 1)
]
),
FunctionType {
type_parameters: None,
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
return_type: Some(Box::new(Type::Integer)),
}
)))
);
}
#[test]
fn function_call() {
let source = "fn(a: int, b: int) -> int { a + b }(1, 2)";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(0, 36)),
(Instruction::load_constant(1, 1, false), Span(36, 37)),
(Instruction::load_constant(2, 2, false), Span(39, 40)),
(Instruction::call(3, 0, 2), Span(35, 41)),
(Instruction::r#return(true), Span(41, 41)),
],
vec![
Value::function(
Chunk::with_data(
None,
vec![
(Instruction::add(2, 0, 1), Span(30, 31)),
(Instruction::r#return(true), Span(35, 36)),
],
vec![Value::string("a"), Value::string("b"),],
vec![
Local::new(0, Some(Type::Integer), false, Scope::default(), 0),
Local::new(1, Some(Type::Integer), false, Scope::default(), 1)
]
),
FunctionType {
type_parameters: None,
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
return_type: Some(Box::new(Type::Integer)),
}
),
Value::integer(1),
Value::integer(2)
],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(3))));
}
#[test]
fn function_declaration() {
let source = "fn add (a: int, b: int) -> int { a + b }";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 1, false), Span(0, 40)),
(Instruction::define_local(0, 0, false), Span(3, 6)),
(Instruction::r#return(false), Span(40, 40))
],
vec![
Value::string("add"),
Value::function(
Chunk::with_data(
None,
vec![
(Instruction::add(2, 0, 1), Span(35, 36)),
(Instruction::r#return(true), Span(40, 40)),
],
vec![Value::string("a"), Value::string("b")],
vec![
Local::new(0, Some(Type::Integer), false, Scope::default(), 0),
Local::new(1, Some(Type::Integer), false, Scope::default(), 1)
]
),
FunctionType {
type_parameters: None,
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
return_type: Some(Box::new(Type::Integer)),
},
)
],
vec![Local::new(
0,
Some(Type::Function(FunctionType {
type_parameters: None,
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
return_type: Some(Box::new(Type::Integer)),
})),
false,
Scope::default(),
0
),],
)),
);
assert_eq!(run(source), Ok(None));
}

127
dust-lang/tests/lists.rs Normal file
View File

@ -0,0 +1,127 @@
use dust_lang::*;
#[test]
fn empty_list() {
let source = "[]";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_list(0, 0), Span(0, 2)),
(Instruction::r#return(true), Span(2, 2)),
],
vec![],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::abstract_list(0, 0, Type::Any))));
}
#[test]
fn list() {
let source = "[1, 2, 3]";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(1, 2)),
(Instruction::load_constant(1, 1, false), Span(4, 5)),
(Instruction::load_constant(2, 2, false), Span(7, 8)),
(Instruction::load_list(3, 0), Span(0, 9)),
(Instruction::r#return(true), Span(9, 9)),
],
vec![Value::integer(1), Value::integer(2), Value::integer(3)],
vec![]
)),
);
assert_eq!(
run(source),
Ok(Some(Value::abstract_list(0, 3, Type::Integer)))
);
}
#[test]
fn list_with_complex_expression() {
let source = "[1, 2 + 3 - 4 * 5]";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(1, 2)),
(
*Instruction::add(1, 1, 2)
.set_b_is_constant()
.set_c_is_constant(),
Span(6, 7)
),
(
*Instruction::multiply(2, 3, 4)
.set_b_is_constant()
.set_c_is_constant(),
Span(14, 15)
),
(Instruction::subtract(3, 1, 2), Span(10, 11)),
(Instruction::close(1, 3), Span(17, 18)),
(Instruction::load_list(4, 0), Span(0, 18)),
(Instruction::r#return(true), Span(18, 18)),
],
vec![
Value::integer(1),
Value::integer(2),
Value::integer(3),
Value::integer(4),
Value::integer(5)
],
vec![]
)),
);
assert_eq!(
run(source),
Ok(Some(Value::abstract_list(0, 4, Type::Integer)))
);
}
#[test]
fn list_with_simple_expression() {
let source = "[1, 2 + 3, 4]";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(1, 2)),
(
*Instruction::add(1, 1, 2)
.set_b_is_constant()
.set_c_is_constant(),
Span(6, 7)
),
(Instruction::load_constant(2, 3, false), Span(11, 12)),
(Instruction::load_list(3, 0), Span(0, 13)),
(Instruction::r#return(true), Span(13, 13)),
],
vec![
Value::integer(1),
Value::integer(2),
Value::integer(3),
Value::integer(4),
],
vec![]
)),
);
assert_eq!(
run(source),
Ok(Some(Value::abstract_list(0, 3, Type::Integer)))
);
}

77
dust-lang/tests/logic.rs Normal file
View File

@ -0,0 +1,77 @@
use dust_lang::*;
#[test]
fn and() {
let source = "true && false";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_boolean(0, true, false), Span(0, 4)),
(Instruction::test(0, false), Span(5, 7)),
(Instruction::jump(1, true), Span(5, 7)),
(Instruction::load_boolean(1, false, false), Span(8, 13)),
(Instruction::r#return(true), Span(13, 13)),
],
vec![],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
}
#[test]
fn or() {
let source = "true || false";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_boolean(0, true, false), Span(0, 4)),
(Instruction::test(0, true), Span(5, 7)),
(Instruction::jump(1, true), Span(5, 7)),
(Instruction::load_boolean(1, false, false), Span(8, 13)),
(Instruction::r#return(true), Span(13, 13)),
],
vec![],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
}
#[test]
fn variable_and() {
let source = "let a = true; let b = false; a && b";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_boolean(0, true, false), Span(8, 12)),
(Instruction::define_local(0, 0, false), Span(4, 5)),
(Instruction::load_boolean(1, false, false), Span(22, 27)),
(Instruction::define_local(1, 1, false), Span(18, 19)),
(Instruction::get_local(2, 0), Span(29, 30)),
(Instruction::test(2, false), Span(31, 33)),
(Instruction::jump(1, true), Span(31, 33)),
(Instruction::get_local(3, 1), Span(34, 35)),
(Instruction::r#return(true), Span(35, 35)),
],
vec![Value::string("a"), Value::string("b"),],
vec![
Local::new(0, None, false, Scope::default(), 0),
Local::new(1, None, false, Scope::default(), 1),
]
))
);
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
}

35
dust-lang/tests/loops.rs Normal file
View File

@ -0,0 +1,35 @@
use dust_lang::*;
#[test]
fn r#while() {
let source = "let mut x = 0; while x < 5 { x = x + 1 } x";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(
*Instruction::less(true, 0, 2).set_c_is_constant(),
Span(23, 24)
),
(Instruction::jump(2, true), Span(41, 42)),
(*Instruction::add(0, 0, 3).set_c_is_constant(), Span(39, 40)),
(Instruction::jump(3, false), Span(41, 42)),
(Instruction::get_local(1, 0), Span(41, 42)),
(Instruction::r#return(true), Span(42, 42)),
],
vec![
Value::integer(0),
Value::string("x"),
Value::integer(5),
Value::integer(1),
],
vec![Local::new(1, None, true, Scope::default(), 0),]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(5))));
}

324
dust-lang/tests/math.rs Normal file
View File

@ -0,0 +1,324 @@
use dust_lang::*;
#[test]
fn add() {
let source = "1 + 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::add(0, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 3)
),
(Instruction::r#return(true), Span(5, 5))
],
vec![Value::integer(1), Value::integer(2)],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(3))));
}
#[test]
fn add_assign() {
let source = "let mut a = 1; a += 2; a";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(*Instruction::add(0, 0, 2).set_c_is_constant(), Span(17, 19)),
(Instruction::get_local(1, 0), Span(23, 24)),
(Instruction::r#return(true), Span(24, 24))
],
vec![Value::integer(1), Value::string("a"), Value::integer(2)],
vec![Local::new(1, None, true, Scope::default(), 0)]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(3))));
}
#[test]
fn add_assign_expects_mutable_variable() {
let source = "1 += 2";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::ExpectedMutableVariable {
found: Token::Integer("1").to_owned(),
position: Span(0, 1)
},
source
})
);
}
// #[test]
// fn add_expects_integer_float_or_string() {
// let source = "true + false";
// assert_eq!(
// parse(source),
// Err(DustError::Parse {
// error: ParseError::ExpectedIntegerFloatOrString {
// found: Token::True,
// position: Span(0, 3)
// },
// source
// })
// );
// }
#[test]
fn divide() {
let source = "2 / 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::divide(0, 0, 0)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 3)
),
(Instruction::r#return(true), Span(5, 5))
],
vec![Value::integer(2)],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(1))));
}
#[test]
fn divide_assign() {
let source = "let mut a = 2; a /= 2; a";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(
*Instruction::divide(0, 0, 0).set_c_is_constant(),
Span(17, 19)
),
(Instruction::get_local(1, 0), Span(23, 24)),
(Instruction::r#return(true), Span(24, 24))
],
vec![Value::integer(2), Value::string("a")],
vec![Local::new(1, None, true, Scope::default(), 0)]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(1))));
}
#[test]
fn divide_assign_expects_mutable_variable() {
let source = "1 -= 2";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::ExpectedMutableVariable {
found: Token::Integer("1").to_owned(),
position: Span(0, 1)
},
source
})
);
}
#[test]
fn math_operator_precedence() {
let source = "1 + 2 - 3 * 4 / 5";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::add(0, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 3)
),
(
*Instruction::multiply(1, 2, 3)
.set_b_is_constant()
.set_c_is_constant(),
Span(10, 11)
),
(
*Instruction::divide(2, 1, 4).set_c_is_constant(),
Span(14, 15)
),
(Instruction::subtract(3, 0, 2), Span(6, 7)),
(Instruction::r#return(true), Span(17, 17)),
],
vec![
Value::integer(1),
Value::integer(2),
Value::integer(3),
Value::integer(4),
Value::integer(5),
],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(1))));
}
#[test]
fn multiply() {
let source = "1 * 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::multiply(0, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 3)
),
(Instruction::r#return(true), Span(5, 5)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(2))));
}
#[test]
fn multiply_assign() {
let source = "let mut a = 2; a *= 3 a";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(
*Instruction::multiply(0, 0, 2).set_c_is_constant(),
Span(17, 19)
),
(Instruction::get_local(1, 0), Span(22, 23)),
(Instruction::r#return(true), Span(23, 23))
],
vec![Value::integer(2), Value::string("a"), Value::integer(3)],
vec![Local::new(1, None, true, Scope::default(), 0),]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(6))));
}
#[test]
fn multiply_assign_expects_mutable_variable() {
let source = "1 *= 2";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::ExpectedMutableVariable {
found: Token::Integer("1").to_owned(),
position: Span(0, 1)
},
source
})
);
}
#[test]
fn subtract() {
let source = "1 - 2";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(
*Instruction::subtract(0, 0, 1)
.set_b_is_constant()
.set_c_is_constant(),
Span(2, 3)
),
(Instruction::r#return(true), Span(5, 5)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(-1))));
}
#[test]
fn subtract_assign() {
let source = "let mut x = 42; x -= 2; x";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(12, 14)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(
*Instruction::subtract(0, 0, 2).set_c_is_constant(),
Span(18, 20)
),
(Instruction::get_local(1, 0), Span(24, 25)),
(Instruction::r#return(true), Span(25, 25)),
],
vec![Value::integer(42), Value::string("x"), Value::integer(2)],
vec![Local::new(1, None, true, Scope::default(), 0)]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(40))));
}
#[test]
fn subtract_assign_expects_mutable_variable() {
let source = "1 -= 2";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::ExpectedMutableVariable {
found: Token::Integer("1").to_owned(),
position: Span(0, 1)
},
source
})
);
}

View File

@ -0,0 +1,59 @@
use dust_lang::*;
#[test]
fn panic() {
let source = "panic(\"Goodbye world!\", 42)";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(6, 22)),
(Instruction::load_constant(1, 1, false), Span(24, 26)),
(
Instruction::call_native(2, NativeFunction::Panic, 2),
Span(0, 27)
),
(Instruction::r#return(true), Span(27, 27))
],
vec![Value::string("Goodbye world!"), Value::integer(42)],
vec![]
)),
);
assert_eq!(
run(source),
Err(DustError::Runtime {
error: VmError::NativeFunction(NativeFunctionError::Panic {
message: Some("Goodbye world! 42".to_string()),
position: Span(0, 27)
}),
source
})
)
}
#[test]
fn to_string() {
let source = "to_string(42)";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(10, 12)),
(
Instruction::call_native(1, NativeFunction::ToString, 1),
Span(0, 13)
),
(Instruction::r#return(true), Span(13, 13))
],
vec![Value::integer(42)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::string("42"))))
}

View File

@ -1,47 +1,244 @@
use dust_lang::*; use dust_lang::*;
#[test] #[test]
fn block_scope_captures_parent() { fn allow_access_to_parent_scope() {
let source = "let x = 42; { x }"; let source = r#"
let x = 1;
{
x
}
"#;
assert_eq!(run(source), Ok(Some(Value::integer(42)))); assert_eq!(run(source), Ok(Some(Value::integer(1))));
} }
#[test] #[test]
fn block_scope_does_not_capture_child() { fn block_scope() {
env_logger::builder().is_test(true).try_init().unwrap(); let source = "
let a = 0;
{
let b = 42;
{
let c = 1;
}
let d = 2;
}
let e = 1;
";
let source = "{ let x = 42; } x"; assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(17, 18)),
(Instruction::define_local(0, 0, false), Span(13, 14)),
(Instruction::load_constant(1, 2, false), Span(50, 52)),
(Instruction::define_local(1, 1, false), Span(46, 47)),
(Instruction::load_constant(2, 4, false), Span(92, 93)),
(Instruction::define_local(2, 2, false), Span(88, 89)),
(Instruction::load_constant(3, 6, false), Span(129, 130)),
(Instruction::define_local(3, 3, false), Span(125, 126)),
(Instruction::load_constant(4, 4, false), Span(158, 159)),
(Instruction::define_local(4, 4, false), Span(154, 155)),
(Instruction::r#return(false), Span(165, 165))
],
vec![
Value::integer(0),
Value::string("a"),
Value::integer(42),
Value::string("b"),
Value::integer(1),
Value::string("c"),
Value::integer(2),
Value::string("d"),
Value::string("e"),
],
vec![
Local::new(1, None, false, Scope::new(0, 0), 0),
Local::new(3, None, false, Scope::new(1, 1), 1),
Local::new(5, None, false, Scope::new(2, 2), 2),
Local::new(7, None, false, Scope::new(1, 1), 3),
Local::new(8, None, false, Scope::new(0, 0), 4),
]
)),
);
assert_eq!(run(source), Ok(None));
}
#[test]
fn multiple_block_scopes() {
let source = "
let a = 0;
{
let b = 42;
{
let c = 1;
}
let d = 2;
}
let q = 42;
{
let b = 42;
{
let c = 1;
}
let d = 2;
}
let e = 1;
";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(17, 18)),
(Instruction::define_local(0, 0, false), Span(13, 14)),
(Instruction::load_constant(1, 2, false), Span(50, 52)),
(Instruction::define_local(1, 1, false), Span(46, 47)),
(Instruction::load_constant(2, 4, false), Span(92, 93)),
(Instruction::define_local(2, 2, false), Span(88, 89)),
(Instruction::load_constant(3, 6, false), Span(129, 130)),
(Instruction::define_local(3, 3, false), Span(125, 126)),
(Instruction::load_constant(4, 2, false), Span(158, 160)),
(Instruction::define_local(4, 4, false), Span(154, 155)),
(Instruction::load_constant(5, 2, false), Span(192, 194)),
(Instruction::define_local(5, 5, false), Span(188, 189)),
(Instruction::load_constant(6, 4, false), Span(234, 235)),
(Instruction::define_local(6, 6, false), Span(230, 231)),
(Instruction::load_constant(7, 6, false), Span(271, 272)),
(Instruction::define_local(7, 7, false), Span(267, 268)),
(Instruction::load_constant(8, 4, false), Span(300, 301)),
(Instruction::define_local(8, 8, false), Span(296, 297)),
(Instruction::r#return(false), Span(307, 307))
],
vec![
Value::integer(0),
Value::string("a"),
Value::integer(42),
Value::string("b"),
Value::integer(1),
Value::string("c"),
Value::integer(2),
Value::string("d"),
Value::string("q"),
Value::string("e"),
],
vec![
Local::new(1, None, false, Scope::new(0, 0), 0),
Local::new(3, None, false, Scope::new(1, 1), 1),
Local::new(5, None, false, Scope::new(2, 2), 2),
Local::new(7, None, false, Scope::new(1, 1), 3),
Local::new(8, None, false, Scope::new(0, 0), 4),
Local::new(3, None, false, Scope::new(1, 3), 5),
Local::new(5, None, false, Scope::new(2, 4), 6),
Local::new(7, None, false, Scope::new(1, 3), 7),
Local::new(9, None, false, Scope::new(0, 0), 8),
]
)),
);
assert_eq!(run(source), Ok(None));
}
#[test]
fn disallow_access_to_child_scope() {
let source = r#"
{
let x = 1;
}
x
"#;
assert_eq!( assert_eq!(
run(source), run(source),
Err(DustError::analysis( Err(DustError::Compile {
[AnalysisError::UndefinedVariable { error: CompileError::VariableOutOfScope {
identifier: Node::new(Identifier::new("x"), (16, 17)) identifier: "x".to_string(),
}], position: Span(52, 53),
variable_scope: Scope::new(1, 1),
access_scope: Scope::new(0, 0),
},
source source
)) })
); );
} }
#[test] #[test]
fn block_scope_does_not_capture_sibling() { fn disallow_access_to_child_scope_nested() {
let source = "{ let x = 42; } { x }"; let source = r#"
{
{
let x = 1;
}
x
}
"#;
assert_eq!( assert_eq!(
run(source), run(source),
Err(DustError::analysis( Err(DustError::Compile {
[AnalysisError::UndefinedVariable { error: CompileError::VariableOutOfScope {
identifier: Node::new(Identifier::new("x"), (18, 19)) identifier: "x".to_string(),
}], position: Span(78, 79),
variable_scope: Scope::new(2, 2),
access_scope: Scope::new(1, 1),
},
source source
)) })
); );
} }
#[test] #[test]
fn block_scope_does_not_pollute_parent() { fn disallow_access_to_sibling_scope() {
let source = "let x = 42; { let x = \"foo\"; let x = \"bar\"; } x"; let source = r#"
{
let x = 1;
}
{
x
}
"#;
assert_eq!(run(source), Ok(Some(Value::integer(42)))); assert_eq!(
run(source),
Err(DustError::Compile {
error: CompileError::VariableOutOfScope {
identifier: "x".to_string(),
variable_scope: Scope::new(1, 1),
access_scope: Scope::new(1, 2),
position: Span(66, 67),
},
source
})
);
}
#[test]
fn disallow_access_to_sibling_scope_nested() {
let source = r#"
{
{
let x = 1;
}
{
x
}
}
"#;
assert_eq!(
run(source),
Err(DustError::Compile {
error: CompileError::VariableOutOfScope {
identifier: "x".to_string(),
variable_scope: Scope::new(2, 2),
access_scope: Scope::new(2, 3),
position: Span(96, 97),
},
source
})
);
} }

View File

@ -0,0 +1,42 @@
use dust_lang::*;
#[test]
fn negate() {
let source = "-(42)";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(*Instruction::negate(0, 0).set_b_is_constant(), Span(0, 1)),
(Instruction::r#return(true), Span(5, 5)),
],
vec![Value::integer(42)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(-42))));
}
#[test]
fn not() {
let source = "!true";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_boolean(0, true, false), Span(1, 5)),
(Instruction::not(1, 0), Span(0, 1)),
(Instruction::r#return(true), Span(5, 5)),
],
vec![],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
}

View File

@ -0,0 +1,63 @@
use dust_lang::*;
#[test]
fn define_local() {
let source = "let x = 42;";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(8, 10)),
(Instruction::define_local(0, 0, false), Span(4, 5)),
(Instruction::r#return(false), Span(11, 11))
],
vec![Value::integer(42), Value::string("x")],
vec![Local::new(1, None, false, Scope::default(), 0)]
)),
);
assert_eq!(run(source), Ok(None));
}
#[test]
fn let_statement_expects_identifier() {
let source = "let 1 = 2";
assert_eq!(
compile(source),
Err(DustError::Compile {
error: CompileError::ExpectedToken {
expected: TokenKind::Identifier,
found: Token::Integer("1").to_owned(),
position: Span(4, 5)
},
source
})
);
}
#[test]
fn set_local() {
let source = "let mut x = 41; x = 42; x";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(12, 14)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(Instruction::load_constant(1, 2, false), Span(20, 22)),
(Instruction::set_local(1, 0), Span(16, 17)),
(Instruction::get_local(2, 0), Span(24, 25)),
(Instruction::r#return(true), Span(25, 25)),
],
vec![Value::integer(41), Value::string("x"), Value::integer(42)],
vec![Local::new(1, None, true, Scope::default(), 0)]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(42))));
}

View File

@ -9,5 +9,7 @@ repository.workspace = true
[dependencies] [dependencies]
clap = { version = "4.5.14", features = ["derive"] } clap = { version = "4.5.14", features = ["derive"] }
colored = "2.1.0"
dust-lang = { path = "../dust-lang" } dust-lang = { path = "../dust-lang" }
env_logger = "0.11.5" env_logger = "0.11.5"
log = "0.4.22"

View File

@ -1,55 +1,138 @@
use std::fs::read_to_string; use std::{fs::read_to_string, io::Write};
use clap::Parser; use clap::Parser;
use dust_lang::run; use colored::Colorize;
use dust_lang::{compile, format, run};
use log::{Level, LevelFilter};
#[derive(Parser)] #[derive(Parser)]
struct Cli { struct Cli {
/// Source code sent via command line
#[arg(short, long)] #[arg(short, long)]
command: Option<String>, command: Option<String>,
/// Whether to output formatted source code
#[arg(short, long)]
format: bool,
/// Whether to output line numbers in formatted source code
#[arg(long)]
format_line_numbers: Option<bool>,
/// Whether to output colors in formatted source code
#[arg(long)]
format_colored: Option<bool>,
/// Whether to output the disassembled chunk
#[arg(short, long)] #[arg(short, long)]
parse: bool, parse: bool,
/// Whether to style the disassembled chunk
#[arg(long)]
style_disassembly: Option<bool>,
/// Log level
#[arg(short, long)]
log: Option<LevelFilter>,
/// Path to a source code file
path: Option<String>, path: Option<String>,
} }
fn main() { fn main() {
env_logger::init();
let args = Cli::parse(); let args = Cli::parse();
let mut logger = env_logger::builder();
if let Some(command) = &args.command { logger.format(|buf, record| {
if args.parse { let level_display = match record.level() {
parse_and_display_errors(command); Level::Info => "INFO".bold().white(),
} else { Level::Debug => "DEBUG".bold().blue(),
run_and_display_errors(command); Level::Warn => "WARN".bold().yellow(),
} Level::Error => "ERROR".bold().red(),
} else if let Some(path) = &args.path { Level::Trace => "TRACE".bold().purple(),
let source = read_to_string(path).expect("Failed to read file"); };
let module = record
.module_path()
.map(|path| path.split("::").last().unwrap_or(path))
.unwrap_or("unknown")
.dimmed();
let display = format!("{level_display:5} {module:^6} {args}", args = record.args());
if args.parse { writeln!(buf, "{display}")
parse_and_display_errors(&source); });
} else {
run_and_display_errors(&source); if let Some(level) = args.log {
} logger.filter_level(level).init();
} else {
logger.parse_env("DUST_LOG").init();
} }
}
fn parse_and_display_errors(source: &str) { let source = if let Some(path) = &args.path {
match dust_lang::parse(source) { &read_to_string(path).expect("Failed to read file")
Ok(ast) => println!("{:#?}", ast), } else if let Some(command) = &args.command {
Err(error) => eprintln!("{}", error.report()), command
} } else {
} eprintln!("No input provided");
fn run_and_display_errors(source: &str) { return;
match run(source) { };
Ok(return_value) => {
if let Some(value) = return_value { if args.format {
println!("{}", value); let line_numbers = args.format_line_numbers.unwrap_or(true);
let colored = args.format_colored.unwrap_or(true);
log::info!("Formatting source");
match format(source, line_numbers, colored) {
Ok(formatted) => println!("{}", formatted),
Err(error) => {
eprintln!("{}", error.report());
} }
} }
Err(error) => eprintln!("{}", error.report()), }
if args.parse {
let styled = args.style_disassembly.unwrap_or(true);
log::info!("Parsing source");
match compile(source) {
Ok(chunk) => {
let disassembly = chunk
.disassembler()
.source(source)
.styled(styled)
.disassemble();
println!("{}", disassembly);
}
Err(error) => {
eprintln!("{}", error.report());
}
}
}
if args.format || args.parse {
return;
}
match run(source) {
Ok(Some(value)) => println!("{}", value),
Ok(None) => {}
Err(error) => {
eprintln!("{}", error.report());
}
}
}
#[cfg(test)]
mod tests {
use clap::CommandFactory;
use super::*;
#[test]
fn verify_cli() {
Cli::command().debug_assert();
} }
} }

5
examples/assets/count.js Normal file
View File

@ -0,0 +1,5 @@
var i = 0;
while (i < 10000) {
i++;
}

View File

@ -0,0 +1,11 @@
function fib(n) {
if (n <= 0) {
return 0;
} else if (n === 1) {
return 1;
} else {
return fib(n - 1) + fib(n - 2);
}
}
console.log(fib(25));

5
examples/count.ds Normal file
View File

@ -0,0 +1,5 @@
let mut i = 0;
while i < 10000 {
i += 1;
}

8
examples/fibonacci.ds Normal file
View File

@ -0,0 +1,8 @@
fn fib (n: int) -> int {
if n <= 0 { return 0 }
if n == 1 { return 1 }
fib(n - 1) + fib(n - 2)
}
write_line(fib(25))

View File

@ -11,7 +11,7 @@ while count <= 15 {
} else if divides_by_5 { } else if divides_by_5 {
"buzz" "buzz"
} else { } else {
count.to_string() to_string(count)
} }
write_line(output) write_line(output)